linux spi驱动分析

From: http://blog.csdn.net/walkingman321/archive/2010/10/17/5946581.aspx

 

关于 spi 的学习,我觉得最好的方法还是看 Linux 的源代码,主要是 driver/spi/spi.c(h) spidev.c(h) spi dev 的示例可以看看 at25.c spi 总线的示例可以看 omap_uwire 或者 spi_s3c24xx.c spi_s3c24xx_gpio.c 。在看这些代码之前,需要对 Linux 的设备模型有一定的了解。

另外,网上有两篇教程不错,《 linux spi 子系统驱动分析》以及《 linux spi 子系统 驱动分析 续》,百度可以直接搜到,这里帖一下我找到的链接,但不清楚是转载的还是原创的。

http://linux.chinaunix.net/techdoc/net/2007/11/12/972031.shtml

http://www.sudu.cn/info/html/edu/20070101/285153.html

 

下面是我整理的关于 SPI 的一些心得,内核版本 2.6.29

 

SPI 子系统

spi 子系统中, spi 设备用struct spi_dev描述 ,它的驱动程序用struct spi_driver描述 spi 总线设备用struct spi_master描述 。另外,还有两个重要的全局变量:

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,

};

 

static struct class spi_master_class = {

       .name             = "spi_master",

       .owner           = THIS_MODULE,

       .dev_release    = spi_master_release,

};

 

spi_bus_type 对应 sys 中的 spi bus 总线, Linux 设备模型对这个结构体有详细介绍。

所有 spi_master 对应的 spi 总线都属于 spi_master_class ,也就是说是一个虚拟设备,它的父设备可能是物理设备,比如 platform_device 等等, s3c2410 就是这种情况。

SPI 设备

SPI 设备的驱动程序通过 spi_register_driver 注册进 SPI 子系统,驱动类型为 struct spi_driver 。典型例子如 at25.c

static struct spi_driver at25_driver = {

       .driver = {

              .name             = "at25",

              .owner           = THIS_MODULE,

       },

       .probe            = at25_probe,

       .remove          = __devexit_p(at25_remove),

};

因为 spi 总线不支持 SPI 设备的自动检测,所以一般在 spi probe 函数中不会检测设备是否存在,而是做一些 spi 设备的初始化工作。

spi 驱动中可以调用下列函数进行 spi 的传输操作:

static inline int spi_write(struct spi_device *spi, const u8 *buf, size_t len);

static inline int spi_read(struct spi_device *spi, u8 *buf, size_t len);

extern int spi_write_then_read(struct spi_device *spi,        const u8 *txbuf, unsigned n_tx,

              u8 *rxbuf, unsigned n_rx);

static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd);

static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd);

 

由于 spi 设备不能被 spi 总线动态扫描,所以 spi 子系统使用了另一种方法,就是通过 spi_register_board_info 函数将spi设备静态得登记到系统中。

int __init spi_register_board_info(struct spi_board_info const *info, unsigned n);

struct spi_board_info {

       char        modalias[32];                // 设备名

       const void      *platform_data;      // 私有数据,会被设置到 spi_device.dev.platform_data

       void        *controller_data;           // 私有数据,会被设置到 spi_device.controller_data

       int           irq;                              // 中断号

       u32         max_speed_hz;             // 最大速率

       u16         bus_num;                            // 用于关联 spi_master

       u16         chip_select;                  // 与片选有关

       u8           mode;                          // spi_device.mode

};

在具体平台的文件中,可以定义 struct spi_board_info 的结构体,然后通过 spi_register_board_info 函数保存这些结构体,最后在 scan_boardinfo 函数中根据这些保存的结构体创建 spi 设备( spi_new_device )。

spi_new_device 用于登记 spi 设备,这里面又分两步,首先是 spi_alloc_device ,然后是 spi_add_device

struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip)

       spi_dev* pdev = spi_alloc(master);

       proxy->chip_select = chip->chip_select;

       proxy->max_speed_hz = chip->max_speed_hz;

       proxy->mode = chip->mode;

       proxy->irq = chip->irq;

       strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));

       proxy->dev.platform_data = (void *) chip->platform_data;

       proxy->controller_data = chip->controller_data;

       proxy->controller_state = NULL;

       spi_add_device(proxy);

 

struct spi_device *spi_alloc_device(struct spi_master *master)

struct device * dev = master->dev.parent;

       struct spi_dev * spi = kzalloc(sizeof *spi, GFP_KERNEL);

       spi->master = master;

       spi->dev.parent = dev;

       spi->dev.bus = &spi_bus_type;

       spi->dev.release = spidev_release;

       device_initialize(&spi->dev);

