SPI设备分为主设备和从设备两种,用于通信和控制的四根线分别是
CS:片选信号
SCK:时钟信号
MISO:主设备的数据输入,从设备的数据输出管脚
MOSI:主设备的数据输出,从设备的数据输入管脚
SPI外设的写操作和读操作是同步完成的,如果只进行写操作,主机只需忽略接收到的字节,反之若主机要读取从机的字节,就必须发送一个空字节来引发从机的传输。
主控制器驱动被加载时,一定会调用spi_register_master(spi_bitbang_start,该函数里面调用了spi_register_master)函数来注册spi_master结构,而spi_register_master函数反过来遍历全局链表board_list上的spi_board_info,然后通过spi_new_device函数添加spi设备。
一旦控制器接受了一个了spi_message,其中的spi_transfer应该按顺序被发送,并且不能被其它spi_message打断,所以spi_message就是一次spi数据交换的原子操作。
如果spi_master设置了transfer回调函数字段,标示控制器驱动不准备使用通用接口层提供的队列化框架。
时钟信号通过时钟信号极性(CPOL)和时钟相位(CPHA)控制着两个SPI设备间何时数据交换以及何时对接收到的数据进行采样来保证两个设备之间时同步传输的。
时钟极性对传输协议没有重大影响;时钟相位能够配置用户选择两种不同传输协议进行数据传输。
在数据传输过程中,每次接收到的数据必须在下一次数据传输之前被采样。如果之前接收到的数据没有被读取,那么已经接口完成的数据将有可能被丢弃,导致SPI物理模块最终失败。
spi_master注册会在spi_register_board_info之后,调用scan_boardinfo扫描board_list,找到挂接再它上面的spi设备,然后创建并注册spi_device。
在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否收到数据。
进入xxx_spi_probe()函数,首先分配spi_master内存空间,spi主控制器被抽象为spi_master
spi_bitbang是对spi主控制器的硬件操作函数和参数的封装。
hw->bitbang.master = hw->master;
hw->bitbang.setup_transfer = silan_spi_setupxfer;
hw->bitbang.chipselect = silan_spi_chipsel;
hw->bitbang.txrx_bufs = silan_spi_txrx;
hw->bitbang.master->setup = silan_spi_setup;
在probe中要注册上面这些回调函数。
*****而最后核心的实际操作的函数还是silan_spi_txrx函数。这个函数里面主要就是对spi模块寄存器的操作。
之后就是获得一些platform的资源(时钟,中断,内存空间等),再对spi主控制器进行复位(控制,地址,数据和命令进行写零操作)。
之后调用spi_bitbang_start().
if (!bitbang->master->mode_bits)
bitbang->master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;
if (!bitbang->master->transfer)
bitbang->master->transfer = spi_bitbang_transfer;
if (!bitbang->txrx_bufs) {
bitbang->use_dma = 0;
bitbang->txrx_bufs = spi_bitbang_bufs;
if (!bitbang->master->setup) {
if (!bitbang->setup_transfer)
bitbang->setup_transfer =
spi_bitbang_setup_transfer;
bitbang->master->setup = spi_bitbang_setup;
bitbang->master->cleanup = spi_bitbang_cleanup;
}
} else if (!bitbang->master->setup)
return -EINVAL;
if (bitbang->master->transfer == spi_bitbang_transfer &&
!bitbang->setup_transfer)
return -EINVAL;
由这个函数可以发现bitbang->master->setup之前必须被注册,但是bitbang->master->transfer之前可以不被注册,如果没有被注册,则赋上一个默认的主控制器数据传输函数spi_bitbang_transfer().
最后调用spi_register_master()注册spi主控制器,调用device_add()创建设备节点。之后遍历spi_master_list,查找是否有匹配的从设备(spi_match_master_to_boardinfo),如果有的话就创建新的spi从设备(spi_new_device),该函数主要是分配spi_device的内存空间,并且初始化其内容,然后调用spi_add_device().首先检查片选端的从设备是否已经在使用中,如果没有的话,就调用spi_setup(),启动spi,该函数里面主要是运用一开始注册的回调函数msater->setup().之后调用device_add()创建设备节点。
在arch/xxx_cpu/xxx_sys/spi_boardinfo.c中有事先写好的spi_board_info结构体类型的spi从设备,会在系统启动的时候调用spi_register_board_info()将设备添加到spi_master_list列表中去。
spi是一种总线驱动,所以需要挂在总线上的设备驱动再去调用之前注册好的回调函数,比如LCD驱动如果需要使用SPI总线,那么步骤就是
1)spi_message_init
2)把要传输的数据填充到spi_message结构体中去
3)spi_message_add_tail 函数将链表添加到spi_message的transfer尾巴上
4)spi_sync 该函数就是最后执行spi驱动的核心函数
跟踪到最后发现是调用了master->transfer(spi, message);
这个函数就是spi驱动注册的时候注册的回调函数。个人感觉linux各个子系统还都是值得学习的,这么简单的一个子系统,用了工作队列,锁,还有链表操作等等。
在看相关代码的时候,也学到了工作队列的知识,也算是一种收获吧。在spi_bitbang_start().中声明
INIT_WORK(&bitbang->work, bitbang_work);//初始化工作队列
bitbang->workqueue = create_singlethread_workqueue(dev_name(bitbang->master->dev.parent));//每个工作队列有一个或多个专用的内核线程,这些进程运行提交到该队列的函数。
在spi_bitbang_transfer中
queue_work(bitbang->workqueue, &bitbang->work);//将work添加到指定的queue中。
而在工作对列中的调用的函数bitbang_work中又调用bitbang->setup_transfer();这个回调函数进行数据的传输。
最后有一个疑惑在spi_register_master()函数最后of_register_spi_devices(master);是干什么的????