分类:
2011-10-16 08:10:32
原文地址:linux spi子系统 作者:chumojing
重要的数据结构:
~~~~~~~~~~
spi控制器的主题是spi_master,虽然一般不需要自己编写spi控制器驱动,了解这个结构体还是必要的。
spi控制器的驱动一般在arch/.../mach-*/board-*.c 声明,注册一个平台设备,然后在driver/spi下面建立一个平台驱动。
spi_master注册过程中会扫描arch/.../mach-*/board-*.c 中调用spi_register_board_info注册的信息,为每一个与本总线编号相同的信息建立一个spi_device。
根据Linux内核的驱动模型,注册在同一总线下的驱动和设备会进行匹配。spi_bus_type总线匹配的依据是名字。这样当自己编写的spi_driver和spi_device同名的时候,
spi_driver的probe方法就会被调用。spi_driver就能看到与自己匹配的spi_device了。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
这个结构体是一个辅助性的数据结构。主要是提供驱动模型下的绑定方法和电源管理接口。其成员driver.name是和spi_device匹配的依据。
这个结构的用处是绑定arch/.../mach-*/board-*.c 中调用spi_register_board_info注册的信息对应的spi_device。
内核中的一个例子如下。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如何编写一个spi驱动?(设备驱动,这里不涉及spi控制器的驱动)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
静态声明
======
声明从设备
-----------------
arch/.../mach-*/board-*.c中一般包含如下的声明
使用以下函数注册上面声明的信息(注册进spi子系统):
spi_register_board_info(spi_board_info, ARRAY_SIZE(spi_board_info));
这种静态的注册,不需要注销。
编写驱动
-------------
SPI通信驱动和平台设备驱动非常类似
驱动核心会将board_info的modalias成员为“CHIP”的设备和本驱动进行绑定。
probe的代码应该像下面这样
probe做私有数据的初始化。之后就可以使用probe得到的spi_device和设备进行通信了。通信的过程根本不需要spi_driver。
spi_driver这个结构体仅仅是为了让驱动得到相应spi_device的指针和进行电源管理;
再在spi_driver之上使用input子系统或者混杂设备已经和spi本身无关了。
spi_device是驱动的下界,它的上界包含了sysfs、输入子系统、ALSA、网络层、MTD、字符设备框架或者其他Linux子系统。
非静态声明
======
静态声明是在内核编译时便已经能确定spi设备了。但是有时无法在编译内核时确定,加入设备时谁都不想修改内核然后再重新编译。
上文我们说到,spi通信过程根本不需要spi_driver,因此仅仅建立我们的spi_device即可。
spi_busnum_to_master()可以根据总线号返回spi_master指针。原型如下:
struct spi_master *spi_busnum_to_master(u16 bus_num);
首先根据要使用的控制器编号(这个编号对应硬件的哪个控制器跟控制器驱动有关)获得spi_master指针,然后像静态声明时一样定义spi_board_info,
最后调用struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip)申请并注册设备。返回非空指针意味着可以使用
返回的spi_device指针进行通信了。注意:从代码中看到,spi_new_device并不能初始化spi_device的bits_per_word成员,因此在得到spi_device之后还因该定义这个字长。
设备移走之后注销设备使用spi_unregister_device()。
如何通信
~~~~
使用封装接口
==================
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);//可能睡眠。
先写n_tx个字节再读n_rx个字节。成功返回零,失败返回负的值。读和写的字节数不应该大于32。
static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd)//上个函数的封装。写8bit然后再读8bit。
static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd)//写写8bit然后再读16bit。
原始接口
================
static inline int
spi_async(struct spi_device *spi, struct spi_message *message);//异步读写。可以在不能睡眠的上下文调用。
extern int spi_sync(struct spi_device *spi, struct spi_message *message);//同步读写。会睡眠,不能在中断上下文中使用。所有同步的spi传输接口都是对它的封装。
下面研究spi_message 。
spi_message
~~~~~~~
spi_message用来原子的执行spi_transfer表示的一串数组传输请求。
这个传输队列是原子的,这意味着在这个消息完成之前不会有其它消息占用总线。
消息的执行总是按照FIFO的顺序。
向底层提交spi_message的代码要负责管理它的内存空间。未显示初始化的内存需要使用0来初始化。
spi_transfer
----------------
上面看到,一个spi_message是由多个spi_transfer组成的。
每个spi_transfer总是读取和写入同样长度的比特数,但是可以很容易的使用空指针舍弃读或写。
为spi_transfer和spi_message分配的内存应该在消息处理期间保证是完整的。
控制器驱动会先写入tx的数据,然后读取同样长度的数据。长度指示是len。
如果tx_buff是空指针,填充rx_buff的时候会输出0(为了产生接收的时钟),如果rx_buff是NULL,接收到的数据将被丢弃。
只有len长读的数据会被输出和接收。
输出不完整的字长是错误的(比如字长为2字节的时候输出三个字节,最后一个字节凑不成一个整字)。
本地内存中的数据总是使用本地cpu的字节序,无论spi的字节序是大段模式还是小段模式(使用SPI_LSB_FIRS)
当spi_transfer的字长不是8bit的2次幂的整数倍,这些数据字就包含扩展位。在spi通信驱动看来内存中的数据总是刚好
对齐的,所以rx中位定义和rx中未使用的比特位总是最高有效位。(比如13bit的字长,每个字占2字节,rx和tx都应该如此存放)
所有的spi传输都以使能相关的片选线为开始。一般来说片选线在本消息结束之前保持有效的状态。驱动可以使用
spi_transfer中的cs_change成员来影响片选:
(i)如果transfer不是message的最后一个,这个标志量可以方便的将片选线置位无效的状态。
有时需要这种方法来告知芯片一个命令的结束并使芯片完成这一批处理任务。
(ii)当这个trasfer是最后一个时,片选可以一直保持有效知道下一个transfer到来。
在多spi从机的总线上没有办法阻止其他设备接收数据,这种方法可以作为一个特别的提示;开始往另一个设备传输信息就要先将
本芯片的片选置为无效。但在其他情况下,这可以保证正确性。一些设备后面的信息依赖于前面的信息并且在一个处理序列完成后需要
禁用片选线。
上面这段是翻译的,讲的不明白。
再说一下:cs_change影响此transfer完成后是否禁用片选线并调用setup改变配置。(这个标志量就是chip select change片选改变的意思)
没有特殊情况,一个spi_message因该只在最后一个transfer置位该标志量。
spi_message的函数接口
-------------------------------
static inline void spi_message_init(struct spi_message *m);//初始化,实际是用0填充并初始化链表节点。
static inline void
spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);//将transfer加到message的链表尾部。
static inline void
spi_transfer_del(struct spi_transfer *t);//将transfer从message链表中移除
static
inline struct spi_message *spi_message_alloc(unsigned ntrans, gfp_t
flags);//创建一个message并创建 ntrans个transfer加入链表,flag是申请内存时的标志。
static inline void spi_message_free(struct spi_message *m);//释放message。
bits_per_word
-----------------------
在spi_device和spi_transfer中均有bits_per_word的定义。
spi控制器驱动中会首先使用spi_transfer中的定义,如果它的值为0则使用默认值,也就是spi_device的bits_per_word定义,如果它也为零,则使用默认值8(bit).