61 OrangePi Linux内核里的spi控制器驱动

时间:2021-01-27 17:56:17

在全志H3里有2个spi控制器, 每个控制器可有4个片选信号:
61  OrangePi Linux内核里的spi控制器驱动

//////////////////////////

script.bin里的spi控制器,spi设备相关设置:

[spi0]
spi_used = 1
spi_cs_bitmap = 1
spi_mosi = port:PC00<3><default><default><default>
spi_miso = port:PC01<3><default><default><default>
spi_sclk = port:PC02<3><default><default><default>
spi_cs0 = port:PC03<3><1><default><default>

[spi1]
spi_used = 0
spi_cs_bitmap = 1
spi_cs0 = port:PA13<2><1><default><default>
spi_sclk = port:PA14<2><default><default><default>
spi_mosi = port:PA15<2><default><default><default>
spi_miso = port:PA16<2><default><default><default>

[spi_devices]
spi_dev_num = 1

[spi_board0]
modalias = "spidev"
max_speed_hz = 33000000
bus_num = 0
chip_select = 0
mode = 0
full_duplex = 1
manual_cs = 0

/////////////////在linux内核源码里spi控制器驱动//////

make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

Device Drivers --->
[*] SPI support --->
<*> SUNXI SPI Controller

控制器驱动源码在”drivers/spi/spi-sunxi.c”
控制器的驱动都是平台驱动,由平台设备来描述具体控制器的硬件资源。这spi控件器驱动与i2c控制器驱动一样,平台设备与平台驱动都在spi-sunxi.c文件里了。用起来方便,但不是很规范.

spi-sunxi.c的主过程分析:

module_init(sunxi_spi_init);


static struct platform_driver sunxi_spi_driver = {
.probe = sunxi_spi_probe,
.remove = sunxi_spi_remove,
.driver = {
.name = SUNXI_SPI_DEV_NAME, //名字为"spi"
.owner = THIS_MODULE,
.pm = SUNXI_SPI_DEV_PM_OPS,
},
};


static int __init sunxi_spi_init(void)
{
int i, ret = 0;

sunxi_spi_device_scan(); //初始化控制器的平台设备,并提供控制器的配置寄存器地址,中断号等硬件资源
sunxi_spi_chan_cfg(sunxi_spi_pdata); //用全局设量spi_used_mask记录script.bin里"spi_used=1"的控制器

ret = sunxi_spi_register_spidev(); //注册script.bin里描述的spi设备
...

#ifdef CONFIG_EVB_PLATFORM //此宏成立
for (i=0; i<SUNXI_SPI_NUM; i++) // SUNXI_SPI_NUM的值为2
#else
i = CONFIG_SPI_CHAN_NUM; /* In FPGA, only one channel is available. */
#endif
{
if (sunxi_spi_chan_is_enable(i)) {
SPI_DBG("Sunxi SPI init channel %d \n", i);
ret = platform_device_register(&sunxi_spi_device[i]); //注册script.bin里"spi_used=1"的控制器的平台设备
...
sunxi_spi_sysfs(&sunxi_spi_device[i]); //并在"/sys"目录下的控制器的平台设备目录下创建出"info", "status"属性文件。
}
}

if (spi_used_mask)
return platform_driver_register(&sunxi_spi_driver); //如有控制器的平台设备注册,则也注册控制器的平台驱动
...
}

///////////////////////////细看初始化函数里调用到的函数////////////////////////////

static int spi_used_mask = 0; //全局变量用于记录script.bin里哪个控制器是"spi_used=1"

//下面三个全局数组表示每个spi控制器使用的平台设备对象,资源,平台数据等 
static struct resource sunxi_spi_resources[SUNXI_SPI_NUM * SUNXI_SPI_RES_NUM];
static struct sunxi_spi_platform_data sunxi_spi_pdata[SUNXI_SPI_NUM];
static struct platform_device sunxi_spi_device[SUNXI_SPI_NUM];


