Linux SPI驱动框架剖析
(说明:我的开发平台是TQ210,处理器是cotex-A8架构的s5pv210,Linux内核版本:linux-3.10.46。)
一、SPI(Serial Peripheral Interface)总线特点
1.该总线式摩托罗拉公司于1979年推出,用于主芯片和外围设备通讯的高速(10Mbps)、支持全双工的串行接口;
2.主机与SPI外设通讯参数没有明确的标准,主机的SPI控制器得根据具体的外设来进行配置。
3.SPI总线管脚多,占用资源多,容易高速通讯,适合于字节流应用。只有一个主机。
4.SPI总线的引脚使用:
①四线模式:SS、CLK、MOSI、MISO。全双工。
②三线模式:MOSI和MISO同一根数据线。半双工。
二、Linux中SPI子系统驱动框架
SPI驱动模型:SPI核心层、SPI主机控制器层、SPI(从机)设备驱动层。SPI核心层为其他两层提供服务即函数的API。下面进行分层分析:
1.SPI核心层:
①相关文件:dervers/spi/spi.c、include/spi/spi.h
②为SPI控制器层提供的接口:
1 //分配spi_master结构 2 struct spi_master *spi_alloc_master(struct device *host, unsigned size); 3 //注册spi_master结构 4 int spi_register_master(struct spi_master *master); 5 //注销spi_master结构 6 void spi_unregister_master(struct spi_master *master);
③为SPI设备驱动层提供的接口:
1 //注册spi_driver结构到spi总线 2 int spi_register_driver(struct spi_driver *sdrv); 3 static inline void spi_unregister_driver(struct spi_driver *sdrv);//spi.h中定义 4 //分配、添加spi_device结构到spi总线上:在内核源码中这个函数又被封装在spi_match_master_to_boardinfo函数中 5 struct spi_device *spi_new_device(struct spi_master *master,struct spi_board_info *chip);
④构建spi总线:这件工作不用驱动人员来做,它只是一种软件机制的载体,内核源码中已经很完善。
1 static int __init spi_init(void) 2 { 3 ...... 4 status = bus_register(&spi_bus_type);; 5 ...... 6 status = class_register(&spi_master_class); 7 ...... 8 } 9 postcore_initcall(spi_init);
2.SPI主机控制器层的构建:该层是基于platform总线搭建起来的
①主要数据结构:
struct spi_master: interface to SPI master controller - SPI主控器接口,一个控制器驱动可以用数据结构struct spi_master来描述,即是对spi控制器的抽象。
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
②该层的构建过程:以spi0为例进行分析
阶段一:将spi master硬件相关的配置资源存放在platform_device结构中,然后将其挂到platform总线中。
1 //在arch/arm/mach-s5pv210/dev-spi.c中 2 struct platform_device s5pv210_device_spi0 = { 3 .name = "s3c64xx-spi", 4 .id = 0,//s5pv210有2个spi控制器,id分别为0和1 5 .resource = s5pv210_spi0_resource, 6 .dev = { 7 .dma_mask = &spi_dmamask, 8 .platform_data = &s5pv210_spi0_pdata, 9 }, 10 };
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
这个platform_device最终是被安放在在struct platform_device *smdkv210_devices[]中:
1 //于arch/arm/mach-s5pv210/mach-smdkv210.c中进行: 2 static struct platform_device *smdkv210_devices[] __initdata = { 3 ...... 4 /** add by clb **/ 5 &s5pv210_device_spi0, 6 &s5pv210_device_spi1, 7 };
开始跟踪,这个结构在下边这个函数中被使用:
1 smdkv210_machine_init 2 platform_add_devices(smdkv210_devices, ARRAY_SIZE(smdkv210_devices));//在内核初始化阶段就已经被注册好了 3 4 MACHINE_START(SMDKV210, "SMDKV210") 5 ...... 6 .init_machine = smdkv210_machine_init, 7 ...... 8 MACHINE_END
把宏展开后看看这个数据结构被放在哪个段,然后再从lds文件中寻找调用线索,具体这里就不分析了。
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
按照内核初始化的先后顺序:是先调用platform_device注册函数,最后边才调用初始化列表的,从这里可以推测,内核是先注册了platform_device后注册platform_driver。
阶段二:根据platform_driver的或“.id_table”的"name"在platform总线上寻找匹配的platform_device,这一阶段一定要成功,否则就没有下文可说了。
1 static struct platform_driver s3c64xx_spi_driver = { 2 .driver = { 3 //.name = "s3c64xx-spi", 4 .owner = THIS_MODULE, 5 .pm = &s3c64xx_spi_pm, 6 .of_match_table = of_match_ptr(s3c64xx_spi_dt_match), 7 }, 8 .remove = s3c64xx_spi_remove, 9 .id_table = s3c64xx_spi_driver_ids, //使用name字段进行匹配 10 .suspend = s3c64xx_spi_suspend, 11 .resume = s3c64xx_spi_resume, 12 };
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
接着往下分析:看看platform_device和platform_driver是如何进行匹配的?匹配成功之后又做了哪些事情?从drivers/spi/spi-s3c64xx.c这个文件开始分析函数的调用关系。
s3c64xx_spi_init()
platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);
drv->probe = probe;
platform_driver_register(drv);
drv->driver.bus = &platform_bus_type; //这里边的match成员引起重视
driver_register()
ret = bus_add_driver(drv);
error = driver_attach(drv);
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);//__driver_attach是回调函数
driver_match_device(drv, dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1; //如果device_driver->bus->match有定义,就调用该函数,否则,返回1。
//跟进drv->bus->match函数,就是上边“期间遇到的问题”里边说到的问题了,这里不再累述。
}
driver_probe_device(drv, dev);
really_probe(dev, drv);
{
v->driver = drv;
if (dev->bus->probe)//空
{
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
}
else if (drv->probe)
{
ret = drv->probe(dev); //执行这个就相当于调用s3c64xx_spi_probe函数,这个函数非常需要分析###
if (ret)
goto probe_failed;
}
}
函数调用跟踪到这里就回答了刚才提出的第一个问题,继续跟进s3c64xx_spi_probe函数(同样在spi-s3c64xx.c文件中),看看他做了哪些事情:
1 static LIST_HEAD(board_list);//这个是啥玩意??? 2 //spi从设备的链表: 3 spi_register_board_info 4 list_add_tail(&bi->list, &board_list);//在这个函数中被设置,体会一下在同一个文件下static变量的用意! 5 static LIST_HEAD(spi_master_list); 6 s3c64xx_spi_probe 7 /* 获取主机控制器资源 */ 8 mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0) 9 irq = platform_get_irq(pdev, 0) 10 res = platform_get_resource(pdev, IORESOURCE_DMA, 0) 11 res = platform_get_resource(pdev, IORESOURCE_DMA, 1) 12 /* 分配主机控制器结构 */ 13 master = spi_alloc_master(&pdev->dev,sizeof(struct s3c64xx_spi_driver_data)) 14 /* ...... 初始化主机控制器结构体 .......*/ 15 /* 注册主机控制器 */ 16 spi_register_master(master) //注册到spi_master_list链表中 17 spi_master_initialize_queue(master) 18 list_add_tail(&master->list, &spi_master_list)//把spi_master结构挂到spi_master_list链表中 19 list_for_each_entry(bi, &board_list, list) //遍历spi_board_info链表,查找与master匹配的spi_board_info 20 spi_match_master_to_boardinfo(master, &bi->board_info) 21 if (master->bus_num != bi->bus_num)//将主机控制器所在的SPI总线号与SPI外设的SPI总线号比较(因为有可能存在先有驱动的情况) 22 return; 23 dev = spi_new_device(master, bi); 24 proxy = spi_alloc_device(master);//master是分配的这个SPI device连接的控制器 25 struct spi_device *spi; 26 spi = kzalloc(sizeof *spi, GFP_KERNEL);//分配一个新的spi_device结构体 27 spi->master = master; //指定SPI控制器 28 spi->dev.bus = &spi_bus_type;//挂接到SPI BUS中 29 device_initialize(&spi->dev); 30 //用bi->board_info初始化这个SPI device 31 proxy->chip_select = chip->chip_select; 32 ... 33 spi_add_device(proxy);//把spi device添加到spi总线上 34 subsys_initcall(s3c64xx_spi_init);//内核初始化阶段调用
对于阶段二做一个概述:
①通过调用platform_driver_probe()函数把s3c64xx_spi_driver注册到platform总线上,struct platform_driver s3c64xx_spi_driver主要是定义了id_table。
②注册完s3c64xx_spi_driver之后,根据id_table的“.name”去platform bus中匹配相应的platform_device结构体,从中获取master的资源。
③获得对应的platform_device资源后,使用spi_register_master()把SPI主机控制器注册到spi_master_list中。
④尝试在board_list(封装了spi_board_info结构)链表中搜索匹配的项:比较master->bus_num ?= bi->bus_num。
⑤如果匹配成功,分配一个新的spi_device结构体给这个spi_board_info对应的SPI从机。
问题思考:
问1. spi_device结构和spi_board_info结构都是表述spi从机,它们有什么区别?
答1. spi_board_info中存储的是从机SPI设备的特性参数,是spi设备的静态定义(比如:SPI设备名、该设备支持的最大波特率、所在的spi总线号、片选信号、工作时序模式等等 它是一个SPI外设的实例),在创建spi_master时,会根据外设的bus_num和主机spi_master的bus_num是否相等,来判断是否为该外设创建一个spi_device结构,spi_device是要依赖于spi_board_info来完成初始化的。
问2.spi_master和spi bus有什么联系?
答2.在硬件上spi总线可以看做是从spi_master引出的线,一个master管理一条总线上的许多设备。在软件上,spi_master结构和spi总线的构建没有直接的关系,spi bus是为了实现设备和驱动分离的效果,spi_master充当“通信时的桥梁”的作用,他要根据不同的SPI外设的特性来进行配置。
问3.spi_master和spi_device之间有什么关系?
答3.假设spi设备和开发板已经连接好,就相当于一个spi_device有一个管理自己spi_master,cpu想要和spi设备通信时,spi_master就是cpu命令的最终落实者。
3.SPI(外部)设备驱动层:是用户接口层,其为用户提供了通过SPI总线访问具体设备的接口。
①主要数据结构:
struct spi_driver:主机端协议驱动 Host side "protocol" driver,描述一个外部SPI设备的驱动,即实现“解析”SPI外设和用户的通信数据的“含义”。
1 // include/linux/spi/spi.h 2 struct spi_driver { 3 const struct spi_device_id *id_table; 4 int (*probe)(struct spi_device *spi);//probe函数用于驱动和设备匹配时被调用 5 int (*remove)(struct spi_device *spi); 6 void (*shutdown)(struct spi_device *spi); 7 int (*suspend)(struct spi_device *spi, pm_message_t mesg); 8 int (*resume)(struct spi_device *spi); 9 struct device_driver driver; 10 };
②spi_driver注册:简单看一下注册的过程:在drivers/spi/spidev.c中
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
driver是为device服务的,spi_driver注册时会扫描SPI bus上的设备,进行驱动和设备的绑定。
③进一步了解spi_device结构:在前边的spi_master构建的阶段二中说到过spi_device的注册。
它是SPI管理器对SPI从机设备的代理(Master side proxy for an SPI slave device),描述一个外部SPI设备,是要和spi_driver成对的。并且spi_device封装了一个spi_master结构体。
1 struct spi_device { 2 struct device dev; 3 struct spi_master *master; //封装了一个SPI控制器:即这个从设备隶属于那个主机控制器 4 /** 从机私有特征数据 **/ 5 u32 max_speed_hz; 6 u8 chip_select; 7 ...... 8 u8 bits_per_word; 9 void *controller_state; 10 void *controller_data; //主机SPI控制器相关的数据 11 char modalias[SPI_NAME_SIZE];//从设备名字 12 int cs_gpio; //从机的片选引脚 13 };
到这里,spi驱动的大概框架就分析完了。