完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:正点原子Linux开发板
2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》 关注官方微信号公众号,获取更多资料:正点原子 第六十二章Linux SPI驱动实验 上一章我们讲解了如何编写Linux下的I2C设备驱动,SPI也是很常用的一个串行通信协议,本章我们就来学习一下如何在Linux下编写SPI设备驱动。本章实验的最终目的就是驱动I.MX6U-ALPHA开发板上的ICM-20608这个SPI接口的六轴传感器,可以在应用程序中读取ICM-20608的原始传感器数据。 62.1 Linux下SPI驱动框架简介SPI驱动框架和I2C很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是发SOC的SPI控制器接口。比如在裸机篇中的《第二十七章SPI实验》,我们编写了bsp_spi.c和bsp_spi.h这两个文件,这两个文件是I.MX6U的SPI控制器驱动,我们编写好SPI控制器驱动以后就可以直接使用了,不管是什么SPI设备,SPI控制器部分的驱动都是一样,我们的重点就落在了种类繁多的SPI设备驱动。 62.1.1 SPI主机驱动SPI主机驱动就是SOC的SPI控制器驱动,类似I2C驱动里面的适配器驱动。Linux内核使用spi_master表示SPI主机驱动,spi_master是个结构体,定义在include/linux/spi/spi.h文件中,内容如下(有缩减): 示例代码62.1.1.1 spi_master结构体 315struct spi_master { 316struct 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; 361struct mutex bus_lock_mutex; 362 363/* flag indicating that the SPI bus is locked for exclusive use */ 364bool bus_lock_flag; ...... 372int(*setup)(struct spi_device *spi); 373 ...... 393int(*transfer)(struct spi_device *spi, 394struct spi_message *mesg); ...... 434 int(*transfer_one_message)(struct spi_master *master, 435struct 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,函数原型如下: 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函数原型如下: void spi_master_put(struct spi_master *master) 函数参数和返回值含义如下: master:要释放的spi_master。 返回值:无。 2、spi_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。 62.1.2 SPI设备驱动spi设备驱动也和i2c设备驱动也很类似,Linux内核使用spi_driver结构体来表示spi设备驱动,我们在编写SPI设备驱动的时候需要实现spi_driver。spi_driver结构体定义在include/linux/spi/spi.h文件中,结构体内容如下: 示例代码62.1.1.2 spi_driver结构体 180struct spi_driver { 180conststruct spi_device_id *id_table; 180int(*probe)(struct spi_device *spi); 180int(*remove)(struct spi_device *spi); 180void(*shutdown)(struct spi_device *spi); 180struct device_driver driver; 180}; 可以看出,spi_driver和i2c_driver、platform_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注册示例程序如下: 示例代码62.1.1.3 spi_driver注册示例程序 1 /* probe函数 */ 2staticint xxx_probe(struct spi_device *spi) 3{ 4 /* 具体函数内容 */ 5 return0; 6} 7 8/* remove函数 */ 9staticint xxx_remove(struct spi_device *spi) 10{ 11 /* 具体函数内容 */ 12 return0; 13} 14/* 传统匹配方式ID列表 */ 15staticconststruct spi_device_id xxx_id[]={ 16 {"xxx",0}, 17 {} 18}; 19 20/* 设备树匹配列表 */ 21staticconststruct of_device_id xxx_of_match[]={ 22 {.compatible ="xxx"}, 23 {/* Sentinel */} 24}; 25 26/* SPI驱动结构体 */ 27staticstruct spi_driver xxx_driver ={ 28 .probe = xxx_probe, 29 .remove = xxx_remove, 30 .driver ={ 31 .owner = THIS_MODULE, 32 .name ="xxx", 33 .of_match_table = xxx_of_match, 34 }, 35 .id_table = xxx_id, 36}; 37 38/* 驱动入口函数 */ 39staticint __init xxx_init(void) 40{ 41 return spi_register_driver(&xxx_driver); 42} 43 44/* 驱动出口函数 */ 45staticvoid __exit xxx_exit(void) 46{ 47 spi_unregister_driver(&xxx_driver); 48} 49 50 module_init(xxx_init); 51 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。 62.1.3 SPI设备和驱动匹配过程SPI设备和驱动的匹配过程是由SPI总线来完成的,这点和platform、I2C等驱动一样,SPI总线为spi_bus_type,定义在drivers/spi/spi.c文件中,内容如下: 示例代码62.1.3.1 spi_bus_type结构体 131struct 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,函数内容如下: 示例代码62.1.3.2 spi_match_device函数 99staticint spi_match_device(struct device *dev, struct device_driver *drv) 100{ 101conststruct spi_device *spi = to_spi_device(dev); 102conststruct spi_driver *sdrv = to_spi_driver(drv); 103 104/* Attempt an OF style match */ 105if(of_driver_match_device(dev, drv)) 106return1; 107 108/* Then try ACPI */ 109if(acpi_driver_match_device(dev, drv)) 110return1; 111 112if(sdrv->id_table) 113return!!spi_match_id(sdrv->id_table, spi); 114 115return strcmp(spi->modalias, drv->name)==0; 116} 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行,i2c_match_id函数用于传统的、无设备树的I2C设备和驱动匹配过程。比较I2C设备名字和i2c_device_id的name字段是否相等,相等的话就说明I2C设备和驱动匹配。 第115行,比较spi_device中modalias成员变量和device_driver中的name成员变量是否相等。 62.2 I.MX6U SPI主机驱动分析和I2C的适配器驱动一样,SPI主机驱动一般都由SOC厂商编写好了,打开imx6ull.dtsi文件,找到如下所示内容: 示例代码62.2.1 imx6ull.dtsi文件中的ecspi3节点内容 1 ecspi3: ecspi@02010000 { 2 #address-cells =<1>; 3 #size-cells =<0>; 4 compatible ="fsl,imx6ul-ecspi","fsl,imx51-ecspi"; 5 reg =<0x020100000x4000>; 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 771>,<&sdma 872>; 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,在此文件中找到如下内容: 示例代码62.2.2 spi_imx_driver结构体 694staticstruct 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 721staticconststruct 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); ...... 1338staticstruct 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文件中找到如下所示内容: 示例代码62.2.3 spi_imx_buf_tx_u8函数 152 #define MXC_SPI_BUF_TX(type) 153staticvoid spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx) 154{ 155 type val =0; 156 157if(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) 从示例代码62.2.3可以看出,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的适配器驱动程序类似。 62.3 SPI设备驱动编写流程62.3.1 SPI设备信息描述1、IO的pinctrl子节点创建与修改 首先肯定是根据所使用的IO来创建或修改pinctrl子节点,这个没什么好说的,唯独要注意的就是检查相应的IO有没有被其他的设备所使用,如果有的话需要将其删除掉! 2、SPI设备节点的创建与修改 采用设备树的情况下,SPI设备信息描述就通过创建相应的设备子节点来完成,我们可以打开imx6qdl-sabresd.dtsi这个设备树头文件,在此文件里面找到如下所示内容: 示例代码62.3.1.1 m25p80设备节点 308&ecspi1 { 309 fsl,spi-num-chipselects =<1>; 310 cs-gpios =<&gpio4 90>; 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}; 示例代码62.3.1.1是I.MX6Q的一款板子上的一个SPI设备节点,在这个板子的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的设备树节点信息的时候就参考示例代码62.3.1.1中的内容即可。 62.3.2 SPI设备数据收发处理流程SPI设备驱动的核心是spi_driver,这个我们已经在62.1.2小节讲过了。当我们向Linux内核注册成功spi_driver以后就可以使用SPI核心层提供的API函数来对设备进行读写操作了。首先是spi_transfer结构体,此结构体用于描述SPI传输信息,结构体内容如下: 示例代码62.3.2.1 spi_transfer结构体 603struct 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 */ 609constvoid*tx_buf; 610void*rx_buf; 611unsigned len; 612 613 dma_addr_t tx_dma; 614 dma_addr_t rx_dma; 615struct sg_table tx_sg; 616struct sg_table rx_sg; 617 618unsigned cs_change:1; 619unsigned tx_nbits:3; 620unsigned 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 628struct list_head transfer_list; 629}; 第609行,tx_buf保存着要发送的数据。 第610行,rx_buf用于保存接收到的数据。 第611行,len是要进行传输的数据长度,SPI是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以spi_transfer中也就没有发送长度和接收长度之分。 spi_transfer需要组织成spi_message,spi_message也是一个结构体,内容如下: 示例代码62.3.2.2 spi_message结构体 660struct spi_message { 661struct list_head transfers; 662 663struct spi_device *spi; 664 665unsigned is_dma_mapped:1; ...... 678/* completion is reported through a callback */ 679void(*complete)(void*context); 680void*context; 681unsigned frame_length; 682unsigned actual_length; 683int 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 */ 689struct list_head queue; 690void*state; 691}; 在使用spi_message之前需要对其进行初始化,spi_message初始化函数为spi_message_init,函数原型如下: void spi_message_init(struct spi_message *m) 函数参数和返回值含义如下: m:要初始化的spi_message。 返回值:无。 spi_message初始化完成以后需要将spi_transfer添加到spi_message队列中,这里我们要用到spi_message_add_tail函数,此函数原型如下: voidspi_message_add_tail(struct spi_transfer *t, struct spi_message *m) 函数参数和返回值含义如下: t:要添加到队列中的spi_transfer。 m:spi_transfer要加入的spi_message。 返回值:无。 spi_message准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待SPI数据传输完成,同步传输函数为spi_sync,函数原型如下: int spi_sync(struct spi_device *spi, struct spi_message *message) 函数参数和返回值含义如下: spi:要进行数据传输的spi_device。 message:要传输的spi_message。 返回值:无。 异步传输不会阻塞的等到SPI数据传输完成,异步传输需要设置spi_message中的complete成员变量,complete是一个回调函数,当SPI异步传输完成以后此函数就会被调用。SPI异步传输函数为spi_async,函数原型如下: int spi_async(struct spi_device *spi, struct spi_message *message) 函数参数和返回值含义如下: spi:要进行数据传输的spi_device。 message:要传输的spi_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个字节的数据发送和接收的示例代码如下所示: 示例代码62.3.2.3 SPI数据读写操作 /* SPI多字节发送 */ staticint 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多字节接收 */ staticint 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; } 62.4 硬件原理图分析本章实验硬件原理图参考26.2小节即可。 62.5 试验程序编写本实验对应的例程路径为:开发板光盘->2、Linux驱动例程->22_spi。 62.5.1 修改设备树1、添加ICM20608所使用的IO 首先在imx6ull-alientek-emmc.dts文件中添加ICM20608所使用的IO信息,在iomuxc节点中添加一个新的子节点来描述ICM20608所使用的SPI引脚,子节点名字为pinctrl_ecspi3,节点内容如下所示: 示例代码62.5.1.1 icm20608 IO节点信息 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文件最后面加入如下所示内容: 示例代码62.5.1.2 向ecspi3节点加入icm20608信息 1&ecspi3 { 2 fsl,spi-num-chipselects =<1>; 3 cs-gpio =<&gpio1 20 GPIO_ACTIVE_LOW>;/* cant't use cs-gpios! */ 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"属性,而是用了一个自己定义的"cs-gpio"属性,因为我们要自己控制片选引脚。如果使用"cs-gpios"属性的话SPI主机驱动就会控制片选引脚。 第5行,设置IO要使用的pinctrl子节点,也就是我们在示例代码62.5.1.1中新建的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系统。 62.5.2 编写ICM20608驱动新建名为"22_spi"的文件夹,然后在22_spi文件夹里面创建vscode工程,工作区命名为"spi"。工程创建好以后新建icm20608.c和icm20608reg.h这两个文件,icm20608.c为ICM20608的驱动代码,icm20608reg.h是ICM20608寄存器头文件。先在icm20608reg.h中定义好ICM20608的寄存器,输入如下内容(有省略,完成的内容请参考例程): 示例代码62.5.2.1 icm20608reg.h文件内容 1 #ifndef ICM20608_H 2 #define ICM20608_H 3/*************************************************************** 4 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 5 文件名 : icm20608reg.h 6作者 : 左忠凯 7版本 : V1.0 8描述 : ICM20608寄存器地址描述头文件 9其他 : 无 10论坛 : www.openedv.com 11日志 : 初版V1.0 2019/9/2 左忠凯创建 12 ***************************************************************/ 13 #define ICM20608G_ID 0XAF/* ID值 */ 14 #define ICM20608D_ID 0XAE/* ID值 */ 15 16/* ICM20608寄存器 17 *复位后所有寄存器地址都为0,除了 18 *Register 107(0X6B) Power Management 1 = 0x40 19 *Register 117(0X75) WHO_AM_I = 0xAF或0xAE 20 */ 21/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */ 22 #define ICM20_SELF_TEST_X_GYRO 0x00 23 #define ICM20_SELF_TEST_Y_GYRO 0x01 24 #define ICM20_SELF_TEST_Z_GYRO 0x02 25 #define ICM20_SELF_TEST_X_ACCEL 0x0D 26 #define ICM20_SELF_TEST_Y_ACCEL 0x0E 27 #define ICM20_SELF_TEST_Z_ACCEL 0x0F ...... 80/* 加速度静态偏移 */ 81 #define ICM20_XA_OFFSET_H 0x77 82 #define ICM20_XA_OFFSET_L 0x78 83 #define ICM20_YA_OFFSET_H 0x7A 84 #define ICM20_YA_OFFSET_L 0x7B 85 #define ICM20_ZA_OFFSET_H 0x7D 86 #define ICM20_ZA_OFFSET_L 0x7E 87 88 #endif 接下来继续编写icm20608.c文件,因为icm20608.c文件内容比较长,因此这里就将其分开来讲解。 |
|
相关推荐
|
|
590 浏览 0 评论
AI模型部署边缘设备的奇妙之旅:如何在边缘端部署OpenCV
2241 浏览 0 评论
tms320280021 adc采样波形,为什么adc采样频率上来波形就不好了?
1233 浏览 0 评论
1788 浏览 0 评论
1464 浏览 0 评论
74844 浏览 21 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 10:04 , Processed in 0.653698 second(s), Total 61, Slave 44 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号