上一章我们讲解了如何编写 Linux 下的 I2C 设备驱动,SPI 也是很常用的串行通信协议,
本章我们就来学习如何在 Linux 下编写 SPI 设备驱动。本章实验的最终目的就是驱动 I.MX6U-ALPHA 开发板上的 ICM-20608 这个 SPI 接口的六轴传感器,可以在应用程序中读取 ICM-20608
的原始传感器数据。

1 Linux SPI 驱动框架简介

        SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC
SPI 控制器接口。比如在裸机篇中的《第二十七章 SPI 实验》,我们编写了 bsp_spi.c bsp_spi.h 这两个文件,这两个文件是 I.MX6U SPI 控制器驱动,我们编写好 SPI 控制器驱动以后就可以直接使用了,不管是什么 SPI 设备,SPI 控制器部分的驱动都是一样,我们的重点就落在了种类繁多的 SPI 设备驱动。

1.1 SPI 主机驱动

        SPI 主机驱动就是 SOC SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。Linux 内核
使用 spi_master 表示 SPI 主机驱动,spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件
中,内容如下(有缩减)
315 struct spi_master {
316     struct device dev;
317
318     struct list_head list;
......
326     s16 bus_num;
327
328     /* chipselects will be integral to many controllers; some others
329      * might use board-specific GPIOs.
330      */
331     u16 num_chipselect;
332
333     /* some SPI controllers pose alignment requirements on DMAable
334      * buffers; let protocol drivers know about these requirements.
335      */
336     u16 dma_alignment;
337
338     /* spi_device.mode flags understood by this controller driver */
339     u16 mode_bits;
340
341     /* bitmask of supported bits_per_word for transfers */
342     u32 bits_per_word_mask;
......
347     /* limits on transfer speed */
348     u32 min_speed_hz;
349     u32 max_speed_hz;
350
351     /* other constraints relevant to this driver */
352     u16 flags;
......
359     /* lock and mutex for SPI bus locking */
360     spinlock_t bus_lock_spinlock;
361     struct mutex bus_lock_mutex;
362
363     /* flag indicating that the SPI bus is locked for exclusive use */
364     bool bus_lock_flag;
......
372     int (*setup)(struct spi_device *spi);
373
......
393     int (*transfer)(struct spi_device *spi,
394                     struct spi_message *mesg);
......
434     int (*transfer_one_message)(struct spi_master *master,
435                                struct spi_message *mesg);
......
462 };
        第 393 行,transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函
数。
        第 434 行,transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message, SPI 的数据会打包成 spi_message,然后以队列方式发送出去。
也就是 SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱
动编写者而言 transfer 函数是需要实现的,因为不同的 SOC SPI 控制器不同,寄存器都不一
样。和 I2C 适配器驱动一样,SPI 主机驱动一般都是 SOC 厂商去编写的,所以我们作为 SOC
使用者,这一部分的驱动就不用操心了,除非你是在 SOC 原厂工作,内容就是写 SPI 主机驱
动。
        SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册
spi_master

1spi_master 申请与释放

spi_alloc_master 函数用于申请 spi_master,函数原型如下:
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
函数参数和返回值含义如下:
dev:设备,一般是 platform_device 中的 dev 成员变量。
size私有数据大小,可以通过 spi_master_get_devdata 函数获取到这些私有数据。
返回值:申请到的 spi_master
spi_master 的释放通过 spi_master_put 函数来完成,当我们删除一个 SPI 主机驱动的时候就
需要释放掉前面申请的 spi_masterspi_master_put 函数原型如下:
void spi_master_put(struct spi_master *master)
函数参数和返回值含义如下:
master:要释放的 spi_master
返回值:无。
2spi_master 的注册与注销
spi_master 初始化完成以后就需要将其注册到 Linux 内核,spi_master 注册函数为
spi_register_master,函数原型如下:
int spi_register_master(struct spi_master *master)
函数参数和返回值含义如下:
master:要注册的 spi_master
返回值:0,成功;负值,失败。
I.MX6U SPI 主机驱动会采用 spi_bitbang_start 这个 API 函数来完成 spi_master 的注册,
spi_bitbang_start 函数内部其实也是通过调用 spi_register_master 函数来完成 spi_master 的注册。
如果要注销 spi_master 的话可以使用 spi_unregister_master 函数,此函数原型为:
void spi_unregister_master(struct spi_master *master)
函数参数和返回值含义如下:
master:要注销的 spi_master
返回值:无。
如果使用 spi_bitbang_start 注册 spi_master 的话就要使用 spi_bitbang_stop 来注销掉
spi_master

