总线驱动–SPI驱动


文章目录

  • 总线驱动--SPI驱动
  • SPI主机驱动
    • 1、spi_master 申请与释放
    • 2、spi_master 的注册与注销
  • SPI设备驱动
  • SPI 设备和驱动匹配过程
  • I.MX6U SPI 主机驱动分析
  • SPI 设备驱动编写流程
    • SPI 设备信息描述
      • 1、 IO 的 pinctrl 子节点创建与修改
      • 2、 SPI 设备节点的创建与修改
    • SPI 设备数据收发处理流程
  • ICM20608实例
    • 修改设备树
      • 1、添加 ICM20608 所使用的 IO
      • 2、在 ecspi3 节点追加 icm20608 子节点
    • 编写 ICM20608 驱动
      • icm20608reg.h
      • icm20608.c
      • icm20608App.c
    • 编译测试 APP

SPI主机驱动

SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。 Linux 内核使用 spi_master 表示 SPI 主机驱动, spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件中,内容如下:

struct spi_master {struct device    dev; // 设备struct list_head list; // 链表/* other than negative (== assign one dynamically), bus_num is fully* board-specific.  usually that simplifies to being SOC-specific.* example:  one SOC has three SPI controllers, numbered 0..2,* and one board's schematics might show it using SPI-2.  software* would normally use bus_num=2 for that controller.*/s16            bus_num; // 总线编号/* chipselects will be integral to many controllers; some others* might use board-specific GPIOs.*/u16            num_chipselect; // 片选数量/* some SPI controllers pose alignment requirements on DMAable* buffers; let protocol drivers know about these requirements.*/u16            dma_alignment; // DMA对齐要求/* spi_device.mode flags understood by this controller driver */u16            mode_bits; // SPI模式/* bitmask of supported bits_per_word for transfers */u32            bits_per_word_mask; // 支持的字长掩码
#define SPI_BPW_MASK(bits) BIT((bits) - 1) // 位掩码
#define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1)) // 位掩码
#define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1)) // 位掩码/* limits on transfer speed */ // 传输速度限制u32            min_speed_hz; // 最小传输速度u32            max_speed_hz; // 最大传输速度/* other constraints relevant to this driver */u16            flags;
#define SPI_MASTER_HALF_DUPLEX    BIT(0)        /* can't do full duplex */
#define SPI_MASTER_NO_RX    BIT(1)        /* can't do buffer read */
#define SPI_MASTER_NO_TX    BIT(2)        /* can't do buffer write */
#define SPI_MASTER_MUST_RX      BIT(3)        /* requires rx */
#define SPI_MASTER_MUST_TX      BIT(4)        /* requires tx */    /* lock and mutex for SPI bus locking */spinlock_t        bus_lock_spinlock;struct mutex        bus_lock_mutex;/* flag indicating that the SPI bus is locked for exclusive use */bool            bus_lock_flag;/* Setup mode and clock, etc (spi driver may call many times).** IMPORTANT:  this may be called when transfers to another* device are active.  DO NOT UPDATE SHARED REGISTERS in ways* which could break those transfers.*/int            (*setup)(struct spi_device *spi);/* bidirectional bulk transfers** + The transfer() method may not sleep; its main role is*   just to add the message to the queue.* + For now there's no remove-from-queue operation, or*   any other request management* + To a given spi_device, message queueing is pure fifo** + The master's main job is to process its message queue,*   selecting a chip then transferring data* + If there are multiple spi_device children, the i/o queue*   arbitration algorithm is unspecified (round robin, fifo,*   priority, reservations, preemption, etc)** + Chipselect stays active during the entire message*   (unless modified by spi_transfer.cs_change != 0).* + The message transfers use clock and SPI mode parameters*   previously established by setup() for this device*/int            (*transfer)(struct spi_device *spi,struct spi_message *mesg);/* called on release() to free memory provided by spi_master */void            (*cleanup)(struct spi_device *spi);/** Used to enable core support for DMA handling, if can_dma()* exists and returns true then the transfer will be mapped* prior to transfer_one() being called.  The driver should* not modify or store xfer and dma_tx and dma_rx must be set* while the device is prepared.*/bool            (*can_dma)(struct spi_master *master,struct spi_device *spi,struct spi_transfer *xfer);/** These hooks are for drivers that want to use the generic* master transfer queueing mechanism. If these are used, the* transfer() function above must NOT be specified by the driver.* Over time we expect SPI drivers to be phased over to this API.*/bool                queued; // 是否排队struct kthread_worker        kworker; // 内核线程工作者struct task_struct        *kworker_task; // 内核线程任务struct kthread_work        pump_messages; // 工作队列spinlock_t            queue_lock; // 自旋锁struct list_head        queue; // 消息队列struct spi_message        *cur_msg; // 当前消息bool                idling; // 是否空闲bool                busy; // 是否忙bool                running; // 是否运行bool                rt; // 是否实时bool                auto_runtime_pm; // 是否自动运行时电源管理bool                            cur_msg_prepared; // 当前消息是否准备好bool                cur_msg_mapped; // 当前消息是否映射struct completion               xfer_completion; // 完成size_t                max_dma_len; // 最大DMA长度/* 准备传输硬件 */int (*prepare_transfer_hardware)(struct spi_master *master);/* 传输单个消息 */int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);/* 取消准备传输硬件 */int (*unprepare_transfer_hardware)(struct spi_master *master);/* 准备消息 */int (*prepare_message)(struct spi_master *master,struct spi_message *message);/* 取消准备消息 */int (*unprepare_message)(struct spi_master *master,struct spi_message *message);/** 这些钩子是为使用核心提供的transfer_one_message()的通用实现的驱动程序而设计的。*//* 设置芯片选择 */void (*set_cs)(struct spi_device *spi, bool enable);/* 传输单个spi_transfer */int (*transfer_one)(struct spi_master *master, struct spi_device *spi,struct spi_transfer *transfer);/* 处理错误 */void (*handle_err)(struct spi_master *master,struct spi_message *message);/* gpio chip select */int            *cs_gpios; // GPIO片选/* DMA channels for use with core dmaengine helpers */struct dma_chan        *dma_tx; // DMA发送通道struct dma_chan        *dma_rx; // DMA接收通道/* dummy data for full duplex devices */void            *dummy_rx; // 全双工设备的虚拟接收缓冲区void            *dummy_tx; // 全双工设备的虚拟发送缓冲区
};
  • transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。
  • 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。

