Linux SPI总线和设备驱动架构之二:SPI通用接口层

通过上一篇文章的介绍,我们知道,SPI通用接口层用于把具体SPI设备的协议驱动和SPI控制器驱动联接在一起,通用接口层除了为协议驱动和控制器驱动提供一系列的标准接口API,同时还为这些接口API定义了相应的数据结构,这些数据结构一部分是SPI设备、SPI协议驱动和SPI控制器的数据抽象,一部分是为了协助数据传输而定义的数据结构。另外,通用接口层还负责SPI系统与Linux设备模型相关的初始化工作。本章的我们就通过这些数据结构和API的讨论来对整个通用接口层进行深入的了解。
 

SPI通用接口层的代码集中在:/drivers/spi/spi.c中。

SPI设备模型的初始化


通常地,根据linux设备模型的组织方式,各种设备会挂在合适的总线上,设备驱动和设备通过总线互相进行匹配,使得设备能够找到正确的驱动程序进行控制和驱动。同时,性质相似的设备可以归为某一个类的设备,它们具有某些共同的设备属性,在设备模型上就是所谓的class。SPI设备也不例外,它们也遵循linux的设备模型的规则:
 

struct bus_type spi_bus_type = {
        .name           = "spi",
        .dev_attrs      = spi_dev_attrs,
        .match          = spi_match_device,
        .uevent         = spi_uevent,
        .pm             = &spi_pm,
};  
    
static struct class spi_master_class = {
        .name           = "spi_master",
        .owner          = THIS_MODULE,
        .dev_release    = spi_master_release,
};
 
static int __init spi_init(void)
{
        int     status;
 
        buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
        ......
        status = bus_register(&spi_bus_type);
        ......
        status = class_register(&spi_master_class);
        ......
        return 0;
        ......
}
 
 
postcore_initcall(spi_init);

可见,在初始化阶段,spi_init函数向系统注册了一个名为spi的总线类型,同时也为SPI控制器注册了一个名为spi_master的设备类。这样,以后在sysfs中就会出现以下两个文件节点:

sys/bus/spi
sys/class/spi_master

代表spi总线的spi_bus_type结构的match字段指向了spi_match_device函数,该函数用于匹配spi总线上的设备和驱动,具体的代码这里就不贴了,各位可以自行查看内核的代码树。


spi_master结构


SPI控制器负责按照设定的物理信号格式在主控和spi设备之间交换数据,SPI控制器数据是如何被传输的,而不关心数据的内容。SPI通用接口层用spi_master结构来表示一个spi控制器,我们看看它的主要字段的意义:

spi_master结构通常由控制器驱动定义,然后通过以下通用接口层的API注册到系统中:

int spi_register_master(struct spi_master *master);


spi_device结构


SPI通用接口层用spi_device结构来表示一个spi设备,它的各个字段的意义如下:

spi_board_info结构大部分字段和spi_device结构相对应,bus_num字段则用来指定所属的控制器编号,通过spi_board_info结构,我们可以有两种方式向系统增加spi设备。第一种方式是在SPI控制器驱动已经被加载后,我们使用通用接口层提供的如下API来完成:
struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip);


第二种方式是在板子的初始化代码中,定义一个spi_board_info数组,然后通过以下API注册spi_board_info:
int spi_register_board_info(struct spi_board_info const *info, unsigned n);


上面这个API会把每个spi_board_info挂在全局链表变量board_list上,并且遍历已经在系统中注册了的控制器,匹配上相应的控制器并取得它们的spi_master结构指针,最终也会通过spi_new_device函数添加SPI设备。因为spi_register_board_info可以在板子的初始化代码中调用,可能这时控制器驱动尚未加载,此刻无法取得相应的spi_master指针,不过不要担心,控制器驱动被加载时,一定会调用spi_register_master函数来注册spi_master结构,而spi_register_master函数会反过来遍历全局链表board_list上的spi_board_info,然后通过spi_new_device函数添加SPI设备。


spi_driver结构


根据linux的设备模型,有device就必定有driver与之对应,上一节介绍的spi_device结构中内嵌了device结构字段dev,同样地,代表驱动程序的spi_driver结构也内嵌了device_driver结构:

首先,transfer_list链表字段用于把该transfer挂在一个spi_message结构中,tx_buf和rx_buf提供了非dma模式下的数据缓冲区地址,len则是需要传输数据的长度,tx_dma和rx_dma则给出了dma模式下的缓冲区地址。原则来讲,spi_transfer才是传输的最小单位,之所以又引进了spi_message进行打包,我觉得原因是:有时候希望往spi设备的多个不连续的地址(或寄存器)一次性写入,如果没有spi_message进行把这样的多个spi_transfer打包,因为通常真正的数据传送工作是在另一个内核线程(工作队列)中完成的,不打包的后果就是会造成更多的进程切换,效率降低,延迟增加,尤其对于多个不连续地址的小规模数据传送而言就更为明显。


通用接口层为我们提供了一系列用于操作和维护spi_message和spi_transfer的API函数,这里也列一下。
用于初始化spi_message结构:

void spi_message_init(struct spi_message *m);


把一个spi_transfer加入到一个spi_message中(注意,只是加入,未启动传输过程),和移除一个spi_transfer:
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);
 void spi_transfer_del(struct spi_transfer *t);
以上两个API的组合,初始化一个spi_message并添加数个spi_transfer结构:


void spi_message_init_with_transfers(struct spi_message *m, struct spi_transfer *xfers, unsigned int num_xfers);
分配一个自带数个spi_transfer机构的spi_message:
struct spi_message *spi_message_alloc(unsigned ntrans, gfp_t flags);


发起一个spi_message的传送操作:
异步版本    int spi_async(struct spi_device *spi, struct spi_message *message);
同步版本    int spi_sync(struct spi_device *spi, struct spi_message *message);


利用以上这些API函数,SPI设备的协议驱动程序就可以完成与某个SPI设备的数据交换工作,同时也可以看到,因为有通用接口层的隔离,控制器驱动对于协议驱动程序来说是透明的,也就是说,协议驱动程序只关心具体需要处理和交换的数据,无需关心控制器是如何传送这些数据的。spi_master,spi_message,spi_transfer这几个数据结构的关系可以用下图来描述:

总结一下,协议驱动发送数据的流程大致是这样的:

  1. 定义一个spi_message结构;
  2. 用spi_message_init函数初始化spi_message;
  3. 定义一个或数个spi_transfer结构,初始化并为数据准备缓冲区并赋值给spi_transfer相应的字段(tx_buf,rx_buf等);
  4. 通过spi_message_init函数把这些spi_transfer挂在spi_message结构下;
  5. 如果使用同步方式,调用spi_sync(),如果使用异步方式,调用spi_async();

另外,通用接口层也为一些简单的数据传输提供了独立的API来完成上述的组合过程:

 

  1. int spi_write(struct spi_device *spi, const void *buf, size_t len);    ----    同步方式发送数据。
  2. int spi_read(struct spi_device *spi, void *buf, size_t len);    ----    同步方式接收数据。
  3. int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers);    ----   同步方式,直接传送数个spi_transfer,接收和发送。
  4. int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx);    ----    先写后读。
  5. ssize_t spi_w8r8(struct spi_device *spi, u8 cmd);    ----    写8位,然后读8位。
  6. ssize_t spi_w8r16(struct spi_device *spi, u8 cmd);    ----    写8位,然后读16位。
     
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值