1.2 SPI 设备驱动

        spi 设备驱动也和 i2c 设备驱动也很类似,Linux 内核使用 spi_driver 结构体来表示 spi 设备
驱动,我们在编写 SPI 设备驱动的时候需要实现 spi_driverspi_driver 结构体定义在
include/linux/spi/spi.h 文件中,结构体内容如下:
180 struct spi_driver {
181     const struct spi_device_id *id_table;
182     int (*probe)(struct spi_device *spi);
183     int (*remove)(struct spi_device *spi);
184     void (*shutdown)(struct spi_device *spi);
185     struct device_driver driver;
186 };
        可以看出,spi_driver i2c_driverplatform_driver 基本一样,当 SPI 设备和驱动匹配成功
以后 probe 函数就会执行。
        同样的,spi_driver 初始化完成以后需要向 Linux 内核注册,spi_driver 注册函数为
spi_register_driver,函数原型如下:
int spi_register_driver(struct spi_driver *sdrv)
函数参数和返回值含义如下:
sdrv要注册的 spi_driver
返回值:0,注册成功;赋值,注册失败。
注销 SPI 设备驱动以后也需要注销掉前面注册的 spi_driver,使用 spi_unregister_driver
数完成 spi_driver 的注销,函数原型如下:
void spi_unregister_driver(struct spi_driver *sdrv)
函数参数和返回值含义如下:
sdrv要注销的 spi_driver
返回值:无。
spi_driver 注册示例程序如下:
3 static int xxx_probe(struct spi_device *spi)
4 {
5     /* 具体函数内容 */
6     return 0;
7 }
8 
9 /* remove 函数 */
10 static int xxx_remove(struct spi_device *spi)
11 {
12     /* 具体函数内容 */
13     return 0;
14 }
15 /* 传统匹配方式 ID 列表 */
16 static const struct spi_device_id xxx_id[] = {
17     {"xxx", 0}, 
18     {}
19 };
20
21 /* 设备树匹配列表 */
22 static const struct of_device_id xxx_of_match[] = {
23     { .compatible = "xxx" },
24     { /* Sentinel */ }
25 };
26
27 /* SPI 驱动结构体 */
28 static struct spi_driver xxx_driver = {
29     .probe = xxx_probe,
30     .remove = xxx_remove,
31     .driver = {
32         .owner = THIS_MODULE,
33         .name = "xxx",
34         .of_match_table = xxx_of_match,
35     },
36     .id_table = xxx_id,
37 };
38 
39 /* 驱动入口函数 */
40 static int __init xxx_init(void)
41 {
42     return spi_register_driver(&xxx_driver);
43 }
44
45 /* 驱动出口函数 */
46 static void __exit xxx_exit(void)
47 {
48     spi_unregister_driver(&xxx_driver);
49 }
50
51 module_init(xxx_init);
52 module_exit(xxx_exit);
        第 1~36 行,spi_driver 结构体,需要 SPI 设备驱动人员编写,包括匹配表、probe 函数等。
i2c_driverplatform_driver 一样,就不详细讲解了。
        第 39~42 行,在驱动入口函数中调用 spi_register_driver 来注册 spi_driver
        第 45~48 行,在驱动出口函数中调用 spi_unregister_driver 来注销 spi_driver

1.3 SPI 设备和驱动匹配过程

        SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,这点和 platformI2C 等驱动一样,SPI
总线为 spi_bus_type,定义在 drivers/spi/spi.c 文件中,内容如下:
131 struct bus_type spi_bus_type = {
132     .name = "spi",
133     .dev_groups = spi_dev_groups,
134     .match = spi_match_device,
135     .uevent = spi_uevent,
136 };
        可以看出,SPI 设备和驱动的匹配函数为 spi_match_device,函数内容如下:
