Linux SPI子系统驱动程序结构分析
- 格式:pdf
- 大小:190.91 KB
- 文档页数:9
一、概况SPI接口是摩托罗拉首先提出的全双工三线同步串行外围接口SCK,MOSI,MISO,采用主从模式(Master Slave)架构;支持多slave模式应用(此时使用四线模式进行通信,在原有三线模式下多出一根片选信号CS),一般一个Master最多支持4个Slave,此时Master控制器需要有4根CS片选信号,因为主从通信的属于独占式,在某一个Slave设备通信时候,其他设备只能等待。
SPI总线时钟由Master控制,在同步时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB FIRST),四线工作模式的SPI接口有2根单向数据线MOSI和MISO,SCK时钟线,Slave设备的片选信号CS。
看下图多Slave模式的主从连接图从上很明显看出各个Slave设备共享数据线和时钟线,各自的片选信号线不一样,三线模式更为简单,简单的说去掉片选信号线即可。
下面是SPI总线的优缺点优点:1) 支持全双工操作;2) 操作简单;3) 数据传输速率较高。
缺点:1) 需要占用主机较多的引脚线(每个从机都需要一根片选线),占用总线带宽;2) 没有指定的流控制,没有应答机制确认是否接收到数据。
3) SPI总线并不是一个标准的协议,为了适应较多产品存在较多的模式,同时各家芯片的读写方法并不是完全一致二SPI总线模式设置SPI协议中最重要的便是模式设置包括CPOL和CPHA。
CPOL指的是SCK时钟空闲时的电平极性Polarity。
CPHA指的是相位Phase.CPOL=0表示SCK时钟在空闲时候是低电平CPOL=1表示SCK时钟在空闲时候是高电平CPHA=0 表示片选信号有效后,SCK的第一个跳变沿采样(接收)数据(包括下降沿/上升沿取决于CPOL),第二个跳变沿发送数据CPHA=1表示片选信号有效后,SCK的第一个跳变沿发送数据,第二个跳变沿采样(接收)数据注意采样数据一般都是在数据线信号最稳定的时候采样-信号中间根据CPOL和CPHA的变化SPI设备存在四种工作模式Linux使用以下四个宏表示SPI_MODE_0,SPI_MODE_1,SPI_MODE_2,SPI_MODE_3为了保证主从设备的正常通信,主从设备需要保持一致的工作模式,对于一个给定的从设备是可以从其datasheet中间找到相应的工作模式,主设备按照datasheet进行设置即可。
一概念用户空间驱动就是指在用户空间实现的驱动程序。
可以认为它跟普通的用户程序没有什么两样,它使用用户进程空间和栈,下面来看看用户态驱动的优点和缺点优点:1 可以和整个C库链接2 驱动的问题不会导致整个系统挂起3 容易调试缺点:1 中断和DMA在用户态是无法使用的(现在在用户态其实也是可以使用中断和DMA但是比较麻烦,比如可以使用poll函数来等待基于GPIO的中断,同样也可以使用/dev/mem来使用DMA)2 响应时间慢, 因为需要上下文切换在客户和硬件之间传递信息或动作所以总的来说对于一个较为慢速的不需要实时交互的设备,可以使用用户态的驱动来实现。
二spi用户态驱动spi用户驱动主要是依赖于内核模块spidev,该模块实现了一个通用的spi驱动,在用户空间调用该通用驱动提供的接口,操作相应的spi设备,其实这就是spi用户态驱动。
下面是配置内核模块spidev图1首先进入Device Drive==>,然后进入如上图1所示spi support图2如上图2选择用户态spi设备驱动,这样内核模块spidev就配置成功一添加spi_board_info首先需要在内核的arch/arm/mach-*/board-*.c 即BSP信息中添加一个spi_board_info(无论是用户态spi驱动还是内核态spi驱动都需要这样),同时名字必须是“spidev”,那么为什么要这么做了,这就是从 2.6内核起linux 驱动使用了一个叫做Device Model即设备模型的东东,总线,驱动和设备,总线将设备和驱动进行匹配,为了要能够使spi驱动的probe函数能够被调用,则必须存在一个设备与该驱动进行匹配,匹配成功后,spi子系统才会自动调用该驱动的probe函数,对于没有使用热插拔机制总线的设备比如platform,,spi,i2c,则必须手动在BSP信息中间手动添加设备信息,spi总线就是通过添加spi_board_info,来触发内核创建spi设备(struct spi_device),i2c则是添加i2c_board_info。
Linux内核中SPI总线驱动分析本文主要有两个大的模块:一个是SPI总线驱动的分析(研究了具体实现的过程);另一个是SPI总线驱动的编写(不用研究具体的实现过程)。
1 SPI概述SPI是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口,是Motorola首先在其MC68HCXX系列处理器上定义的。
SPI接口主要应用在EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。
SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便。
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要4根线,事实上3根也可以。
也是所有基于SPI的设备共有的,它们是SDI(数据输入),SDO(数据输出),SCLK(时钟),CS(片选)。
MOSI(SDO):主器件数据输出,从器件数据输入。
MISO(SDI):主器件数据输入,从器件数据输出。
SCLK :时钟信号,由主器件产生。
CS:从器件使能信号,由主器件控制。
其中CS是控制芯片是否被选中的,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),对此芯片的操作才有效,这就允许在同一总线上连接多个SPI设备成为可能。
需要注意的是,在具体的应用中,当一条SPI总线上连接有多个设备时,SPI本身的CS 有可能被其他的GPIO脚代替,即每个设备的CS脚被连接到处理器端不同的GPIO,通过操作不同的GPIO口来控制具体的需要操作的SPI设备,减少各个SPI设备间的干扰。
SPI是串行通讯协议,也就是说数据是一位一位从MSB或者LSB开始传输的,这就是SCK 时钟线存在的原因,由SCK提供时钟脉冲,MISO、MOSI则基于此脉冲完成数据传输。
SPI 支持4-32bits的串行数据传输,支持MSB和LSB,每次数据传输时当从设备的大小端发生变化时需要重新设置SPI Master的大小端。
Linux下的SPI总线驱动(一)2013-04-12 15:08:46分类:LINUX版权所有,转载请说明转自一.SPI理论介绍SPI总线全名,串行外围设备接口,是一种串行的主从接口,集成于很多微控制器内部。
和I2C使用2根线相比,SPI总线使用4根线:MOSI (SPI 总线主机输出/ 从机输入)、MISO (SPI总线主机输入/从机输出)、SCLK(时钟信号,由主设备产生)、CS(从设备使能信号,由主设备控制)。
由于SPI总线有专用的数据线用于数据的发送和接收,因此可以工作于全双工,当前市面上可以找到的SPI外围设备包括RF芯片、智能卡接口、E2PROM、RTC、触摸屏传感器、ADC。
SCLK信号线只由主设备控制,从设备不能控制信号线。
同样,在一个基于SPI的设备中,至少有一个主控设备。
这样传输的特点:这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCLK 时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。
也就是说,主设备通过对SCLK时钟线的控制可以完成对通讯的控制。
SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。
不同的SPI 设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档。
在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。
在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比I2C 系统要稍微复杂一些。
二.SPI驱动移植我们下面将的驱动的移植是针对Mini2440的SPI驱动的移植Step1:在Linux Source Code中修改arch/arm/mach-s3c2440/文件,加入头文件:#include <linux/spi/>#include <../mach-s3c2410/include/mach/>然后加入如下代码:static struct spi_board_info s3c2410_spi0_board[] ={[0] = {.modalias = "spidev", us_num = 0, hip_select = 0, rq = IRQ_EINT9, ax_speed_hz = 500 * 1000,in_cs = S3C2410_GPG(2),.num_cs = 1, us_num = 0, pio_setup = s3c24xx_spi_gpiocfg_bus0_gpe11_12_13, odalias = "spidev",.bus_num = 1,.chip_select = 0,.irq = IRQ_EINT2,.max_speed_hz = 500 * 1000,}};static struct s3c2410_spi_info s3c2410_spi1_platdata = {.pin_cs = S3C2410_GPG(3),.num_cs = 1,.bus_num = 1,.gpio_setup = s3c24xx_spi_gpiocfg_bus1_gpg5_6_7,};Step2:在mini2440_devices[]平台数组中添加如下代码:&s3c_device_spi0,&s3c_device_spi1,Step3:最后在mini2440_machine_init函数中加入如下代码:&s3c2410_spi0_platdata;spi_register_board_info(s3c2410_spi0_board, ARRAY_SIZE(s3c2410_spi0_board)); &s3c2410_spi1_platdata;spi_register_board_info(s3c2410_spi1_board, ARRAY_SIZE(s3c2410_spi1_board)); Step4:最后需要修改arch/arm/plat-s3c24xx/KConfig文件找到config S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13boolhelpSPI GPIO configuration code for BUS0 when connected toGPE11, GPE12 and GPE13.config S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7boolhelpSPI GPIO configuration code for BUS 1 when connected toGPG5, GPG6 and GPG7.修改为config S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13bool "S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13"helpSPI GPIO configuration code for BUS0 when connected toGPE11, GPE12 and GPE13.config S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7bool "S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7"helpSPI GPIO configuration code for BUS 1 when connected toGPG5, GPG6 and GPG7.Step5:最后make menuconfig配置,选中System Type和SPI support相应文件Step6:执行make生成zInage,将编译好的内核导入开发板,并且编译测试程序运行即可。
SPI驱动编写简而言之,SPI驱动的编写分为:1.spi_device就构建并注册在板文件中添加spi_board_info,并在板文件的init函数中调用spi_register_board_info(s3c_spi_devs,ARRAY_SIZE(s3c_spi_devs));spi_register_board_info(s3c_spi_devs,ARRAY_SIZE(s3c_spi_devs));//注册spi_board_info。
这个代码会把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)static struct spi_driver m25p80_driver = {.driver = {.name ="m25p80",.bus =&spi_bus_type,.owner = THIS_MODULE,},.probe = m25p_probe,.remove =__devexit_p(m25p_remove),};(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)例如:struct spi_transfer st={。
Linux下spi驱动开发(1)华清远见刘洪涛一、概述基于子系统去开发驱动程序已经是linux内核中普遍的做法了。
前面写过基于I2C子系统的驱动开发。
本文介绍另外一种常用总线SPI的开发方法。
SPI子系统的开发和I2C有很多的相似性,大家可以对比学习。
本主题分为两个部分叙述,第一部分介绍基于SPI子系统开发的理论框架;第二部分以华清远见教学平台FS_S5PC100上的M25P10芯片为例(内核版本2.6.29),编写一个SPI驱动程序实例。
二、SPI总线协议简介介绍驱动开发前,需要先熟悉下SPI通讯协议中的几个关键的地方,后面在编写驱动时,需要考虑相关因素。
SPI总线由MISO(串行数据输入)、MOSI(串行数据输出)、SCK(串行移位时钟)、CS(使能信号)4个信号线组成。
如FS_S5PC100上的M25P10芯片接线为:上图中M25P10的D脚为它的数据输入脚,Q为数据输出脚,C为时钟脚。
SPI常用四种数据传输模式,主要差别在于:输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置。
如果CPOL= 0,串行同步时钟的空闲状态为低电平;如果CPOL= 1,串行同步时钟的空闲状态为高电平。
如果CPHA= 0,在串行同步时钟的前沿(上升或下降)数据被采样;如果CPHA = 1,在串行同步时钟的后沿(上升或下降)数据被采样。
这四种模式中究竟选择哪种模式取决于设备。
如M25P10的手册中明确它可以支持的两种模式为:CPOL=0 CPHA=0 和CPOL=1 CPHA=1三、linux下SPI驱动开发首先明确SPI驱动层次,如下图:SPI总线可理解为SPI控制器引出的总线1、Platform busP latform bus对应的结构是platform_bus_type,这个内核开始就定义好的。
我们不需要定义。
2、Platform_deviceSPI控制器对应platform_device的定义方式,同样以S5PC100中的SPI控制器为例,参看arch/arm/plat-s5pc1xx/dev-spi.c文件struct platform_device s3c_device_spi0 = {.name = "s3c64xx-spi", //名称,要和Platform_driver匹配.id = 0, //第0个控制器,S5PC100中有3个控制器.num_resources = ARRAY_SIZE(s5pc1xx_spi0_resource),//占用资源的种类.resource = s5pc1xx_spi0_resource,//指向资源结构数组的指针.dev = {.dma_mask = &spi_dmamask, //dma寻址范围.coherent_dma_mask = DMA_BIT_MASK(32), //可以通过关闭cache等措施保证一致性的dma寻址范围.platform_data = &s5pc1xx_spi0_pdata,//特殊的平台数据,参看后文},};static struct s3c64xx_spi_cntrlr_info s5pc1xx_spi0_pdata = {.cfg_gpio = s5pc1xx_spi_cfg_gpio, //用于控制器管脚的IO配置.fifo_lvl_mask = 0x7f,.rx_lvl_offset = 13,};static int s5pc1xx_spi_cfg_gpio(struct platform_device *pdev){s witch (pdev->id) {c ase 0:s3c_gpio_cfgpin(S5PC1XX_GPB(0), S5PC1XX_GPB0_SPI_MISO0);s3c_gpio_cfgpin(S5PC1XX_GPB(1), S5PC1XX_GPB1_SPI_CLK0);s3c_gpio_cfgpin(S5PC1XX_GPB(2), S5PC1XX_GPB2_SPI_MOSI0);s3c_gpio_setpull(S5PC1XX_GPB(0), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPB(1), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPB(2), S3C_GPIO_PULL_UP);break;c ase 1:s3c_gpio_cfgpin(S5PC1XX_GPB(4), S5PC1XX_GPB4_SPI_MISO1);s3c_gpio_cfgpin(S5PC1XX_GPB(5), S5PC1XX_GPB5_SPI_CLK1);s3c_gpio_cfgpin(S5PC1XX_GPB(6), S5PC1XX_GPB6_SPI_MOSI1);s3c_gpio_setpull(S5PC1XX_GPB(4), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPB(5), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPB(6), S3C_GPIO_PULL_UP);break;c ase 2:s3c_gpio_cfgpin(S5PC1XX_GPG3(0), S5PC1XX_GPG3_0_SPI_CLK2);s3c_gpio_cfgpin(S5PC1XX_GPG3(2), S5PC1XX_GPG3_2_SPI_MISO2);s3c_gpio_cfgpin(S5PC1XX_GPG3(3), S5PC1XX_GPG3_3_SPI_MOSI2);s3c_gpio_setpull(S5PC1XX_GPG3(0), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPG3(2), S3C_GPIO_PULL_UP);s3c_gpio_setpull(S5PC1XX_GPG3(3), S3C_GPIO_PULL_UP);break;d efault:dev_err(&pdev->dev, "Invalid SPI Controller number!");return -EINVAL;}3、Platform_driver再看platform_driver,参看drivers/spi/spi_s3c64xx.c文件static struct platform_driver s3c64xx_spi_driver = {.driver = {.name = "s3c64xx-spi", //名称,和platform_device对应.owner = THIS_MODULE,},.remove = s3c64xx_spi_remove,.suspend = s3c64xx_spi_suspend,.resume = s3c64xx_spi_resume,};platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);//注册s3c64xx_spi_driver和平台中注册的platform_device匹配后,调用s3c64xx_spi_probe。
Linux驱动:SPI驱动编写要点题外话:⾯对成功和失败,⼀个⼈有没有“冠军之⼼”,直接影响他的表现。
⼏周前剖析了Linux SPI 驱动框架,算是明⽩个所以然,对于这么⼀个庞⼤的框架,并不是每⼀⾏代码都要⾃⼰去敲,因为前⼈已经把这个框架搭建好了,作为驱动开发者的我们只需要搞清楚哪⼀部分是需要⾃⼰修改或重新编写就OK了。
结合Linux内核⾯向对象的设计思想,SPI总的设计思路⼤概是这样的:第①处:内核中抽象了SPI控制器,让spi_master成为他的象征,他的实例化对象就是与硬⽣⽣的SPI控制器对应的,在Linux内核中习惯将集成到SOC上的控制器⽤假想的platform总线来进⾏管理,于是乎spi_master的实例化就得依靠platform_device和platform_driver来联⼿完成了。
细嚼慢咽:这⾥的platform_device就相当于是spi_master的静态描述:包括⼏号控制器、寄存器地址、引脚配置等等,把这些信息以“资源”的形式挂在platform_device上,等platform_driver找到命中注定的那个他之后就可以获得这个(静态描述)资源,probe中⽤这些资源就⽣出了spi_master实例对象。
这⼀系列流程前辈们都已经做好了,我们要做的就是将这静态描述platform_device修改成和⾃⼰SOC中的spi 控制器⼀致的特性即可。
即:适当修改arch/arm/mach-s5pv210/dev-spi.c中platform_device涉及的成员。
1// SPI0的寄存器地址2static struct resource s5pv210_spi0_resource[] = {3 [0] = {4 .start = S5PV210_PA_SPI0,5 .end = S5PV210_PA_SPI0 + 0x100 - 1,6 .flags = IORESOURCE_MEM,7 },8 [1] = {9 .start = DMACH_SPI0_TX,10 .end = DMACH_SPI0_TX,11 .flags = IORESOURCE_DMA,12 },13 [2] = {14 .start = DMACH_SPI0_RX,15 .end = DMACH_SPI0_RX,16 .flags = IORESOURCE_DMA,17 },18 [3] = {19 .start = IRQ_SPI0,20 .end = IRQ_SPI0,21 .flags = IORESOURCE_IRQ,22 },23 };2425/**26 * struct s3c64xx_spi_info - SPI Controller defining structure27 * @src_clk_nr: Clock source index for the CLK_CFG[SPI_CLKSEL] field.28 * @src_clk_name: Platform name of the corresponding clock.29 * @clk_from_cmu: If the SPI clock/prescalar control block is present30 * by the platform's clock-management-unit and not in SPI controller.31 * @num_cs: Number of CS this controller emulates.32 * @cfg_gpio: Configure pins for this SPI controller.33 * @fifo_lvl_mask: All tx fifo_lvl fields start at offset-634 * @rx_lvl_offset: Depends on tx fifo_lvl field and bus number35 * @high_speed: If the controller supports HIGH_SPEED_EN bit36 * @tx_st_done: Depends on tx fifo_lvl field37*/38static struct s3c64xx_spi_info s5pv210_spi0_pdata = {39 .cfg_gpio = s5pv210_spi_cfg_gpio, //将GPIO配置成SPI0引脚的函数40 .fifo_lvl_mask = 0x1ff,41 .rx_lvl_offset = 15,42 .high_speed = 1, //看s5pv210的使⽤⼿册P901可知:这是⽤来配置CH_CFG寄存器的,主要是210⽤于从设备时......43 .tx_st_done = 25,44 };4546static u64 spi_dmamask = DMA_BIT_MASK(32);4748struct platform_device s5pv210_device_spi0 = {49 .name = "s3c64xx-spi",50 .id = 0,51 .num_resources = ARRAY_SIZE(s5pv210_spi0_resource),52 .resource = s5pv210_spi0_resource,53 .dev = {54 .dma_mask = &spi_dmamask,55 .coherent_dma_mask = DMA_BIT_MASK(32),56 .platform_data = &s5pv210_spi0_pdata,//特殊的spi_master数据57 },58 };platform_device第②处:添加/修改SPI外设“静态描述”的结构。
需要了解Linux下SPI从设备驱动的编写需要了解Linux下SPI从设备驱动的编写SPI(Serial Peripheral Interface) 是一个同步的四线制串行线,用于连接微控制器和传感器、存储器及外围设备。
三条信号线持有时钟信号(SCLK,经常在10MHz左右)和并行数据线带有“主出,从进(MOSI)”或是“主进,从出(MISO)”信号。
数据交换的时候有四种时钟模式,模式0和模式3是最经常使用的。
每个时钟周期将会传递数据进和出。
如果没有数据传递的话,时钟将不会循环。
SPI驱动分为两类:控制器驱动:它们通常内嵌于片上系统处理器,通常既支持主设备,又支持从设备。
这些驱动涉及硬件寄存器,可能使用DMA。
或它们使用GPIO引脚成为PIO bitbangers。
这部分通常会由特定的开发板提供商提供,不用自己写。
协议驱动:它们通过控制器驱动,以SPI连接的方式在主从设备之间传递信息。
这部分涉及具体的SPI从设备,通常需要自己编写。
那么特定的目标板如何让Linux 操控SPI设备?下面以AT91SAM9260系列CAN设备驱动为例,Linux内核版本为2.6.19。
本文不涉及控制器驱动分析。
board_info提供足够的信息使得系统正常工作而不需要芯片驱动加载[cpp] view plain copy在arch/arm/mach-at91rm9200/board-at91sam9260.c中有如下代码:#include#include…….static struct spi_board_info ek_spi_devices[] = {/* spi can ,add by mrz */#if defined(CONFIG_CAN_MCP2515) {.modalias = "mcp2515",.chip_select = 0,// .controller_data = AT91_PIN_PB3,。
Linux SPI子系统驱动程序结构分析关键之:SPI、framework、platform、driver、deviceLinux SPI这个子系统系列的介绍会在linux驱动模型的基础上进行阐述,会偏重于framework的介绍,对于大牛可能会对这类文章不屑,但本系列仅当是一个知识备忘,当linux 体系这张大网织的差不多了,会有一个全新的系列,来去繁就简,成之经典,毕竟,现阶段,对这些的感悟还不是太深,将原来的工作内容进行回忆,将现在工作碰到的问题补充,下一阶段会有更深的体会的。
由于这是这个系统的第一篇文章,可能零碎的东西介绍的会多些。
0,分层与分离在面向对象的程序设计中,可以为某一类相似的事物定义一个基类,而具体的事物可以继承这个基类中的函数。
Linux 内核中频繁使用到面向对象的设计思想。
在设备驱动方面,往往为同类的设备设计了一个框架,而框架中的核心层则实现了该设备通用的一些功能。
而且具体的设备不想使用核心层的函数,它可以重载之。
这就是我们所说的在驱动设计中的分层思想。
此外,在驱动的设计中,我们还会使用分离的思想。
如果一个设备的驱动和host的驱动休戚相关,那么,这就意味着这个普通的设备如果用在不同的host上,会采用n个版本的驱动。
如果产品单一,也许感觉不到不使用分离思想来设计驱动的危害,但是我们想一下,这个世上被人们称道的多是什么?精品,艺术品!精品如何打造?注重细节,不只考虑单一需求!大家开发个东西不容易,怎么能随随便便就让它茫然众码矣呢,所以,何时何地,我们都要以打造精品的思想来要求自己,让自己的劳动力不浪费。
使用分离的思想来设计驱动的话,就够就是这样的:外设驱动与主机控制器的驱动不相关,主机控制器的驱动不关心外设,而外设驱动也不关心主机,外设只是访问核心层的通用API进行数据传输,主机和外设之间可以进行任意的组合。
相当于在控制器驱动和设备驱动之间增加一层核心层,对内对外都隐藏了对端的不确定性。
仔细通读USB,SPI,PCI的代码就会发现这种思想的体现。
1,设备模型在最新的设备驱动模型中,主要包含总线、设备和驱动三个实体,总线将设备和驱动绑定,在系统每注册一个设备的时候,会寻找与之匹配的驱动,反之,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。
所以,因此,由是之…(之所以写这么多,是因为自己在理解这个设备模型的时候,对照代码产生很多疑问,特别是在这儿。
现在回过头来看,觉得很显而易见的啊,:)看来那个什么Q还是有些问题),根据这个模型的需求,一个现实的linux设备和驱动通常都需要挂接在一种总线上,否则谁来管他们的匹配啊,注册驱动和注册设备都是由不同的API来完成的。
对于本身依附于PCI,USB,I2C,SPI等的设备而言,这自然不是问题,但是在嵌入式系统里面,Controller系统中集成的外设控制器,挂载在内存空间的外设确不依附于此类总线。
那咋整呢?而且这些东西还不少,像SPI控制器,PCI控制器啊都是这些,这些很多都已经是主了,还让他们靠谁去?基于这一背景,Linux发明了一种虚拟的总线(就是我们所说的除了政治领袖以外的精神领袖),称为platform总线,相应的设备称为platform_device,而驱动称为platform_driver。
2,platform总线这个框架中为platform总线定义了一个bus_type的实例platform_bus_type:1 struct bus_type platform_bus_type = {2 .name = “platform”,3 .dev_attrs = platform_dev_attrs,4 .match = platform_match,5 .uevent = platform_uevent,6 .pm = PLATFORM_PM_OPS_PTR,7 };8 EXPORT_SYMBOL_GPL(platform_bus_type);这里重点关注其match成员函数,正是此成员表明了platform_device和platform_driver 之间如何匹配。
1 static int platform_match(struct device *dev, struct device_driver *drv)2 {3 struct platform_device *pdev;45 pdev = container_of(dev, struct platform_device, dev);6 return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);7 }从代码中可以看出,匹配platform_device和platform_driver主要看两者的name字段是否相同。
对platform_device的定义通常在BSP包里面实现(即arch目录下的),在BSP文件中,将platform_device归纳为一个数组,最终通过platform_add_devices()函数统一注册。
platform_add_devices()函数可以讲平台设备添加到系统中。
3,SPI讲了这么多才说道SPI,看来老婆没有说错我啊,罗里啰嗦……因为该文档权当备忘,所以接下来还会罗嗦几句SPI的缘由3.1 what is SPISPI(同步外设接口)是由摩托罗拉公司开发的全双工同步串行总线,其接口由MISO(串行数据输入),MOSI(串行数据输出),SCK(串行移位时钟),SS(从使能信号)四种信号构成(当然了,现在芯片技术日新月异,SPI模块的结构也在变化中,象OMAP系列中的SPI模块还支持5线的一种模式),SS决定了唯一的与主设备通信的从设备,主设备通过产生移位时钟来发起通讯。
通讯时,数据由MOSI输出,MISO输入,数据在时钟的上升或下降沿由MOSI输出,在紧接着的下降或上升沿由MISO读入,这样经过8/16次时钟的改变,完成8/16位数据的传输。
SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置。
如果 CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。
如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。
3.2 SPI Framework#控制器设备和驱动控制器设备在BSP的初始化中注册,驱动在drvier/spi中#核心层驱动核心层代码负责这个框架中通用的部分,满足分层的思想。
主题承担的工作包括:注册spi 总线,提供基本SPI总线操作API。
#SPI外设驱动对于SPI的设备驱动,因为可爱的linux driver framework设计者的功劳,这里我们只需要用到spi.h中定义的方法就可以了,不用去修改spi控制器的代码。
一般的,我们的设备驱动框架是使用spi_regiser_driver向系统进行注册,就可以让系统用你指定的与.name相匹配的硬件交互并执行你的读写请求,满足分离的思想。
spi.h中大部分函数中都会用到struct spi_device *spi这个指针,在probe函数中获得这个指针,保存好这个指针,就可以在驱动中的任何地方通过他去处理与spi设备相关的操作。
3.3 SPI控制器驱动SPI控制器要挂载到platform总线上的,so,需要在BSP文件中添加相应的资源代码:通常,会在xxxx.c中添加:static struct platform_device da850_spi_pdev1 = {.name = “dm_spi”,.id = 1,.resource = da850_spi_resources1,.num_resources = ARRAY_SIZE(da850_spi_resources1),.dev = {.platform_data = &da850_spi_pdata1,},};然后会在BSP的init过程中使用platform_device_register将它注册进系统MACHINE_START(DAVINCI_DA850_EVM, “Gemstones Platform version 0.03″).phys_io = IO_PHYS,.io_pg_offst = (__IO_ADDRESS(IO_PHYS) >> 18) & 0xfffc,.boot_params = (0xC0000100),.map_io = da850_map_io,.init_irq = da850_evm_irq_init,.timer = &davinci_timer,.init_machine = da850_evm_init,MACHINE_END-> da850_evm_init()->da850_init_spi1()->platform_device_register(&da850_spi_pdev1) 自此,将SPI控制器设备注册进了系统,name字段为dm_spi我们还注意到,设备的资源信息还可以放在控制器的设备资源里面,利用platform提供的platform_data的支持,其中platform_data的形式是自定义的。
设备可以通过以下方式拿到platform_data信息:struct xxxx_plat_data *pdata = pdev->dev.platform_data;其中,pdev为platform_device的指针,当然也可以通过别的方法,只要能获得device的指针。
到这里先小总结一下:控制器驱动的流程主要就是:第一步:注册platform_device;第二步:注册platform_driver,然后platform总线会让两者匹配在一起。
使用platform总线在驱动中大体有以下几个好处:a,使得设备被挂接在一个总线上,使配套的sysfs节点、设备电源管理都成为可能。
b, 隔离了BSP和驱动。
BSP中定义platform设备和设备使用的资源(可以使用platform_data的形式来包括platform设备的设备),设备的具体配置信息。
而在驱动中,只需要通过通用API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。
以上注册完设备,下面就要开始注册驱动。
注册platform driver,首先要有platform_driver数据结构static struct platform_driver davinci_spi_driver = {.driver = {.name = “dm_spi”,.owner = THIS_MODULE,},.probe = davinci_spi_probe,.remove = davinci_spi_remove,.suspend = davinci_spi_suspend,.suspend_late = davinci_spi_suspend_late,.resume_early = davinci_spi_resume_early,};name字段要和device保持一致。