本帖最后由 babyking 于 2016-6-21 11:31 编辑
第25课:SPI驱动程序开发
(一)SPI子系统驱动
一、概述
基于子系统去开发驱动程序已经是Linux内核中普遍的做法了。前面写过基于I2C 子系统的驱动开发。本文介绍另一种常用总线。SPI子系统的开发和I2C有很多相似性,大家可以对比学习。
二、SPI总线协议简介
介绍驱动开发前,需要先熟悉下SPI通讯协议中的几个关键的地方,后面再编写驱动时,需要考虑相关因素。
SPI总线由MISO(串行数据输入)、MOSI(串行数据输出)、SCK(串行移位时钟)、CS(使能信号)4个信号线组成。
SPI常用四种数据传输模式的主要差别在于:输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。如果CPHA=0,在串行同步时钟的前沿(上升或下降)数据被采样;如果CPHA=1,在串行时钟的后沿(上升或下降)数据被采样。
这四种模式中究竟选择哪种模式取决于设备。
三、Linux下SPI驱动开发
首先明确SPI驱动层次,如下图:
我们以上面这个图为思路
1.Platform bus
Platform bus对应的结构是platform_bus_type,这个内核是开始就定义好的。
2.Platform_device
SPI控制器对应platform_device的定义方式,参看arch/arm/plat-samsung/dev-spi.c文件
3.Platform_driver
再来看platform_driver,参看drivers/spi/spi_s3c.c文件
Platform_driver_probe(&s3c_spi_driver,s3c_spi_probe);注册driver
然后根据传入的platform_device参数,构建一个用于描述SPI控制器的结构体spi_master并注册。Spi_register_master(master)。后续注册的spi_device需要选定自己的spi_master,并利用spi_master提供的传输功能传输spi数据。
和I2C类似,SPI也有一个描述控制器的对象叫spi_master,其主要成员是主机控制器的序号(系统中可能存在多个SPI主机控制器)、片选数量、SPI模式和时钟设置用到的函数、数据传输用到的函数等。
4.Spi bus
SPI总线对应的总线类型为spi_bus_type,在内核的drivers/spi/spi.c中定义
对应的匹配规则是(高版本的匹配规则会稍有变化,引入了id_table,可以匹配多个spi设备名称)
5.spi_devic
下面讲spi_device的构建与注册。Spi_device对应的含义是挂载在spi总线上的一个设备,所以描述它的时候应该明确它自身的设备特性、传输要求、及挂接在哪个总线上。
spi_register_board_info(s3c_spi_devs,ARRAY_SIZE(s3c_spi_devs));//注册spi_board_info。这个代码会把spi_board_info注册到链表board_list上。
事实上上文提到的spi_master的注册会在spi_register_board_info之后,spi_master注册的过程中会调用scan_board_info扫描board_list,找到挂接在它上面的spi设备,然后创建并注册spi_device。
6.spi_driver(参考)
本文先以Linux内核中的/driver/mtd/devices/m25p80.c驱动为参考。
Spi_register_driver(&m25p80_driver); //spi driver 的注册
在有匹配的spi driver时,会调用m25p_probe
Static int_devinit m25p_probe(struct spi_device *spi)
{
........
}
根据传入的spi_device参数,可以找到对应的spi_master,接下来就可以利用spi子系统为我们完成数据交互了,可以参看m25p80_read函数,要完成传输先理解下面几个给够的含义(这两个结构的定义及详细注释参见include/linux/spi/spi.h)
Spi_message:描述一次完整的传输,即cs信号从高->低->高的传输
Spi_transfer:多个api_transfer构成一个spi_message
举例说明:m25p80的读过程如下图
可以分解为两个spi_transfer,一个是写命令,另一个是读数据具体实现参见m25p80.c中的m25p80_read函数,下面内容摘取之此函数
Spi_sync为同步方式发送,还可以用spi_async异步方式,需要设置回调完成函数。
另外也可以选择一些封装好的更容易使用的函数,这些函数可以在include/linux/spi/spi.h文件中找到,如:
extern int spi_write_then_read(struct spi_device *spi,
const u8 *txbuf,unsigned n_tx,
u8 *rxbuf,unsigned n_rx);
(二)编写SPI驱动概述(参考)
本文不具体分析Linux内核中SPI总线的架构,只针对这种架构阐述如何进行SPI设备驱动的编写。简而言之,SPI驱动的编写分为两个部分:
第一:spi_device的构建和注册
第二:spi_driver的构建和注册
1.spi_device的构建并注册
首先在板文件中添加spi_board_info,例如:
并在板文件的init函数中调用spi_register_board_info(s3c_spi_devs,ARRAY_SIZE(s3c_spi_devs));这个函数会把spi_board_info注册到链表board_list上,spi_device封装了一个spi_master结构体,事实上spi_master的注册会在spi_register_board_info之后,spi_master注册的过程中会调用scan_boardinfo扫描board_list,找到挂接在它上面的spi设备然后创建并注册spi_device。
2.Spi_driver的构建与注册分为三步:
(1)构建spi_driver
(2)Spi_driver的注册
Spi_register_driver(&m25p80_driver); 当匹配了spi_device以后调用probe
(3)实现probe操作
Spi_transfer(里面集成了数据buf空间地址等信息)
Spi_message(是spi_transfer的集合)的构建:spi_message_init(初始化spi_message)、spi_message_add_tail(将新的spi_transfer添加到spi_message队列尾部)
Spi_sync函数的调用(调用spi_master发送spi_message)
例如:
这样就基本完成了SPI设备的驱动编写,读写具体操作要根据芯片的时序来确定,具体如何利用SPI传数据那么就要看自己程序的逻辑。