linux驱动之spi学习小结

 Linux SPI驱动总体架构
      在2.6的linux内核中,SPI的驱动架构可以分为如下三个层次:SPI 核心层、SPI控制器驱动层和SPI设备驱动层。
      Linux 中SPI驱动代码位于drivers/spi目录。

        

Linux SPI体系结构图

        本文对驱动框架不做详细分析(可参见孟浩依然的博文《linux内核SPI总线驱动分析》),只阐述SPI驱动各环节衔接过程,因为我发现在缺乏必要的驱动理论基础的前提下,对模块分而治之就像瞎子摸象,缺少整体感和流畅性。


Linux中SPI子系统的初始化是从drivers/spi/spi.c文件中的spi_init函数开始的,看看它的定义:

static int __init spi_init(void)
{
    int    status;
    buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
    if (!buf) {
        status = -ENOMEM;
        goto err0;
    }
    status = bus_register(&spi_bus_type);
    if (status < 0)
        goto err1;
    status = class_register(&spi_master_class);
    if (status < 0)
        goto err2;
    return 0;
err2:
    bus_unregister(&spi_bus_type);
err1:
    kfree(buf);
    buf = NULL;
err0:
    return status;
}

初始化过程主要为总线分配内存资源、完成总线注册,这里涉及到SPI总线结构体,定义如下:

struct bus_type spi_bus_type = {
    .name        = "spi",
    .dev_attrs    = spi_dev_attrs,
    .match        = spi_match_device,
    .uevent        = spi_uevent,
    .suspend        = spi_suspend,
    .resume        = spi_resume,
};
其中包含了最重要的match函数,该函数作用是匹配总线设备与驱动,在spi_driver注册时,会被用于扫描spi总线上的设备,详见match定义

补充知识:

Device Naming and Driver Binding
platform_device.dev.bus_id是一个设备在总线上的名字,它包含两部分:
  * platform_device.name   设备名字,用来进行driver的匹配
  * platform_device.id     设备实例的标号,如果是-1,表示同样名字的设备只有一个
举个简单的例子,name/id是“serial/1”则它的bus_id就是serial.1  如果name/id是“serial/0”则它的bus_id就是serial.0 ,如果它的name/id是“serial/-1”则它的bus_id就是serial。
driver的绑定是通过driver core自动完成的,完成driver和device的匹配后以后会自动执行probe()函数,如果函数执行成功,则driver和device就绑定在一起了,drvier和device匹配的方法有3种:
  * 当一个设备注册的时候,会在总线上寻找匹配的driver,platform device一般在系统启动很早的时候就注册了。
  * 当一个驱动注册[platform_driver_register()]的时候,会遍历所有总线上的设备来寻找匹配,在启动的过程驱动的注册一般比较晚,或者在模块载入的时候
  * 当一个驱动注册[platform_driver_probe()]的时候, 功能上和使用platform_driver_register()是一样的,唯一的区别是它不能被以后其他的device probe了,也就是说这个driver只能和一个device绑定。

spi_device 和spi_master

platform总线有platform_device和platform_driver,对于SPI总线,也有对应的spi_device和spi_driver。

struct spi_device {
    struct device        dev;
    struct spi_master    *master;
    u32            max_speed_hz;
    u8            chip_select;
    u8            mode;
#define    SPI_CPHA    0x01            /* clock phase */
#define    SPI_CPOL    0x02            /* clock polarity */
#define    SPI_MODE_0    (0|0)            /* (original MicroWire) */
#define    SPI_MODE_1    (0|SPI_CPHA)
#define    SPI_MODE_2    (SPI_CPOL|0)
#define    SPI_MODE_3    (SPI_CPOL|SPI_CPHA)
#define    SPI_CS_HIGH    0x04            /* chipselect active high? */
#define    SPI_LSB_FIRST    0x08            /* per-word bits-on-wire */
#define    SPI_3WIRE    0x10            /* SI/SO signals shared */
#define    SPI_LOOP    0x20            /* loopback mode */
#define    SPI_NO_CS    0x40            /* 1 dev/bus, no chipselect */
#define    SPI_READY    0x80            /* slave pulls low to pause */
    u8            bits_per_word;
    int            irq;
    void            *controller_state;
    void            *controller_data;
    char            modalias[SPI_NAME_SIZE];

    /*
     * likely need more hooks for more protocol options affecting how
     * the controller talks to each chip, like:
     *  - memory packing (12 bit samples into low bits, others zeroed)
     *  - priority
     *  - drop chipselect after each word
     *  - chipselect delays
     *  - ...
     */
};

spi_device通常在BSP的板文件中定义,它的板信息用spi_board_info结构体描述,该结构体记录SPI外设使用的主机控制器序号、片选序号、数据比特率、SPI传输模式(即使)等。spi_board_info结构体声明如下:
struct spi_board_info {
    char  modalias[SPI_NAME_SIZE];
    const void *platform_data;
    void  *controller_data;
    int   irq;
    u32   max_speed_hz;
    u16   bus_num;
    u16   chip_select;
    u8    mode;
};

在系统开机时,在机器初始化函数init_machine()中将通过spi_register_board_info()将spi_board_info注册到链表board_list上,这一点与启动时通过platform_add_devices()函数统一注册platform_device(归纳为一个数组)过程非常相似但是必须要强调的是,此时并未创建spi_device或者spi_master。

SPI控制器驱动
SPI控制器驱动层,每种处理器平台都有自己的控制器驱动,属于平台移植相关层。它的职责是为系统中每条SPI总线实现相应的读写方法, 一个控制器驱动用于支持一条特定的SPI总线的读写。 在物理上,每个SPI控制器可以连接若干个SPI从设备。 一般在系统开机时,SPI控制器驱动被首先装载。 在spi_device封装了一个spi_master结构体, 事实上spi_master的注册会在spi_register_board_info之后。
struct spi_master {  
    struct device   dev;  
    s16         bus_num;  
    u16         num_chipselect;  
    int         (*setup)(struct spi_device *spi);  
    int         (*transfer)(struct spi_device *spi, struct spi_message *mesg);  
    void        (*cleanup)(struct spi_device *spi);  
};  

另外,一般来说设备是不能被热插拔的,所以可以将probe()函数放在spi控制器驱动init段里面来节省driver运行时候的内存开销:

static int __init s3c64xx_spi_init(void)
{
     return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);
}
在s3c6410中,spi控制器驱动在设备与驱动匹配成功后才创建并注册的,详见s3c64xx_spi_probe函数定义。


spi_driver在驱动模块加载时进行初始化并注册,注册时会扫描总线上的设备,通过总线match函数进行设备与驱动匹配,若成功,则调用spi_driver的probe函数,通过platform_get_resource等函数获取spi_board_info中定义的资源信息,创建并注册spi_master,spi_master注册过程会调用scan_boardinfo扫描board_list,通过总线号(bus_num)找到挂接在它上面的spi设备,创建并注册一个spi_device。


SPI外设驱动

spi外设驱动遍布于内核的drivers、sounds各个子目录之下,spi只是一种总线,spi_driver的作用只是将spi外设挂接在该总线上,因此在spi_driver的probe函数中,将注册本身所属设备驱动的类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值