总线驱动--SPI驱动(上)

本文涉及的产品
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
数据传输服务 DTS,数据同步 1个月
简介: 总线驱动--SPI驱动

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,
};
}


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
目录
相关文章
|
7月前
嵌入式开发板串口驱动框架
嵌入式开发板串口驱动框架
72 0
|
5月前
|
异构计算 内存技术
FPGA进阶(1):基于SPI协议的Flash驱动控制(二)
FPGA进阶(1):基于SPI协议的Flash驱动控制
65 0
|
5月前
|
异构计算 内存技术
FPGA进阶(1):基于SPI协议的Flash驱动控制(一)
FPGA进阶(1):基于SPI协议的Flash驱动控制(一)
211 0
|
Linux 程序员 容器
总线,设备,驱动与class(二)
总线,设备,驱动与class
110 1
|
7月前
|
Perl
【ZYNQ】SPI 简介及 EMIO 模拟 SPI 驱动示例
【ZYNQ】SPI 简介及 EMIO 模拟 SPI 驱动示例
680 0
|
Linux 容器
总线,设备,驱动与class(三)
总线,设备,驱动与class(三)
110 1
|
Linux
总线驱动--SPI驱动(下)
总线驱动--SPI驱动
199 0
|
传感器 Linux
总线驱动---IIC驱动(下)
总线驱动---IIC驱动
100 0
|
传感器 算法 Linux
总线驱动---IIC驱动(上)
总线驱动---IIC驱动
168 0
|
Linux 程序员 Shell
总线,设备,驱动与class(一)
总线,设备,驱动与class
135 0

热门文章

最新文章