Linux SPI 驱动实验

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

1 LinuxSPI****驱动框架简介

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 文件
中,内容如下(有缩减):

cpp 复制代码
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。

1**、spi_master申请与释放**

spi_alloc_master 函数用于申请 spi_master,函数原型如下:

cpp 复制代码
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_master,spi_master_put 函数原型如下:

cpp 复制代码
void spi_master_put(struct spi_master *master)

函数参数和返回值含义如下:
master:要释放的 spi_master。
返回值:无。
2
spi_master的注册与注销

当 spi_master 初始化完成以后就需要将其注册到 Linux 内核,spi_master 注册函数为
spi_register_master,函数原型如下:

cpp 复制代码
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 函数,此函数原型为:

cpp 复制代码
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_driver。spi_driver 结构体定义在
include/linux/spi/spi.h 文件中,结构体内容如下:

cpp 复制代码
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_driver、platform_driver 基本一样,当 SPI 设备和驱动匹配成功
以后 probe 函数就会执行。
同样的,spi_driver 初始化完成以后需要向 Linux 内核注册,spi_driver 注册函数为
spi_register_driver,函数原型如下:

cpp 复制代码
int spi_register_driver(struct spi_driver *sdrv)

函数参数和返回值含义如下:
sdrv**:**要注册的 spi_driver。
**返回值:**0,注册成功;赋值,注册失败。
注销 SPI 设备驱动以后也需要注销掉前面注册的 spi_driver,使用 spi_unregister_driver 函
数完成 spi_driver 的注销,函数原型如下:

cpp 复制代码
void spi_unregister_driver(struct spi_driver *sdrv)

函数参数和返回值含义如下:
sdrv**:**要注销的 spi_driver。
**返回值:**无。
spi_driver 注册示例程序如下:

cpp 复制代码
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_driver、platform_driver 一样,就不详细讲解了。
第 39~42 行,在驱动入口函数中调用 spi_register_driver 来注册 spi_driver。
第 45~48 行,在驱动出口函数中调用 spi_unregister_driver 来注销 spi_driver。

1.3 SPI****设备和驱动匹配过程

SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,这点和 platform、I2C 等驱动一样,SPI
总线为 spi_bus_type,定义在 drivers/spi/spi.c 文件中,内容如下:

cpp 复制代码
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,函数内容如下:

cpp 复制代码
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
文件,找到如下所示内容:

cpp 复制代码
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,在此文件中找到如下内容:

cpp 复制代码
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****设备信息描述

1**、IOpinctrl子节点创建与修改**

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

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

采用设备树的情况下,SPI 设备信息描述就通过创建相应的设备子节点来完成,我们可以
打开 imx6qdl-sabresd.dtsi 这个设备树头文件,在此文件里面找到如下所示内容:

cpp 复制代码
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 传输信息,结构体内容如下:

cpp 复制代码
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_message,spi_message 也是一个结构体,内容如下:

cpp 复制代码
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,
函数原型如下:

cpp 复制代码
void spi_message_init(struct spi_message *m)

spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,这里我们要用 到 spi_message_add_tail 函数,此函数原型如下:

cpp 复制代码
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)

spi_message 准备好以后就可以进行数据传输了,数据传输分为同步传输和异步传输,同步
传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync,函数原型如下:

cpp 复制代码
int spi_sync(struct spi_device *spi, struct spi_message *message)

异步传输不会阻塞的等到 SPI 数据传输完成,异步传输需要设置 spi_message 中的 complete 成员变量,complete 是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。SPI 异步传 输函数为 spi_async,函数原型如下:

cpp 复制代码
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 个字节的数据发送和接收的示例代码如下所示:

cpp 复制代码
/* 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,节
点内容如下所示:

cpp 复制代码
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 文件最后面
加入如下所示内容:

cpp 复制代码
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 系统。

相关推荐
adnyting2 小时前
【Linux日新月异(九)】CentOS 7其他常用命令大全:系统操作与管理必备技巧
linux·运维·centos
-dcr2 小时前
39.华为云运维类服务
运维·服务器·华为云·lts·cts·ces·smn
偶像你挑的噻3 小时前
3-Linux驱动开发-简单内核模块代码详解
linux·驱动开发·stm32·嵌入式硬件
赖small强3 小时前
【Linux驱动开发】 Linux字符设备开发详细指南
linux·驱动开发·字符设备
p66666666683 小时前
【☀Linux驱动开发笔记☀】linux下led驱动(非设备树)_03
linux·驱动开发·笔记·嵌入式硬件·学习
木易 士心3 小时前
Protocol Buffers (Protobuf) 详解
运维·服务器
q***64973 小时前
华为HuaweiCloudStack(一)介绍与架构
服务器·华为·架构
百***24133 小时前
Nginx反向代理出现502 Bad Gateway问题的解决方案
运维·nginx·gateway
以琦琦为中心4 小时前
在RK3568开发板嵌入式开发中,配置NFS服务是实现与Ubuntu虚拟机之间文件共享的常用方法
linux·运维·ubuntu·rk3568