SPI设备驱动

spi 设备驱动也和 i2c 设备驱动也很类似, Linux 内核使用 spi_driver 结构体来表示 spi 设备驱动,我们在编写 SPI 设备驱动的时候需要实现 spi_driver。 spi_driver 结构体定义在include/linux/spi/spi.h 文件中,结构体内容如下:

struct spi_driver {// SPI设备ID表const struct spi_device_id *id_table;// 探测SPI设备int            (*probe)(struct spi_device *spi);// 移除SPI设备int            (*remove)(struct spi_device *spi);// 关闭SPI设备void            (*shutdown)(struct spi_device *spi);// 设备驱动struct device_driver    driver;
};

可以看出,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 注册示例程序如下:

  /* probe 函数 */static int xxx_probe(struct spi_device *spi){/* 具体函数内容 */return 0;}/* remove 函数 */static int xxx_remove(struct spi_device *spi){/* 具体函数内容 */return 0;}/* 传统匹配方式 ID 列表 */static const struct spi_device_id xxx_id[] = {{"xxx", 0},{}};/* 设备树匹配列表 */static const struct of_device_id xxx_of_match[] = {{ .compatible = "xxx" },{ /* Sentinel */ }};/* SPI 驱动结构体 */static struct spi_driver xxx_driver = {.probe = xxx_probe,.remove = xxx_remove,.driver = {.owner = THIS_MODULE,.name = "xxx",.of_match_table = xxx_of_match,},.id_table = xxx_id,};/* 驱动入口函数 */static int   init xxx_init(void)
{return spi_register_driver(&xxx_driver);}/* 驱动出口函数 */static void   exit xxx_exit(void){spi_unregister_driver(&xxx_driver);}module_init(xxx_init);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。

SPI 设备和驱动匹配过程

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

struct bus_type spi_bus_type = {.name        = "spi",.dev_groups    = spi_dev_groups,.match        = spi_match_device,.uevent        = spi_uevent,
};

可以看出, SPI 设备和驱动的匹配函数为 spi_match_device,函数内容如下:

static int spi_match_device(struct device *dev, struct device_driver *drv)
{const struct spi_device    *spi = to_spi_device(dev);const struct spi_driver    *sdrv = to_spi_driver(drv);/* Attempt an OF style match */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI */if (acpi_driver_match_device(dev, drv))return 1;if (sdrv->id_table)return !!spi_match_id(sdrv->id_table, spi);return strcmp(spi->modalias, drv->name) == 0;
}
  • spi_match_device 函数和 i2c_match_device 函数的对于设备和驱动的匹配过程基本一样。
  • of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节点的 compatible 属性和 of_device_id 中的compatible 属性是否相等,如果相当的话就表示 SPI 设备和驱动匹配。
  • acpi_driver_match_device 函数用于 ACPI 形式的匹配。
  • spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较SPI设备名字和
  • spi_device_id 的 name 字段是否相等,相等的话就说明 SPI 设备和驱动匹配。
    比较 spi_device 中 modalias 成员变量和 device_driver 中的 name 成员变量是否相等。

I.MX6U SPI 主机驱动分析

和 I2C 的适配器驱动一样, SPI 主机驱动一般都由 SOC 厂商编写好了,打开 imx6ull.dtsi文件,找到如下所示内容:

   ecspi1: ecspi@02008000 {compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";reg = <0x02008000 0x4000>;interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;dmas = <&sdma 3 7 1>, <&sdma 4 7 2>;dma-names = "rx", "tx";clocks = <&clks IMX6SLL_CLK_ECSPI1>,<&clks IMX6SLL_CLK_ECSPI1>;clock-names = "ipg", "per";status = "disabled";};ecspi2: ecspi@0200c000 {compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";reg = <0x0200c000 0x4000>;interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;dmas = <&sdma 5 7 1>, <&sdma 6 7 2>;dma-names = "rx", "tx";clocks = <&clks IMX6SLL_CLK_ECSPI2>,<&clks IMX6SLL_CLK_ECSPI2>;clock-names = "ipg", "per";status = "disabled";};ecspi3: ecspi@02010000 {compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";reg = <0x02010000 0x4000>;interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;dma-names = "rx", "tx";clocks = <&clks IMX6SLL_CLK_ECSPI3>,<&clks IMX6SLL_CLK_ECSPI3>;clock-names = "ipg", "per";status = "disabled";};ecspi4: ecspi@02014000 {compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";reg = <0x02014000 0x4000>;interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;dmas = <&sdma 9 7 1>, <&sdma 10 7 2>;dma-names = "rx", "tx";clocks = <&clks IMX6SLL_CLK_ECSPI4>,<&clks IMX6SLL_CLK_ECSPI4>;clock-names = "ipg", "per";status = "disabled";};

重点来看一下第 4 行的 compatible 属性值, compatible 属性有两个值“fsl,imx6ul-ecspi”和“fsl,imx51-ecspi”,在 Linux 内核源码中搜素这两个属性值即可找到 I.MX6U 对应的 ECSPI(SPI)主机驱动。 I.MX6U 的 ECSPI 主机驱动文件为 drivers/spi/spi-imx.c,在此文件中找到如下内容:

