Linux SPI驱动框架剖析

Linux SPI驱动框架剖析

http://www.cnblogs.com/lubiao/p/4806146.html

(说明:我的开发平台是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控制器的抽象。

  spi_master

②该层的构建过程:以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 };
复制代码
  s5pv210_spi0_resource
  s5pv210_spi0_pdata

这个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文件中寻找调用线索,具体这里就不分析了。

  内核启动流程概况

按照内核初始化的先后顺序:是先调用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 };
复制代码
  id_table
  期间遇到的问题

接着往下分析:看看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中

  应用操作接口底层实现&spi_driver实例

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驱动的大概框架就分析完了。

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值