99 static int spi_match_device(struct device *dev,
100			     struct device_driver *drv)
101 {
102	const struct spi_device *spi = to_spi_device(dev);
103	const struct spi_driver *sdrv = to_spi_driver(drv);
104
105	/* Attempt an OF style match */
106	if (of_driver_match_device(dev, drv))
107		return 1;
108
109	/* Then try ACPI */
110	if (acpi_driver_match_device(dev, drv))
111		return 1;
112
113	if (sdrv->id_table)
114		return !!spi_match_id(sdrv->id_table, spi);
115
116	return strcmp(spi->modalias, drv->name) == 0;
117 }
        spi_match_device 函数和 i2c_match_device 函数的对于设备和驱动的匹配过程基本一样。
        第 105 行,of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节
点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 SPI
备和驱动匹配。
        第 109 行,acpi_driver_match_device 函数用于 ACPI 形式的匹配。
        第 113 行,spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较 SPI
设备名字和 spi_device_id name 字段是否相等,相等的话就说明 SPI 设备和驱动匹配。
        第 115 行,比较 spi_device modalias 成员变量和 device_driver 中的 name 成员变量是否
相等。

2 I.MX6U SPI 主机驱动分析

        和 I2C 的适配器驱动一样,SPI 主机驱动一般都由 SOC 厂商编写好了,打开 imx6ull.dtsi
文件,找到如下所示内容:
1 ecspi3: ecspi@02010000 {
2     #address-cells = <1>;
3     #size-cells = <0>;
4     compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
5     reg = <0x02010000 0x4000>;
6     interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
7     clocks = <&clks IMX6UL_CLK_ECSPI3>,
8     <&clks IMX6UL_CLK_ECSPI3>;
9     clock-names = "ipg", "per";
10     dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;
11     dma-names = "rx", "tx";
12     status = "disabled";
13 };
        重点来看一下第 4 行的 compatible 属性值,compatible 属性有两个值“fsl,imx6ul-ecspi”和
fsl,imx51-ecspi”,在 Linux 内核源码中搜素这两个属性值即可找到 I.MX6U 对应的 ECSPI(SPI)
主机驱动。I.MX6U ECSPI 主机驱动文件为 drivers/spi/spi-imx.c,在此文件中找到如下内容:
694 static struct platform_device_id spi_imx_devtype[] = {
695     {
696         .name = "imx1-cspi",
697         .driver_data = (kernel_ulong_t) &imx1_cspi_devtype_data,
698     }, {
699     .name = "imx21-cspi",
700     .driver_data = (kernel_ulong_t) &imx21_cspi_devtype_data,
......
713     }, {
714     .name = "imx6ul-ecspi",
715     .driver_data = (kernel_ulong_t) &imx6ul_ecspi_devtype_data,
716 }, {
717 /* sentinel */
718 }
719 };
720
721 static const struct of_device_id spi_imx_dt_ids[] = {
722     { .compatible = "fsl,imx1-cspi", .data =&imx1_cspi_devtype_data, },
......
728     { .compatible = "fsl,imx6ul-ecspi", .data =&imx6ul_ecspi_devtype_data, },
729 { /* sentinel */ }
730 };
731 MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);
......
1338 static struct platform_driver spi_imx_driver = {
1339     .driver = {
1340     .name = DRIVER_NAME,
1341     .of_match_table = spi_imx_dt_ids,
1342     .pm = IMX_SPI_PM,
1343 },
1344     .id_table = spi_imx_devtype,
1345     .probe = spi_imx_probe,
1346     .remove = spi_imx_remove,
1347 };
1348 module_platform_driver(spi_imx_driver);
        第 714 行,spi_imx_devtype SPI 无设备树匹配表。
        第 721 行,spi_imx_dt_ids SPI 设备树匹配表。
        第 728 行,“fsl,imx6ul-ecspi”匹配项,因此可知 I.MX6U ECSPI 驱动就是 spi-imx.c 这个
文件。
        第 1338~1347 行,platform_driver 驱动框架,和 I2C 的适配器驱动一样,SPI 主机驱动器采