static struct platform_device_id spi_imx_devtype[] = {{.name = "imx1-cspi",.driver_data = (kernel_ulong_t) &imx1_cspi_devtype_data,}, {.name = "imx21-cspi",.driver_data = (kernel_ulong_t) &imx21_cspi_devtype_data,}, {.name = "imx27-cspi",.driver_data = (kernel_ulong_t) &imx27_cspi_devtype_data,}, {.name = "imx31-cspi",.driver_data = (kernel_ulong_t) &imx31_cspi_devtype_data,}, {.name = "imx35-cspi",.driver_data = (kernel_ulong_t) &imx35_cspi_devtype_data,}, {.name = "imx51-ecspi",.driver_data = (kernel_ulong_t) &imx51_ecspi_devtype_data,}, {.name = "imx6ul-ecspi",.driver_data = (kernel_ulong_t) &imx6ul_ecspi_devtype_data,}, {/* sentinel */}};
static const struct of_device_id spi_imx_dt_ids[] = {{ .compatible = "fsl,imx1-cspi", .data = &imx1_cspi_devtype_data, },{ .compatible = "fsl,imx21-cspi", .data = &imx21_cspi_devtype_data, },{ .compatible = "fsl,imx27-cspi", .data = &imx27_cspi_devtype_data, },{ .compatible = "fsl,imx31-cspi", .data = &imx31_cspi_devtype_data, },{ .compatible = "fsl,imx35-cspi", .data = &imx35_cspi_devtype_data, },{ .compatible = "fsl,imx51-ecspi", .data = &imx51_ecspi_devtype_data, },{ .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data, },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);
static struct platform_driver spi_imx_driver = {.driver = {.name = DRIVER_NAME,.of_match_table = spi_imx_dt_ids,.pm = IMX_SPI_PM,},.id_table = spi_imx_devtype,.probe = spi_imx_probe,.remove = spi_imx_remove,
};
module_platform_driver(spi_imx_driver);
  • spi_imx_devtype 为 SPI 无设备树匹配表。
  • spi_imx_dt_ids 为 SPI 设备树匹配表。
  • “fsl,imx6ul-ecspi”匹配项,因此可知 I.MX6U 的 ECSPI 驱动就是 spi-imx.c 这个文件。
  • 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 文件中找到如下所示内容:

#define MXC_SPI_BUF_TX(type)                        \
static void spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx)        \
{                                    \type val = 0;                            \\if (spi_imx->tx_buf) {                        \val = *(type *)spi_imx->tx_buf;                \spi_imx->tx_buf += sizeof(type);            \}                                \\spi_imx->count -= sizeof(type);                    \\writel(val, spi_imx->base + MXC_CSPITXDATA);            \
}MXC_SPI_BUF_RX(u8)
MXC_SPI_BUF_TX(u8)
MXC_SPI_BUF_RX(u16)
MXC_SPI_BUF_TX(u16)
MXC_SPI_BUF_RX(u32)
MXC_SPI_BUF_TX(u32)

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 函数都是这样实现的,这里就不做介绍了。基本套路和 I2C 的适配器驱动程序类似。

SPI 设备驱动编写流程

SPI 设备信息描述

1、 IO 的 pinctrl 子节点创建与修改

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

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

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

