第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传数据那么就要看自己程序的逻辑。
|