用了 platfom 驱动框架。当设备和驱动匹配成功以后 spi_imx_probe 函数就会执行。
spi_imx_probe 函数会从设备树中读取相应的节点属性值,申请并初始化 spi_master,最后
调用 spi_bitbang_start 函数(spi_bitbang_start 会调用 spi_register_master 函数)Linux 内核注册
spi_master
        对于 I.MX6U 来讲,SPI 主机的最终数据收发函数为 spi_imx_transfer,此函数通过如下层
层调用最终实现 SPI 数据发送:
spi_imx_transfer
        -> spi_imx_pio_transfer
                -> spi_imx_push
                        -> spi_imx->tx
spi_imx 是个 spi_imx_data 类型的机构指针变量,其中 tx rx 这两个成员变量分别为 SPI
数据发送和接收函数。I.MX6U SPI 主机驱动会维护一个 spi_imx_data 类型的变量 spi_imx,并
且使用 spi_imx_setupxfer 函数来设置 spi_imx tx rx 函数。根据要发送的数据数据位宽的不
同,分别有 8 位、16 位和 32 位的发送函数,如下所示:
spi_imx_buf_tx_u8
spi_imx_buf_tx_u16
spi_imx_buf_tx_u32
同理,也有 8 位、16 位和 32 位的数据接收函数,如下所示:
spi_imx_buf_rx_u8
spi_imx_buf_rx_u16
spi_imx_buf_rx_u32
我们就以 spi_imx_buf_tx_u8 这个函数为例,看看,一个自己的数据发送是怎么完成的,在
spi-imx.c 文件中找到如下所示内容:
152 #define MXC_SPI_BUF_TX(type)
153 static void spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx)
154 {
155         type val = 0;
156
157         if (spi_imx->tx_buf) {
158                 val = *(type *)spi_imx->tx_buf;
159                 spi_imx->tx_buf += sizeof(type);
160 }
161
162         spi_imx->count -= sizeof(type);
163
164         writel(val, spi_imx->base + MXC_CSPITXDATA);
165 }
166
167 MXC_SPI_BUF_RX(u8)
168 MXC_SPI_BUF_TX(u8)
        spi_imx_buf_tx_u8 函数是通过 MXC_SPI_BUF_TX 宏来实现 的。第 164 行就是将要发送的数据值写入到 ECSPI TXDATA 寄存器里面去,这和我们 SPI 裸 机实验的方法一样。将第 168 行的 MXC_SPI_BUF_TX(u8)展开就是 spi_imx_buf_tx_u8 函数。
        其他的 tx rx 函数都是这样实现的,这里就不做介绍了。关于 I.MX6U 的主机驱动程序就讲
解到这里,基本套路和 I2C 的适配器驱动程序类似。

3 SPI 设备驱动编写流程

3.1 SPI 设备信息描述

1IO pinctrl 子节点创建与修改

        首先肯定是根据所使用的 IO 来创建或修改 pinctrl 子节点,这个没什么好说的,唯独要注意的就是检查相应的 IO 有没有被其他的设备所使用,如果有的话需要将其删除掉!

2SPI 设备节点的创建与修改

        采用设备树的情况下,SPI 设备信息描述就通过创建相应的设备子节点来完成,我们可以
打开 imx6qdl-sabresd.dtsi 这个设备树头文件,在此文件里面找到如下所示内容:
308 &ecspi1 {
309     fsl,spi-num-chipselects = <1>;
310     cs-gpios = <&gpio4 9 0>;
311     pinctrl-names = "default";
312     pinctrl-0 = <&pinctrl_ecspi1>;
313     status = "okay";
314
315     flash: m25p80@0 {
316     #address-cells = <1>;
317     #size-cells = <1>;
318     compatible = "st,m25p32";
319     spi-max-frequency = <20000000>;
320     reg = <0>;
321     };
322 };
        在这个板子的 ECSPI 接口上接了一个 m25p80,这是一个 SPI 接口的设备。
        第 309 行,设置“fsl,spi-num-chipselects”属性为 1,表示只有一个设备。
        第 310 行,设置“cs-gpios”属性,也就是片选信号为 GPIO4_IO09
        第 311 行,设置“pinctrl-names”属性,也就是 SPI 设备所使用的 IO 名字。
        第 312 行,设置“pinctrl-0”属性,也就是所使用的 IO 对应的 pinctrl 节点。
        第 313 行,将 ecspi1 节点的“status”属性改为“okay”。
        第 315~320 行,ecspi1 下的 m25p80 设备信息,每一个 SPI 设备都采用一个子节点来描述