&ecspi1 {fsl,spi-num-chipselects = <1>;cs-gpios = <&gpio4 9 0>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_ecspi1>;status = "okay";flash: m25p80@0 {#address-cells = <1>;#size-cells = <1>;compatible = "st,m25p32";spi-max-frequency = <20000000>;reg = <0>;};
};

I.MX6Q 的一款板子上的一个SPI 设备节点,在这个板子的 ECSPI 接
口上接了一个m25p80,这是一个 SPI 接口的设备。

  • 设置“fsl,spi-num-chipselects”属性为 1,表示只有一个设备。
  • 设置“cs-gpios”属性,也就是片选信号为 GPIO4_IO09。
  • 设置“pinctrl-names”属性,也就是 SPI 设备所使用的 IO 名字。
  • 设置“pinctrl-0”属性,也就是所使用的 IO 对应的 pinctrl 节点。
  • 将 ecspi1 节点的“status”属性改为“okay”。
  • ecspi1 下的 m25p80 设备信息,每一个 SPI 设备都采用一个子节点来描述其设备信息。第 315 行的“m25p80@0”后面的“0”表示 m25p80 的接到了ECSPI 的通道 0上。这个要根据自己的具体硬件来设置。
  • SPI 设备的 compatible 属性值,用于匹配设备驱动。
  • “spi-max-frequency”属性设置 SPI 控制器的最高频率,这个要根据所使用的SPI 设备来设置,比如在这里将 SPI 控制器最高频率设置为 20MHz。
    reg 属性设置 m25p80 这个设备所使用的ECSPI 通道,和“m25p80@0”后面的“0”一样。

我们一会在编写 ICM20608 的设备树节点信息的时候就参考示例代码 中的内容即可。

SPI 设备数据收发处理流程

SPI 设备驱动的核心是 spi_driver,这个我们已经在 62.1.2 小节讲过了。当我们向 Linux 内核注册成功 spi_driver 以后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。首先是 spi_transfer 结构体,此结构体用于描述 SPI 传输信息,结构体内容如下:

/*** struct spi_transfer - 一个读写缓冲区对* @tx_buf: 待写入的数据(dma-safe memory),或者为NULL* @rx_buf: 待读取的数据(dma-safe memory),或者为NULL* @tx_dma: tx_buf的DMA地址,如果@spi_message.is_dma_mapped* @rx_dma: rx_buf的DMA地址,如果@spi_message.is_dma_mapped* @tx_nbits: 写入使用的位数。如果为0,则使用默认值(SPI_NBITS_SINGLE)。* @rx_nbits: 读取使用的位数。如果为0,则使用默认值(SPI_NBITS_SINGLE)。* @len: rx和tx缓冲区的大小(以字节为单位)* @speed_hz: 选择与设备默认速度不同的速度进行此传输。如果为0,则使用默认值(来自@spi_device)。* @bits_per_word: 选择与设备默认的bits_per_word不同的bits_per_word进行此传输。如果为0,则使用默认值(来自@spi_device)。* @cs_change: 在此传输完成后影响chipselect* @delay_usecs: 在此传输之后延迟的微秒数,然后(可选)更改chipselect状态,然后开始下一个传输或完成此@spi_message。* @transfer_list: 通过@spi_message.transfers对传输进行排序* @tx_sg: 用于传输的Scatterlist,目前不适用于客户端使用* @rx_sg: 用于接收的Scatterlist,目前不适用于客户端使用** SPI传输始终写入与读取相同数量的字节。* 协议驱动程序应始终提供@rx_buf和/或@tx_buf。* 在某些情况下,它们可能还希望为正在传输的数据提供DMA地址;当底层驱动程序使用dma时,这可能会减少开销。** 如果传输缓冲区为null,则填充的数据将被忽略。如果tx_buf和rx_buf相同,则可以使用单个缓冲区进行全双工传输。
在这种情况下,传输长度必须是偶数,并且将交替写入和读取数据。
SPI传输的字节顺序为big-endian,除了SPI_LSB_FIRST。因此,例如,当bits_per_word为16时,缓冲区的长度为2N字节(@len = 2N),并以CPU字节顺序保存N个16位字。
当SPI传输的字大小不是8位的2的幂时,这些内存字包括额外的位。内存中的字始终由协议驱动程序视为右对齐,因此未定义的(rx)或未使用的(tx)位始终是最高有效位。
所有SPI传输都从相关的chipselect处开始。通常,它保持选定状态,直到消息中的最后一个传输之后。驱动程序可以使用cs_change来影响chipselect信号。
(i)如果传输不是消息中的最后一个,则使用此标志使chipselect在消息中间短暂地变为非活动状态。以这种方式切换chipselect可能需要终止芯片命令,从而使单个spi_message执行所有芯片事务。
(ii)当传输是消息中的最后一个时,芯片可能保持选定状态,直到下一个传输。在没有阻止消息发送到其他设备的情况下,这只是性能提示;启动发送到另一个设备的消息会取消选择此设备。但在其他情况下,这可以用于确保正确性。某些设备需要从一系列spi_message提交构建协议事务,在这些事务中,一个消息的内容由先前消息的结果确定,并且整个事务在chipselect变为非活动状态时结束。
当SPI可以以1x、2x或4x传输时,它可以通过@tx_nbits和@rx_nbits从设备获取此传输信息。在双向传输中,这两个应该都设置。用户可以使用SPI_NBITS_SINGLE(1x)SPI_NBITS_DUAL(2x)和SPI_NBITS_QUAD(4x)设置传输模式以支持这三种传输。
提交spi_message(及其spi_transfers)到较低层的代码负责管理其内存。对您没有明确设置的每个字段进行零初始化,以隔离未来的API更新。在提交消息及其传输后,请忽略它们,直到完成回调。
*/
struct spi_transfer {const void *tx_buf;     /* 待写入的数据(dma-safe memory),或者为NULL */void *rx_buf;           /* 待读取的数据(dma-safe memory),或者为NULL */unsigned len;           /* rx和tx缓冲区的大小(以字节为单位) */dma_addr_t tx_dma;      /* tx_buf的DMA地址,如果@spi_message.is_dma_mapped */dma_addr_t rx_dma;      /* rx_buf的DMA地址,如果@spi_message.is_dma_mapped */struct sg_table tx_sg;  /* 用于传输的Scatterlist,目前不适用于客户端使用 */struct sg_table rx_sg;  /* 用于接收的Scatterlist,目前不适用于客户端使用 */unsigned cs_change:1;   /* 在此传输完成后影响chipselect */unsigned tx_nbits:3;    /* 写入使用的位数。如果为0,则使用默认值(SPI_NBITS_SINGLE)。 */unsigned rx_nbits:3;    /* 读取使用的位数。如果为0,则使用默认值(SPI_NBITS_SINGLE)。 */
#define SPI_NBITS_SINGLE    0x01 /* 1bit transfer */
#define SPI_NBITS_DUAL      0x02 /* 2bits transfer */
#define SPI_NBITS_QUAD      0x04 /* 4bits transfer */u8 bits_per_word;       /* 选择与设备默认的bits_per_word不同的bits_per_word进行此传输。如果为0,则使用默认值(来自@spi_device)。 */u16 delay_usecs;        /* 在此传输之后延迟的微秒数,然后(可选)更改chipselect状态,然后开始下一个传输或完成此@spi_message。 */u32 speed_hz;           /* 选择与设备默认速度不同的速度进行此传输。如果为0,则使用默认值(来自@spi_device)。 */struct list_head transfer_list; /* 通过@spi_message.transfers对传输进行排序 */
};
  • tx_buf 保存着要发送的数据。
  • rx_buf 用于保存接收到的数据。
  • len 是要进行传输的数据长度, SPI 是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以 spi_transfer 中也就没有发送长度和接收长度之分。
    spi_transfer 需要组织成 spi_message, spi_message 也是一个结构体,内容如下:
struct spi_message {struct list_head    transfers;struct spi_device    *spi;unsigned        is_dma_mapped:1;/* REVISIT:  we might want a flag affecting the behavior of the* last transfer ... allowing things like "read 16 bit length L"* immediately followed by "read L bytes".  Basically imposing* a specific message scheduling algorithm.** Some controller drivers (message-at-a-time queue processing)* could provide that as their default scheduling algorithm.  But* others (with multi-message pipelines) could need a flag to* tell them about such special cases.*//* completion is reported through a callback */void            (*complete)(void *context);void            *context;unsigned        frame_length;unsigned        actual_length;int            status;/* for optional use by whatever driver currently owns the* spi_message ...  between calls to spi_async and then later* complete(), that's the spi_master controller driver.*/struct list_head    queue;void            *state;
};

在使用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 函数,此函数原型如下:

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

spi_message_init(&m);       /* 初始化 spi_message */
spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */ ret = spi_sync(spi, &m);    /*
同步传输 */
return ret;
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_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 多字节接收 */
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,
};
}