这里 spi_dev 的父设备被指定为 master 的父设备,而 master spi 总线设备,拥有 class ,是一个虚拟设备。也就是说, spi 设备和与之对应的总线设备拥有同一个父设备,这个父设备一般来说是一个物理设备。

 

int spi_add_device(struct spi_device *spi)

       snprintf(spi->dev.bus_id, sizeof spi->dev.bus_id, "%s.%u", spi->master->dev.bus_id,

                     spi->chip_select);

       status = spi->master->setup(spi);

status = device_add(&spi->dev);

spi 总线

struct spi_master {

       struct device   dev;

 s16   bus_num;                         // 总线号,如果板子上有多个 spi 总线,靠这个域区分;另外, spi_dev 中也有 bus_num spi_dev 通过这个域找到它所属的总线。

       u16    num_chipselect;         // 片选号,如果一个 spi 总线有多个设备,

       /* setup mode and clock, etc (spi driver may call many times) */

       int                  (*setup)(struct spi_device *spi);

       int                  (*transfer)(struct spi_device *spi,       struct spi_message *mesg);

       /* called on release() to free memory provided by spi_master */

       void               (*cleanup)(struct spi_device *spi);

};

 

登记 spi 总线

struct spi_master *spi_alloc_master(struct device *dev, unsigned size);

 

int spi_register_master(struct spi_master *master);

       scan_boardinfo(master);

spi_register_master 中会调用 scan_boardinfo scan_boardinfo 中,会扫描前面保存的 boardinfo ,看新注册的 master 中的 bus_num 是否与 boardinfo bus_num 匹配,如果匹配,那就调用 spi_new_device 创建 spi 设备,并登记到 spi 子系统中。

 

setup 函数

setup 函数会做一些初始化工作。比如,根据 spi 设备的速率,设备 paster 的位传输定时器;设置 spi 传输类型;等等。

spi_add_device 函数中,会先调用 setup 函数,然后再调用 device_add 。这是因为 device_add 中会调用到 driver probe 函数,而 probe 函数中可能会对 spi 设备做 IO 操作。所以 spi 子系统就先调用 setup 为可能的 IO 操作做好准备。

但是,在代码中, setup 函数似乎也就只在这一个地方被调用。具体传输过程中切换 spi 设备时也要做配置工作,但这里的配置工作就由具体传输的实现代码决定了,可以看看 spi_bitbang.c 中的函数 bitbang_work

 

cleanup 函数

cleanup 函数会在 spidev_release 函数中被调用, spidev_release 被登记为 spi dev release 函数。

 

transfer 函数

transfer 函数用于 spi IO 传输。但是, transfer 函数一般不会执行真正的传输操作,而是把要传输的内容放到一个队列里,然后调用一种类似底半部的机制进行真正的传输。 这是因为, spi 总线一般会连多个 spi 设备,而 spi 设备间的访问可能会并发。如果直接在 transfer 函数中实现传输,那么会产生竞态, spi 设备互相间会干扰。

所以,真正的 spi 传输与具体的 spi 控制器的实现有关, spi 的框架代码中没有涉及。像 spi 设备的片选、根据具体设备进行时钟调整等等都在实现传输的代码中被调用。

 

SPI 的传输命令都是通过结构体 spi_message 定义。设备程序调用 transfer 函数将 spi_message 交给 spi 总线驱动,总线驱动再将 message 传到底半部排队,实现串行化传输。

struct spi_message {

       struct list_head       transfers;

       struct spi_device    *spi;

       unsigned         is_dma_mapped:1;

       void               (*complete)(void *context);

       void               *context;

       unsigned         actual_length;

       int                  status;

       struct list_head       queue;

       void               *state;

};

spi_message 中,有一个 transfers 队列, spi_transfer 结构体通过这个队列挂到 spi_message 中。一个 spi_message 代表一次传输会话, spi_transfer 代表一次单独的 IO 操作。比如,有些 spi 设备需要先读后写,那么这个读写过程就是一次 spi 会话,里面包括两个 transfer ,一个定义写操作的参数,另一个定义读操作的参数。

spidev.c

如果不想为自己的 SPI 设备写驱动,那么可以用 Linux 自带的 spidev.c 提供的驱动程序。要使用 spidev.c 的驱动,只要在登记设备时,把设备名设置成 spidev 就可以。 spidev.c 会在 device 目录下自动为每一个匹配的 SPI 设备创建设备节点,节点名 ”spi%d” 。之后,用户程序可以通过字符型设备的通用接口控制 SPI 设备。

需要注意的是, spidev 创建的设备在设备模型中属于虚拟设备,它的 class spidev_class 。它的父设备是在boardinfo中定义的spi设备。

你可能感兴趣的:(linux,struct,IO,Module,Class,化工)