//初始化控制器的平台设备,并提供控制器的配置寄存器地址,中断号等硬件资源
static void __init sunxi_spi_device_scan(void)
{
int i;

memset(sunxi_spi_device, 0, sizeof(sunxi_spi_device));
memset(sunxi_spi_pdata, 0, sizeof(sunxi_spi_pdata));
memset(sunxi_spi_resources, 0, sizeof(sunxi_spi_resources));

for (i=0; i<SUNXI_SPI_NUM; i++) {
sunxi_spi_resources[i * SUNXI_SPI_RES_NUM].start = SUNXI_SPI_MEM_START(i);
...
sunxi_spi_resources[i * SUNXI_SPI_RES_NUM + 1].start = SUNXI_SPI_IRQ(i);
...
sunxi_spi_pdata[i].cs_bitmap = SUNXI_CS_BITMAP(i); // 值为1
sunxi_spi_pdata[i].cs_num = SUNXI_CS_NUM(i); // 值为1

sunxi_spi_device[i].name = SUNXI_SPI_DEV_NAME; //"spi"
sunxi_spi_device[i].id = i; //平台设备的ID,也就驱动好的控制器编号
... //下面准备控制器的平台设备的资源,平台数据
sunxi_spi_device[i].resource = &sunxi_spi_resources[i * SUNXI_SPI_RES_NUM];
sunxi_spi_device[i].num_resources = SUNXI_SPI_RES_NUM;
sunxi_spi_device[i].dev.platform_data = &sunxi_spi_pdata[i];
}
}

//用全局设量spi_used_mask记录script.bin里"spi_used=1"的控制器
static void sunxi_spi_chan_cfg(struct sunxi_spi_platform_data *pdata)
{
int i;
script_item_u item = {0};
script_item_value_type_e type = 0;
char spi_para[16] = {0};

for (i=0; i<SUNXI_SPI_NUM; i++) {
sprintf(spi_para, SUNXI_SPI_DEV_NAME"%d", i);
type = script_get_item(spi_para, "spi_used", &item);
...
if (item.val)
spi_used_mask |= SUNXI_SPI_CHAN_MASK(i);
...
}
}
////////////////
就像i2c设备驱动模型一样,spi设备驱动模型里用spi_board_info来描述spi设备,然后用spi_register_board_info函数来注册spi_board_info设备信息. 最后在控制器驱动对象注册时再创建出相应的spi_device设备对象

struct spi_board_info {
char modalias[SPI_NAME_SIZE]; //设备名字
const void *platform_data; //spi_device.dev.platform_data
void *controller_data; // spi_device.controller_data, 这里通常指定一个片选的操作函数及IO口. 在控制器驱动里会调用到
int irq; // spi_device.irq

u32 max_speed_hz; // spi_device.max_speed_hz;

u16 bus_num; //标明此SPI设备是挂载到哪个SPI控制器上
u16 chip_select; //一个控制器可以挂载多个设备,这里指定此设备使用的是控制器上的第几个片选, 使片选数组里的第几个片选

u8 mode; //工作时序方式
};


//根据script.bin里描述的spi设备,创建出对应的spi_board_info对象,并注册
static int __init sunxi_spi_register_spidev(void)
{
script_item_u spi_dev_num, temp_info;
script_item_value_type_e type;
int i, spidev_num;
char spi_board_name[32] = {0};
struct spi_board_info* board;

type = script_get_item("spi_devices", "spi_dev_num", &spi_dev_num); //获取script.bin里的"spi_devices"的键值
...
spidev_num = spi_dev_num.val; //表示在script.bin里描述spi设备的个数, 在script.bin里每个设备的主键是"[spi_board%d]"
...
spi_boards = (struct spi_board_info*)kzalloc(sizeof(struct spi_board_info) * spidev_num, GFP_KERNEL); //创建出所需的spi_board_info对象空间.


for (i=0; i<spidev_num; i++) {
board = &spi_boards[i];
sprintf(spi_board_name, "spi_board%d", i);

type = script_get_item(spi_board_name, "modalias", &temp_info); //获取"spi_board0"主键下的"modalias"子键的值
...
sprintf(board->modalias, "%s", temp_info.str); //把获取出的名字设为spi_board_info对象的名字

type = script_get_item(spi_board_name, "max_speed_hz", &temp_info); //设备的最大工作时钟
...
board->max_speed_hz = temp_info.val;

type = script_get_item(spi_board_name, "bus_num", &temp_info); //表示此设备是连接在哪个编号的控制器
...
board->bus_num = temp_info.val;

type = script_get_item(spi_board_name, "chip_select", &temp_info);//获取设备的片选线
...
board->chip_select = temp_info.val;

type = script_get_item(spi_board_name, "mode", &temp_info); //获取设备的时序工作方式
...
board->mode = temp_info.val;

SPI_INF("%-16d %-16s %-16d %-8d %-4d %-4d\n", i, board->modalias, board->max_speed_hz,
board->bus_num, board->chip_select, board->mode);
}

if (spi_register_board_info(spi_boards, spidev_num)) { //最后注册所有的spi_board_info信息
...
}

return 0;
}

