总线驱动--SPI驱动

总线驱动–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 dev


ice_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

总线驱动--SPI驱动_第1张图片

可以看出 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位样本打包到低位,其他位置为零)
     *  - 优先级
     *  - 每个字后放弃片选
     *  - 片选延迟
     *  - ...
     */
};

你可能感兴趣的:(Linux驱动V2.0,驱动开发,#,总线驱动,linux,arm开发,驱动开发,C语言,嵌入式)