ICM20608实例

修改设备树

1、添加 ICM20608 所使用的 IO

首先在 imx6ull-alientek-emmc.dts 文件中添加 ICM20608 所使用的 IO 信息,在 iomuxc 节点中添加一个新的子节点来描述 ICM20608 所使用的 SPI 引脚,子节点名字为 pinctrl_ecspi3,节点内容如下所示:

pinctrl_ecspi3: icm20608 {fsl,pins = <MX6UL_PAD_UART2_TX_DATA  GPIO1_IO20     0x10b0 /* CS */MX6UL_PAD_UART2_RX_DATA  ECSPI3_SCLK    0x10b1 /* SCLK */MX6UL_PAD_UART2_RTS_B  ECSPI3_MISO      0x10b1 /* MISO */           MX6UL_PAD_UART2_CTS_B  ECSPI3_MOSI      0x10b1 /、* MOSI */>;
};

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

  &ecspi3 {fsl,spi-num-chipselects = <1>;cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;pinctrl-names = "default";pinctrl-0 = <&pinctrl_ecspi3>;status = "okay";spidev: icm20608@0 {compatible = "alientek,icm20608";spi-max-frequency = <8000000>;reg = <0>;};

};

第 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 系统

编写 ICM20608 驱动

icm20608reg.h

#ifndef ICM20608_H
#define ICM20608_H#define ICM20608G_ID            0XAF    /* ID值 */
#define ICM20608D_ID            0XAE    /* ID值 *//* ICM20608寄存器 *复位后所有寄存器地址都为0,除了*Register 107(0X6B) Power Management 1     = 0x40*Register 117(0X75) WHO_AM_I               = 0xAF或0xAE*/
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#define ICM20_SELF_TEST_X_GYRO      0x00
#define ICM20_SELF_TEST_Y_GYRO      0x01
#define ICM20_SELF_TEST_Z_GYRO      0x02
#define ICM20_SELF_TEST_X_ACCEL     0x0D
#define ICM20_SELF_TEST_Y_ACCEL     0x0E
#define ICM20_SELF_TEST_Z_ACCEL     0x0F/* 陀螺仪静态偏移 */
#define ICM20_XG_OFFS_USRH          0x13
#define ICM20_XG_OFFS_USRL          0x14
#define ICM20_YG_OFFS_USRH          0x15
#define ICM20_YG_OFFS_USRL          0x16
#define ICM20_ZG_OFFS_USRH          0x17
#define ICM20_ZG_OFFS_USRL          0x18#define ICM20_SMPLRT_DIV            0x19
#define ICM20_CONFIG                0x1A
#define ICM20_GYRO_CONFIG           0x1B
#define ICM20_ACCEL_CONFIG          0x1C
#define ICM20_ACCEL_CONFIG2         0x1D
#define ICM20_LP_MODE_CFG           0x1E
#define ICM20_ACCEL_WOM_THR         0x1F
#define ICM20_FIFO_EN               0x23
#define ICM20_FSYNC_INT             0x36
#define ICM20_INT_PIN_CFG           0x37
#define ICM20_INT_ENABLE            0x38
#define ICM20_INT_STATUS            0x3A/* 加速度输出 */
#define ICM20_ACCEL_XOUT_H          0x3B
#define ICM20_ACCEL_XOUT_L          0x3C
#define ICM20_ACCEL_YOUT_H          0x3D
#define ICM20_ACCEL_YOUT_L          0x3E
#define ICM20_ACCEL_ZOUT_H          0x3F
#define ICM20_ACCEL_ZOUT_L          0x40/* 温度输出 */
#define ICM20_TEMP_OUT_H            0x41
#define ICM20_TEMP_OUT_L            0x42/* 陀螺仪输出 */
#define ICM20_GYRO_XOUT_H           0x43
#define ICM20_GYRO_XOUT_L           0x44
#define ICM20_GYRO_YOUT_H           0x45
#define ICM20_GYRO_YOUT_L           0x46
#define ICM20_GYRO_ZOUT_H           0x47
#define ICM20_GYRO_ZOUT_L           0x48#define ICM20_SIGNAL_PATH_RESET     0x68
#define ICM20_ACCEL_INTEL_CTRL      0x69
#define ICM20_USER_CTRL             0x6A
#define ICM20_PWR_MGMT_1            0x6B
#define ICM20_PWR_MGMT_2            0x6C
#define ICM20_FIFO_COUNTH           0x72
#define ICM20_FIFO_COUNTL           0x73
#define ICM20_FIFO_R_W              0x74
#define ICM20_WHO_AM_I              0x75/* 加速度静态偏移 */
#define ICM20_XA_OFFSET_H           0x77
#define ICM20_XA_OFFSET_L           0x78
#define ICM20_YA_OFFSET_H           0x7A
#define ICM20_YA_OFFSET_L           0x7B
#define ICM20_ZA_OFFSET_H           0x7D
#define ICM20_ZA_OFFSET_L           0x7E#endif