其设备信息。第 315 行的“m25p80@0”后面的“0”表示 m25p80 的接到了 ECSPI 的通道 0上。这个要根据自己的具体硬件来设置。
        第 318 行,SPI 设备的 compatible 属性值,用于匹配设备驱动。
        第 319 行,“spi-max-frequency”属性设置 SPI 控制器的最高频率,这个要根据所使用的
SPI 设备来设置,比如在这里将 SPI 控制器最高频率设置为 20MHz
        第 320 行,reg 属性设置 m25p80 这个设备所使用的 ECSPI 通道,和“m25p80@0”后面的
0”一样。
        我们一会在编写 ICM20608 的设备树节点信息的时候就参考示例代码中的内容即可。

3.2 SPI 设备数据收发处理流程

        SPI 设备驱动的核心是 spi_driver。当我们向 Linux 内 核注册成功 spi_driver 以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。 首先是 spi_transfer 结构体,此结构体用于描述 SPI 传输信息,结构体内容如下:
603 struct spi_transfer {
604 /* it's ok if tx_buf == rx_buf (right?)
605 * for MicroWire, one buffer must be null
606 * buffers must work with dma_*map_single() calls, unless
607 * spi_message.is_dma_mapped reports a pre-existing mapping
608 */
609     const void *tx_buf;
610     void *rx_buf;
611     unsigned len;
612
613     dma_addr_t tx_dma;
614     dma_addr_t rx_dma;
615     struct sg_table tx_sg;
616     struct sg_table rx_sg;
617
618     unsigned cs_change:1;
619     unsigned tx_nbits:3;
620     unsigned rx_nbits:3;
621     #define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
622     #define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
623     #define SPI_NBITS_QUAD 0x04 /* 4bits transfer */
624     u8 bits_per_word;
625     u16 delay_usecs;
626     u32 speed_hz;
627
628     struct list_head transfer_list;
629 };
        第 609 行,tx_buf 保存着要发送的数据。
        第 610 行,rx_buf 用于保存接收到的数据。
        第 611 行,len 是要进行传输的数据长度,SPI 是全双工通信,因此在一次通信中发送和
接收的字节数都是一样的,所以 spi_transfer 中也就没有发送长度和接收长度之分。
spi_transfer 需要组织成 spi_messagespi_message 也是一个结构体,内容如下:
660 struct spi_message {
661     struct list_head transfers;
662
663     struct spi_device *spi;
664
665     unsigned is_dma_mapped:1;
        ......
678     /* completion is reported through a callback */
679     void (*complete)(void *context);
680     void *context;
681     unsigned frame_length;
682     unsigned actual_length;
683     int status;
684
685 /* for optional use by whatever driver currently owns the
686 * spi_message ... between calls to spi_async and then later
687 * complete(), that's the spi_master controller driver.
688 */
689     struct list_head queue;
690     void *state;
691 };
        在使用spi_message之前需要对其进行初始化,spi_message初始化函数为spi_message_init
函数原型如下:
void spi_message_init(struct spi_message *m)
        spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,这里我们要用 到 spi_message_add_tail 函数,此函数原型如下:
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
        spi_message 准备好以后就可以进行数据传输了,数据传输分为同步传输和异步传输,同步
传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:
int spi_sync(struct spi_device *spi, struct spi_message *message)
        异步传输不会阻塞的等到 SPI 数据传输完成,异步传输需要设置 spi_message 中的 complete 成员变量,complete 是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。SPI 异步传 输函数为 spi_async,函数原型如下:
int spi_async(struct spi_device *spi, struct spi_message *message)
        在本章实验中,我们采用同步传输方式来完成 SPI 数据的传输工作,也就是 spi_sync 函数。
        综上所述,SPI 数据传输步骤如下:
