基于子系统去开发驱动程序已经是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驱动层次,如下图:
我们以上面的这个图为思路
1、 Platform busPlatform bus对应的结构是platform_bus_type,这个内核开始就定义好的。我们不需要定义。
2、Platform_deviceSPI控制器对应platform_device的定义方式,同样以S5PC100中的SPI控制器为例,参看arch/arm/plat-s5pc1xx/dev-spi.c文件
structplatform_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 structs3c64xx_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(structplatform_device *pdev)
{
switch (pdev->id) {
case 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;
case 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;
case 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;
default:
dev_err(&pdev->dev, "InvalidSPI Controller number!");
return -EINVAL;
}
3、Platform_driver再看platform_driver,参看drivers/spi/spi_s3c64xx.c文件
static structplatform_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