icm20608.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "icm20608reg.h"#define ICM20608_CNT    1
#define ICM20608_NAME   "icm20608"struct icm20608_dev {dev_t devid;                /* 设备号   */struct cdev cdev;           /* cdev     */struct class *class;        /* 类        */struct device *device;      /* 设备    */struct device_node  *nd;    /* 设备节点 */int major;                  /* 主设备号 */void *private_data;         /* 私有数据         */signed int gyro_x_adc;      /* 陀螺仪X轴原始值      */signed int gyro_y_adc;      /* 陀螺仪Y轴原始值     */signed int gyro_z_adc;      /* 陀螺仪Z轴原始值         */signed int accel_x_adc;     /* 加速度计X轴原始值    */signed int accel_y_adc;     /* 加速度计Y轴原始值    */signed int accel_z_adc;     /* 加速度计Z轴原始值    */signed int temp_adc;        /* 温度原始值            */
};static struct icm20608_dev icm20608dev;/** @description : 从icm20608读取多个寄存器数据* @param - dev:  icm20608设备* @param - reg:  要读取的寄存器首地址* @param - val:  读取到的数据* @param - len:  要读取的数据长度* @return      : 操作结果*/
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{int ret = -1;unsigned char txdata[1];unsigned char * rxdata;struct spi_message m;struct spi_transfer *t;struct spi_device *spi = (struct spi_device *)dev->private_data;t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);   /* 申请内存 */if(!t) {return -ENOMEM;}rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);   /* 申请内存 */if(!rxdata) {goto out1;}/* 一共发送len+1个字节的数据,第一个字节为寄存器首地址,一共要读取len个字节长度的数据,*/txdata[0] = reg | 0x80;     /* 写数据的时候首寄存器地址bit8要置1 */           t->tx_buf = txdata;         /* 要发送的数据 */t->rx_buf = rxdata;         /* 要读取的数据 */t->len = len+1;             /* t->len=发送的长度+读取的长度 */spi_message_init(&m);       /* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m);    /* 同步发送 */if(ret) {goto out2;}memcpy(buf , rxdata+1, len);  /* 只需要读取的数据 */out2:kfree(rxdata);                  /* 释放内存 */
out1:   kfree(t);                       /* 释放内存 */return ret;
}/** @description : 向icm20608多个寄存器写入数据* @param - dev:  icm20608设备* @param - reg:  要写入的寄存器首地址* @param - val:  要写入的数据缓冲区* @param - len:  要写入的数据长度* @return    :   操作结果*/
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{int ret = -1;unsigned char *txdata;struct spi_message m;struct spi_transfer *t;struct spi_device *spi = (struct spi_device *)dev->private_data;t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);   /* 申请内存 */if(!t) {return -ENOMEM;}txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);if(!txdata) {goto out1;}/* 一共发送len+1个字节的数据,第一个字节为寄存器首地址,len为要写入的寄存器的集合,*/*txdata = reg & ~0x80;  /* 写数据的时候首寄存器地址bit8要清零 */memcpy(txdata+1, buf, len); /* 把len个寄存器拷贝到txdata里,等待发送 */t->tx_buf = txdata;         /* 要发送的数据 */t->len = len+1;             /* t->len=发送的长度+读取的长度 */spi_message_init(&m);       /* 初始化spi_message */spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ret = spi_sync(spi, &m);    /* 同步发送 */if(ret) {goto out2;}out2:kfree(txdata);              /* 释放内存 */
out1:kfree(t);                   /* 释放内存 */return ret;
}/** @description : 读取icm20608指定寄存器值,读取一个寄存器* @param - dev:  icm20608设备* @param - reg:  要读取的寄存器* @return    :   读取到的寄存器值*/
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{u8 data = 0;icm20608_read_regs(dev, reg, &data, 1);return data;
}/** @description : 向icm20608指定寄存器写入指定的值,写一个寄存器* @param - dev:  icm20608设备* @param - reg:  要写的寄存器* @param - data: 要写入的值* @return   :    无*/ static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{u8 buf = value;icm20608_write_regs(dev, reg, &buf, 1);
}/** @description : 读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、*              : 三轴加速度计和内部温度。* @param - dev : ICM20608设备* @return      : 无。*/
void icm20608_readdata(struct icm20608_dev *dev)
{unsigned char data[14] = { 0 };icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14);dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]); dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]); dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]); dev->temp_adc    = (signed short)((data[6] << 8) | data[7]); dev->gyro_x_adc  = (signed short)((data[8] << 8) | data[9]); dev->gyro_y_adc  = (signed short)((data[10] << 8) | data[11]);dev->gyro_z_adc  = (signed short)((data[12] << 8) | data[13]);
}/** @description     : 打开设备* @param - inode   : 传递给驱动的inode* @param - filp    : 设备文件,file结构体有个叫做pr似有ate_data的成员变量*                    一般在open的时候将private_data似有向设备结构体。* @return          : 0 成功;其他 失败*/
static int icm20608_open(struct inode *inode, struct file *filp)
{filp->private_data = &icm20608dev; /* 设置私有数据 */return 0;
}/** @description     : 从设备读取数据 * @param - filp    : 要打开的设备文件(文件描述符)* @param - buf     : 返回给用户空间的数据缓冲区* @param - cnt     : 要读取的数据长度* @param - offt    : 相对于文件首地址的偏移* @return          : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{signed int data[7];long err = 0;struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;icm20608_readdata(dev);data[0] = dev->gyro_x_adc;data[1] = dev->gyro_y_adc;data[2] = dev->gyro_z_adc;data[3] = dev->accel_x_adc;data[4] = dev->accel_y_adc;data[5] = dev->accel_z_adc;data[6] = dev->temp_adc;err = copy_to_user(buf, data, sizeof(data));return 0;
}/** @description     : 关闭/释放设备* @param - filp    : 要关闭的设备文件(文件描述符)* @return          : 0 成功;其他 失败*/
static int icm20608_release(struct inode *inode, struct file *filp)
{return 0;
}/* icm20608操作函数 */
static const struct file_operations icm20608_ops = {.owner = THIS_MODULE,.open = icm20608_open,.read = icm20608_read,.release = icm20608_release,
};/** ICM20608内部寄存器初始化函数 * @param   : 无* @return  : 无*/
void icm20608_reginit(void)
{u8 value = 0;icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);mdelay(50);icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);mdelay(50);value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);printk("ICM20608 ID = %#X\r\n", value); icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00);    /* 输出速率是内部采样率                   */icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18);   /* 陀螺仪±2000dps量程                */icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18);  /* 加速度计±16G量程                   */icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04);        /* 陀螺仪低通滤波BW=20Hz               */icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz            */icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00);    /* 打开加速度计和陀螺仪所有轴                */icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00);   /* 关闭低功耗                        */icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00);       /* 关闭FIFO                       */
}/** @description     : spi驱动的probe函数,当驱动与*                    设备匹配以后此函数就会执行* @param - client  : i2c设备* @param - id      : i2c设备ID* */
static int icm20608_probe(struct spi_device *spi)
{/* 1、构建设备号 */if (icm20608dev.major) {icm20608dev.devid = MKDEV(icm20608dev.major, 0);register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);} else {alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);icm20608dev.major = MAJOR(icm20608dev.devid);}/* 2、注册设备 */cdev_init(&icm20608dev.cdev, &icm20608_ops);cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);/* 3、创建类 */icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);if (IS_ERR(icm20608dev.class)) {return PTR_ERR(icm20608dev.class);}/* 4、创建设备 */icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);if (IS_ERR(icm20608dev.device)) {return PTR_ERR(icm20608dev.device);}/*初始化spi_device */spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/spi_setup(spi);icm20608dev.private_data = spi; /* 设置私有数据 *//* 初始化ICM20608内部寄存器 */icm20608_reginit();     return 0;
}/** @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行* @param - client  : i2c设备* @return          : 0,成功;其他负值,失败*/
static int icm20608_remove(struct spi_device *spi)
{/* 删除设备 */cdev_del(&icm20608dev.cdev);unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);/* 注销掉类和设备 */device_destroy(icm20608dev.class, icm20608dev.devid);class_destroy(icm20608dev.class);return 0;
}/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {{"alientek,icm20608", 0},  {}
};/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {{ .compatible = "alientek,icm20608" },{ /* Sentinel */ }
};/* SPI驱动结构体 */
static struct spi_driver icm20608_driver = {.probe = icm20608_probe,.remove = icm20608_remove,.driver = {.owner = THIS_MODULE,.name = "icm20608",.of_match_table = icm20608_of_match, },.id_table = icm20608_id,
};/** @description : 驱动入口函数* @param       : 无* @return      : 无*/
static int __init icm20608_init(void)
{return spi_register_driver(&icm20608_driver);
}/** @description : 驱动出口函数* @param       : 无* @return      : 无*/
static void __exit icm20608_exit(void)
{spi_unregister_driver(&icm20608_driver);
}module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");