///////////////////////////////////////////////////////////////////////

/////////// 在linux内核里,spi控制器驱动好后用struct spi_master的一个对象来描述.
struct spi_master {
struct device dev; //基于device成员扩展
...
s16 bus_num; //控制器对象的编号,由平台设备提供
...
u16 num_chipselect;
...
u16 mode_bits; //工作时序方式
...
};


在spi-sunxi.c里,封装了结构体struct sunxi_spi用于记录每个控制器对象在驱动里的数据
struct sunxi_spi {
struct platform_device *pdev; //指定控制器的平台设备对象地址
struct spi_master *master; //指定驱动好后创建出来的spi_master对象地址
...
enum spi_mode_type mode_type; //工作时序方式

unsigned int irq; //中断号
char dev_name[48]; //名字为"spi.%d"
...
};

控制器的平台驱动的probe函数
static int __devinit sunxi_spi_probe(struct platform_device *pdev)
{
struct resource *mem_res;
struct sunxi_spi *sspi;
struct sunxi_spi_platform_data *pdata;
struct spi_master *master;
int ret = 0, err = 0, irq;
int cs_bitmap = 0;

...
pdata = pdev->dev.platform_data;

//获取平台设备提供的资源
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
...
irq = platform_get_irq(pdev, 0);

//创建出spi_master对象的空间和struct sunxi_spi对象的空间, 并dev_set_drvdata(&spi_master.dev, sunxi_spi);
master = spi_alloc_master(&pdev->dev, sizeof(struct sunxi_spi));
...

platform_set_drvdata(pdev, master);
sspi = spi_master_get_devdata(master); // dev_get_drvdata(...)
memset(sspi, 0, sizeof(struct sunxi_spi));

sspi->master = master;
sspi->irq = irq;

..
sspi->cs_control = sunxi_spi_cs_control;
sspi->cs_bitmap = pdata->cs_bitmap; /* cs0-0x1; cs1-0x2; cs0&cs1-0x3. */
sspi->busy = SPI_FREE;
sspi->mode_type = MODE_TYPE_NULL;

//初始化spi_master对象
master->bus_num = pdev->id;
master->setup = sunxi_spi_setup;
master->cleanup = sunxi_spi_cleanup;
master->transfer = sunxi_spi_transfer;
master->num_chipselect = pdata->cs_num;
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH| SPI_LSB_FIRST;
...
cs_bitmap = sunxi_spi_get_cfg_csbitmap(pdev->id);

...
snprintf(sspi->dev_name, sizeof(sspi->dev_name), SUNXI_SPI_DEV_NAME"%d", pdev->id);
err = request_irq(sspi->irq, sunxi_spi_handler, IRQF_DISABLED, sspi->dev_name, sspi);
...

sspi->base_addr = ioremap(mem_res->start, resource_size(mem_res));
..
sspi->base_addr_phy = mem_res->start;

sspi->workqueue = create_singlethread_workqueue(dev_name(master->dev.parent)); //创建一个基于线程的工作队列,用于spi控制器的数据传输工作任务
...

sspi->pdev = pdev;
pdev->dev.init_name = sspi->dev_name;
ret = sunxi_spi_hw_init(sspi, pdata); //根据参数配置控制器的寄存器
...

spin_lock_init(&sspi->lock);
init_completion(&sspi->done);
INIT_WORK(&sspi->work, sunxi_spi_work); //初始化工作任务,当工作任务得到调用时,sunxi_spi_work函数就会触发调用
INIT_LIST_HEAD(&sspi->queue);

if (spi_register_master(master)) { //注册spi_master对象
...
}
...
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
spi控制器驱动好不会在/sys目录下创建spi_master对象的子目录,只可以通过”/sys/bus/platform/drivers”目录下查看平台驱动目录下有哪些匹配上的平台设备.