1、概览
对于ARM平台来说,大多数CPU都是SoC。spi控制器被集成在CPU内部。spi总线上的数据传输过程通常就是这个spi控制器来控制的。为了使spi控制器能工作在linux spi子系统中,我们就需要针对CPU内部的spi控制器编写一个驱动。前面的博文提到过,在linux spi系统中struct spi_master
对象就对应了一个spi控制器。编写spi控制器驱动其实就是实现struct spi_master
中的各种接口,并将该struct spi_master
结构注册到spi子系统中去。下面将结合代码,具体介绍如何实现一个spi控制器驱动。
2、向内核申明spi控制器设备
对ARM平台中各种控制器,主要有两种方法在内核中申明一个spi控制器设备。一种是在板级代码中向内核中注册一个struct platform_device
对象。另一种方法是使用device tree来描述spi控制器的各种信息,然后由device tree相关代码解析并向内核申明一个spi控制器相关的设备。对于第一种方法主要在2.6.xx的内核中普遍使用的。而第二种方法在3.xx的内核中使用的。虽然说新版的内核对于第一种方法仍然支持,但是已经很少使用了。对于描述spi控制器上述两种方法同样适用,下面具体介绍两种方法。
2.1)板级代码中向内核注册spi控制器
为了更好理解,我们可以通过代码来具体讲解如何注册一个描述spi控制器的struct platform_device
。鉴于新的平台已经使用Device Tree来描述SoC中的各种控制器,我们选用比较老的CPU来讲解这种方式描述spi控制器,我们用s3c64xx的板级代码作为示例。目前在3.xx内核中arch/arm/mach-s3c64xx/mach-crag6410.c中有如何注册描述spi控制器的struct platform_device
的。此文件371行左右有如下代码:
371 static struct platform_device *crag6410_devices[] __initdata = {
/*[......] */ /* 省略与spi无关代码 */
385 &s3c64xx_device_spi0,
/*[......]*/
397 };
代码中定义了一个struct platform_device
指针数组nexcoder_devices[]
,这个数组中的struct platform_device
指针对应的struct platform_device
用来表示CPU上个各种总线的控制器。这个数组中的各个struct platform_device
最终都会被注册到内核中。由上面的代码可以看出与spi控制器相关的struct platform_device
结构:s3c64xx_device_spi0
,表示s3c64xx上的spi0。注册crag6410_devices[]
中的struct platform_device
对象到内核中的的代码如下:
825 static void __init crag6410_machine_init(void)
826 {
/* [......] */ /* 省略与spi无关代码 */
855 s3c64xx_spi0_set_platdata(NULL, 0, 2);
856
857 platform_add_devices(crag6410_devices, ARRAY_SIZE(crag6410_devices));
858
/* [......] */
864 }
s3c64xx_spi0_set_platdata()
设置一些spi控制器相关信息到s3c64xx_device_spi0
中去,包括片选信息,该spi控制器的时钟等等,这些信息会在后面的spi控制器驱动中用到,代码如下:
1541 void __init s3c64xx_spi0_set_platdata(int (*cfg_gpio)(void), int src_clk_nr,
1542 int num_cs)
1543 {
1544 struct s3c64xx_spi_info pd;
1545
1546 /* Reject invalid configuration */
1547 if (!num_cs || src_clk_nr < 0) {
1548 pr_err("%s: Invalid SPI configuration\n", __func__);
1549 return;
1550 }
1551
1552 pd.num_cs = num_cs;
1553 pd.src_clk_nr = src_clk_nr;
1554 pd.cfg_gpio = (cfg_gpio) ? cfg_gpio : s3c64xx_spi0_cfg_gpio;
1555
1556 s3c_set_platdata(&pd, sizeof(pd), &s3c64xx_device_spi0);
1557 }
我们回过头来看看s3c64xx_device_spi0
是什么样的,它们在文件arch/arm/plat-samsung/devs.c中的1522行左右:
1522 #ifdef CONFIG_S3C64XX_DEV_SPI0
1523 static struct resource s3c64xx_spi0_resource[] = {
1524 [0] = DEFINE_RES_MEM(S3C_PA_SPI0, SZ_256), //spi寄存器地址信息
1525 [1] = DEFINE_RES_DMA(DMACH_SPI0_TX),
1526 [2] = DEFINE_RES_DMA(DMACH_SPI0_RX),
1527 [3] = DEFINE_RES_IRQ(IRQ_SPI0),//中断信息
1528 };
1529
1530 struct platform_device s3c64xx_device_spi0 = {
1531 .name = "s3c6410-spi",
1532 .id = 0,
1533 .num_resources = ARRAY_SIZE(s3c64xx_spi0_resource),
1534 .resource = s3c64xx_spi0_resource,
1535 .dev = {
1536 .dma_mask = &samsung_device_dma_mask,
1537 .coherent_dma_mask = DMA_BIT_MASK(32),
1538 },
1539 };
代码中struct resource
对象描述的是与spi控制器硬件相关信息,包括寄存器起始地址和地址大小,DMA相关信息,以及中断号等信息,这些信息在spi控制器驱动中会用到。若希望一个spi控制器可以正常的工作在linux spi子系统中,构建并注册一个描述spi控制器的struct platform_device
对象是必须的。只有注册了这个设备,才能在spi控制器的平台设备驱动注册时匹配这个设备,并使用这个struct platform_device
中的信息来够建和注册struct spi_master
对象到linux spi子系统中去。所有的spi设备驱动必须要调用struct spi_master
对象中接口来发送和接收数据。
2.2)device tree描述spi控制器
使用device tree来描述spi控制器相关信息就会简单很多,它的格式也比较固定,下面以基于TI的AM33xx的CPU来讲解如何使用device tree来描述spi控制器信息。在arch/arm/boot/dts/am33xxx.dtsi中有关于spi控制器的描述:
369 spi0: spi@48030000 {
370 compatible = "ti,omap4-mcspi"; //用来和驱动的of_device_id项目匹配
371 #address-cells = <1>;
372 #size-cells = <0>;
373 reg = <0x48030000 0x400>; //spi控制器寄存器地址信息
374 interrupt = <65>; //spi相关中断信息
375 ti,spi-num-cs = <2>; //片选脚个数
376 ti,hwmods = "spi0";
377 dmas = <&edma 16
378 &edma 17
379 &edma 18
380 &edma 19>;
381 dma-names = "tx0", "rx0", "tx1", "rx1";
382 status = "disabled"; //控制是否解析该节点
383 };
384
385 spi1: spi@481a0000 {
386 compatible = "ti,omap4-mcspi";
387 #address-cells = <1>;
388 #size-cells = <0>;
389 reg = <0x481a0000 0x400>;
390 interrupt = <125>;
391 ti,spi-num-cs = <2>;
392 ti,hwmods = "spi1";
393 dmas = <&edma 42
394 &edma 43
395 &edma 44
396 &edma 45>;
397 dma-names = "tx0", "rx0", "tx1", "rx1";
398 status = "disabled";
399
400 };
其实device tree中描述的spi控制器信息在device tree解析时也会转化成一个struct platform_device
对象,并注册的到内核。它和第一种方法本质上是一样的,不过这种方法不需要在板级代码中构建各种struct platform_device
对象来描述CPU上各种控制器的信息,可以提高代码复用度。
每个描述spi控制器的device tree节点会被转化为一个struct platform_device对象。device tree节点中的reg
和interrupt
属性也会被转化为对应的描述控制器寄存器和中断信息的struct resource
对象,并被该节点转化的struct platform_device
对象中的resource
成员引用。
关于如何Device Tree来描述spi控制器的详细介绍,请参考内核源码中Documentation/devicetree/bindings/spi/目录下的文档。
关于Device Tree的用法请参考内核源码中Documentation/devicetree/下的文档。
3、spi控制器平台设备驱动
和上一节一样,本节中也会用两个驱动,分别对应上一节中每个小节。用s3c64xx CPU的spi控制器驱动来讲解在板级代码中用struct platform_device
对象来描述spi控制器时,驱动的写法。用am33xx CPU的spi控制器驱动来讲解用device tree描述spi控制器时驱动的写法。
在开始讲解这两个驱动之前,先来详细讲解spi控制器驱动中会用到的各种数据结构。首先我们来了解一下spi_bus_type
:
230 struct bus_type spi_bus_type = {
231 .name = "spi",
232 .dev_attrs = spi_dev_attrs,
233 .match = spi_match_device,
234 .uevent = spi_uevent,
235 .pm = &spi_pm,
236 };