icm20608App.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
/***************************************************************使用方法     :./icm20608App /dev/icm20608***************************************************************//** @description     : main主程序* @param - argc    : argv数组元素个数* @param - argv    : 具体参数* @return          : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd;char *filename;signed int databuf[7];unsigned char data[14];signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;signed int accel_x_adc, accel_y_adc, accel_z_adc;signed int temp_adc;float gyro_x_act, gyro_y_act, gyro_z_act;float accel_x_act, accel_y_act, accel_z_act;float temp_act;int ret = 0;if (argc != 2) {printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if(fd < 0) {printf("can't open file %s\r\n", filename);return -1;}while (1) {ret = read(fd, databuf, sizeof(databuf));if(ret == 0) {          /* 数据读取成功 */gyro_x_adc = databuf[0];gyro_y_adc = databuf[1];gyro_z_adc = databuf[2];accel_x_adc = databuf[3];accel_y_adc = databuf[4];accel_z_adc = databuf[5];temp_adc = databuf[6];/* 计算实际值 */gyro_x_act = (float)(gyro_x_adc)  / 16.4;gyro_y_act = (float)(gyro_y_adc)  / 16.4;gyro_z_act = (float)(gyro_z_adc)  / 16.4;accel_x_act = (float)(accel_x_adc) / 2048;accel_y_act = (float)(accel_y_adc) / 2048;accel_z_act = (float)(accel_z_adc) / 2048;temp_act = ((float)(temp_adc) - 25 ) / 326.8 + 25;printf("\r\n原始值:\r\n");printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);printf("temp = %d\r\n", temp_adc);printf("实际值:");printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);printf("act temp = %.2f°C\r\n", temp_act);}usleep(100000); /*100ms */}close(fd);  /* 关闭文件 */  return 0;
}

编译测试 APP

在 icm20608App.c 这个测试 APP 中我们用到了浮点计算,而 I.MX6U 是支持硬件浮点的,因此我们在编译 icm20608App.c 的时候就可以使能硬件浮点,这样可以加速浮点计算。使能硬件浮点很简单,在编译的时候加入如下参数即可:

-march-armv7-a -mfpu-neon -mfloat=hard

输入如下命令使能硬件浮点编译 icm20608App.c 这个测试程序:

arm-linux-gnueabihf-gcc   -march=armv7-a   -mfpu=neon   -mfloat-abi=hard   icm20608App.c   -o
icm20608App

编译成功以后就会生成 icm20608App 这个应用程序,那么究竟有没有使用硬件浮点呢?使
用 arm-linux-gnueabihf-readelf 查看一下编译出来的 icm20608App 就知道了,输入如下命令:

arm-linux-gnueabihf-readelf -A icm20608App

可以看出 FPU 架构为 VFPv3, SIMD 使用了 NEON,并且使用了 SP 和 DP,说明 icm20608App 这个应用程序使用了硬件浮点。