①、申请并初始化 spi_transfer,设置 spi_transfer tx_buf 成员变量,tx_buf 为要发送的数
据。然后设置 rx_buf 成员变量,rx_buf 保存着接收到的数据。最后设置 len 成员变量,也就是
要进行数据通信的长度。
②、使用 spi_message_init 函数初始化 spi_message
③、使用spi_message_add_tail函数将前面设置好的spi_transfer添加到spi_message 队列中。
④、使用 spi_sync 函数完成 SPI 数据同步传输。
通过 SPI 进行 n 个字节的数据发送和接收的示例代码如下所示:
/* SPI 多字节发送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len)
{
    int ret;
    struct spi_message m;

    struct spi_transfer t = {
        .tx_buf = buf,
        .len = len,
    };
    spi_message_init(&m); /* 初始化 spi_message */
    spi_message_add_tail(&t, &m); /* 将 spi_transfer 添加到 spi_message 队列 */
    ret = spi_sync(spi, &m); /* 同步传输 */
    return ret;
}

/* SPI 多字节接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len)
{
    int ret;
    struct spi_message m;

    struct spi_transfer t = {
        .rx_buf = buf,
        .len = len,
    };
    spi_message_init(&m); /* 初始化 spi_message */
    spi_message_add_tail(&t, &m); /* 将 spi_transfer 添加到 spi_message 队列 */
    ret = spi_sync(spi, &m); /* 同步传输 */
    return ret;
}

4 硬件原理图分析

5 试验程序编写

5.1 修改设备树

1、添加 ICM20608 所使用的 IO

        首先在 imx6ull-alientek-emmc.dts 文件中添加 ICM20608 所使用的 IO 信息,在 iomuxc 节点
中添加一个新的子节点来描述 ICM20608 所使用的 SPI 引脚,子节点名字为 pinctrl_ecspi3,节
点内容如下所示:
1 pinctrl_ecspi3: icm20608 {
2     fsl,pins = <
3     MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */
4     MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */
5     MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */
6     MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */
7     >;
8 };
        UART2_TX_DATA 这个 IO ICM20608 的片选信号,这里我们并没有将其复用为 ECSPI3
SS0 信号,而是将其复用为了普通的 GPIO。因为我们需要自己控制片选信号,所以将其复
用为普通的 GPIO

2、在 ecspi3 节点追加 icm20608 子节点

        在 imx6ull-alientek-emmc.dts 文件中并没有任何向 ecspi3 节点追加内容的代码,这是因为
NXP 官方的 6ULL EVK 开发板上没有连接 SPI 设备。在 imx6ull-alientek-emmc.dts 文件最后面
加入如下所示内容:
1 &ecspi3 {
2     fsl,spi-num-chipselects = <1>;
3     cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
4     pinctrl-names = "default";
5     pinctrl-0 = <&pinctrl_ecspi3>;
6     status = "okay";
7 
8     spidev: icm20608@0 {
9     compatible = "alientek,icm20608";
10     spi-max-frequency = <8000000>;
11     reg = <0>;
12     };
13 };
2 行,设置当前片选数量为 1,因为就只接了一个 ICM20608
3 行,一定要使用 “cs-gpios”属性来描述片选引脚,SPI 主机驱动就会控制片选引脚。
5 行,设置 IO 要使用的 pinctrl 子节点,也就是我们在示例代码中新建的pinctrl_ecspi3。
6 行,imx6ull.dtsi 文件中默认将 ecspi3 节点状态(status)设置为“disable”,这里我们要将
其改为“okay”。
8~12 行,icm20608 设备子节点,因为 icm20608 连接在 ECSPI3 的第 0 个通道上,因此
@后面为 0。第 9 行设置节点属性兼容值为“alientek,icm20608”,第 10 行设置 SPI 最大时钟频
率为 8MHz,这是 ICM20608 SPI 接口所能支持的最大的时钟频率。第 11 行,icm20608 连接
在通道 0 上,因此 reg 0
imx6ull-alientek-emmc.dts 文件修改完成以后重新编译一下,得到新的 dtb 文件,并使用新
dtb 启动 Linux 系统。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