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的寄存器地址 2 static 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 }; 24 25 /** 26 * struct s3c64xx_spi_info - SPI Controller defining structure 27 * @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 present 30 * 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-6 34 * @rx_lvl_offset: Depends on tx fifo_lvl field and bus number 35 * @high_speed: If the controller supports HIGH_SPEED_EN bit 36 * @tx_st_done: Depends on tx fifo_lvl field 37 */ 38 static 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 }; 45 46 static u64 spi_dmamask = DMA_BIT_MASK(32); 47 48 struct 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 };
第②处:添加/修改SPI外设“静态描述”的结构。在arch/arm/mach-s5pv210/mach-smdkv210.c文件中添加/修改SPI外设信息。这个结构被spi_register_board_info()函数挂在boardlist上,这个结构就是外设相关的,也即是说驱动编写的内容之一。内核在注册了spi_master之后就开始在boardlist中搜索spi外设,然后向spi总线中注册spi_device,这个结构就是用spi_board_info来初始化的,这些过程也是前人做好的。
1 /* 2 * 以MMC SPI外设为例 3 */ 4 static struct s3c64xx_spi_csinfo smdk_spi0_csi[] = { 5 [SMDK_MMCSPI_CS] = { 6 .line = S5PV210_GPB(1), 7 .set_level = gpio_set_value, 8 .fb_delay = 0x2, 9 }, 10 }; 11 static struct s3c64xx_spi_csinfo smdk_spi1_csi[] = { 12 [SMDK_MMCSPI_CS] = { 13 .line = S5PV210_GPB(5), 14 .set_level = gpio_set_value, 15 .fb_delay = 0x2, 16 }, 17 }; 18 //在这个结构中根据具体的外设来修改内容:这个要依靠外设的使用手册来进行 19 static struct spi_board_info s5pv210_spi_devs[] __initdata = { 20 { 21 .modalias = "spidev", /* MMC SPI ,mod别名*/ 22 .mode = SPI_MODE_0, /* CPOL=0, CPHA=0 */ 23 .max_speed_hz = 10000000, 24 /* Connected to SPI-0 as 1st Slave */ 25 .bus_num = 0, //挂接在SPI0总线上 26 .chip_select = 0, 27 .controller_data = &smdk_spi0_csi[SMDK_MMCSPI_CS], 28 }, 29 { 30 .modalias = "spidev", /* MMC SPI */ 31 .mode = SPI_MODE_0, /* CPOL=0, CPHA=0 */ 32 .max_speed_hz = 10000000, 33 /* Connected to SPI-1 as 1st Slave */ 34 .bus_num = 1, //挂接在SPI1总线上 35 .chip_select = 0, 36 .controller_data = &smdk_spi1_csi[SMDK_MMCSPI_CS], 37 }, 38 };
第③处:spi_master有了,spi_device也有了,接下来就是SPI驱动编写的重头任务了!编写spi_driver驱动,就是要针对具体的外设编写具体的操作“策略”。在内核源码中有参考例程就是drivers/spi/spidev.c,总体的思路和之前学习的字符设备一样:构建spi_driver结构体,然后将它注册到spi总线中,构建spi的file_operations结构体然后创建spi字符设备,spi_driver找到spi_device就在probe中创建字符设备节点提供给应用层。