struct spi_device {/* 设备模型表示 */struct device        dev;/* SPI控制器 */struct spi_master    *master;/* 最大时钟频率 */u32            max_speed_hz;/* 芯片选择 */u8            chip_select;/* 每个字的位数 */u8            bits_per_word;/* SPI模式 */u16            mode;
#define    SPI_CPHA    0x01            /* 时钟相位 */
#define    SPI_CPOL    0x02            /* 时钟极性 */
#define    SPI_MODE_0    (0|0)            /* (原始MicroWire) */
#define    SPI_MODE_1    (0|SPI_CPHA)
#define    SPI_MODE_2    (SPI_CPOL|0)
#define    SPI_MODE_3    (SPI_CPOL|SPI_CPHA)
#define    SPI_CS_HIGH    0x04            /* 片选高电平有效 */
#define    SPI_LSB_FIRST    0x08            /* 每个字的位在传输线上的顺序 */
#define    SPI_3WIRE    0x10            /* SI/SO信号共享 */
#define    SPI_LOOP    0x20            /* 回环模式 */
#define    SPI_NO_CS    0x40            /* 1个设备/总线,没有片选 */
#define    SPI_READY    0x80            /* 从机拉低以暂停 */
#define    SPI_TX_DUAL    0x100            /* 用2根线传输 */
#define    SPI_TX_QUAD    0x200            /* 用4根线传输 */
#define    SPI_RX_DUAL    0x400            /* 用2根线接收 */
#define    SPI_RX_QUAD    0x800            /* 用4根线接收 *//* 中断号 */int            irq;/* 控制器的运行时状态 */void            *controller_state;/* 控制器的板级定义,例如FIFO初始化参数;来自board_info.controller_data */void            *controller_data;/* 驱动程序名称或别名 */char            modalias[SPI_NAME_SIZE];/* 片选GPIO号 */int            cs_gpio;    /** 可能需要更多的钩子来处理更多的协议选项,影响控制器与每个芯片的通信方式,例如:*  - 内存打包(12位样本打包到低位,其他位置为零)*  - 优先级*  - 每个字后放弃片选*  - 片选延迟*  - ...*/
};

总线驱动--SPI驱动相关推荐

  1. [转载]Linux驱动-SPI驱动 之二:SPI通用接口层

    通过上一篇文章的介绍,我们知道,SPI通用接口层用于把具体SPI设备的协议驱动和SPI控制器驱动联接在一起,通用接口层除了为协议驱动和控制器驱动提供一系列的标准接口API,同时还为这些接口API定义了 ...

  2. 2:STM32CubeMX配置STM32F103C8T6驱动-SPI驱动

    STM32CubeMX下载地址: 下载界面 1:时钟 CS 2:模块 3:代码配置 u8 SPI_WriteByte(u8 Byte) {u8 rxdata = 1;HAL_GPIO_WritePin ...

  3. 4412 SPI驱动

    1.Linux主机驱动和外设驱动分离思想(I2C驱动里有) SPI驱动总线架构:SPI核心层(x),SPI控制器驱动层(x),SPI设备驱动层(√).前面两个设备驱动搞明白了可以去看 2.教程中介绍: ...

  4. platform总线(Linux驱动开发篇)

    简单介绍platform驱动中的led驱动,input设备驱动,i2c驱动,spi驱动 1.Platform led驱动 最简单的了解platform平台的例子,可以理解为3部分,由驱动层,系统核心层 ...

  5. Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化

    我们知道,SPI数据传输可以有两种方式:同步方式和异步方式.所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返 ...

  6. Linux SPI总线和设备驱动架构之三:SPI控制器驱动

    通过第一篇文章,我们已经知道,整个SPI驱动架构可以分为协议驱动.通用接口层和控制器驱动三大部分.其中,控制器驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能: 1 ...

  7. Linux SPI总线和设备驱动架构之二:SPI通用接口层

    通过上一篇文章的介绍,我们知道,SPI通用接口层用于把具体SPI设备的协议驱动和SPI控制器驱动联接在一起,通用接口层除了为协议驱动和控制器驱动提供一系列的标准接口API,同时还为这些接口API定义了 ...

  8. Linux SPI总线和设备驱动架构之一:系统概述

    SPI是"Serial Peripheral Interface" 的缩写,是一种四线制的同步串行通信接口,用来连接微控制器.传感器.存储设备,SPI设备分为主设备和从设备两种,用 ...

  9. stm32h7高速通信_【STM32H7教程】第75章 STM32H7的SPI总线应用之驱动DAC8501(双路输出,16bit分辨率,0-5V)...

    第75章       STM32H7的SPI总线应用之驱动DAC8501(双路输出,16bit分辨率,0-5V) 本章节为大家讲解标准SPI接线方式驱动模数转换器DAC8501,制作了中断和DMA两种 ...

最新文章

  1. 4G EPS 中的小区选择
  2. SHA256CryptoServiceProvider vs SHA256Managed
  3. osgi实战学习之路:6. Service-1
  4. c++ 多key_详解Zabbix自定义Key监控Rabbitmq(监控特定队列)
  5. Ping Function
  6. 第一次接广告的心得,关于广告我怎么看
  7. Graph QL和SAP Graph的区别
  8. 鹤峰:美丽的茶乡—— 山水篇
  9. 微软私有云分享(R2)22 计算机配置文件与基础设置
  10. linux下mtr命令,如何使用Linux mtr命令
  11. mysql查询20-30的数据_20. mysql查询表的数据大小
  12. Magento教程 8:如何新增首页选单?
  13. IPMI从驱动到应用(下篇 )
  14. Tomcat(介绍,JDK安装,Tomcat安装,配置Tomcat监听80端口)
  15. python基于情感词典的情感分析
  16. java代码 软件_适合新手的java代码编写软件有哪些?
  17. java 图片合并成pdf_Java多张图片合成PDF
  18. 万能五笔输入法弹窗_万能五笔输入法广告怎么去掉
  19. Magick.NET Crack,支持多种文件格式的综合库
  20. 小程序云开发 免费版 云函数数量超过限制 怎么办

热门文章

  1. win7网络发现不能启用解决方法
  2. 单利 java_java 单利模式
  3. Python自动化——pytest常用插件详解
  4. Python破解WiFi密码Demo
  5. deepinlinux下C语言穷举解决9宫格填数字
  6. 如何查看手机信息隐藏代码
  7. eXpress Persistent Objects (XPO)入门:数据查询
  8. 镇江SEO推广:内容页收录很少,怎么办?
  9. Ubuntu8.10 DVD的下载和Mac OSX Leopard的模拟
  10. 咸鱼ZTMR实例—自动浇花器