首页 > 其他分享 >SPI驱动学习二(驱动框架)

SPI驱动学习二(驱动框架)

时间:2024-09-01 22:56:58浏览次数:5  
标签:struct 框架 SPI spi device 驱动 驱动程序 设备

目录

一、回顾平台总线设备驱动模型

Linux驱动程序开始基于"平台总线设备驱动"模型,把驱动程序分成2边:

  • 左边注册一个platform_driver结构体,里面是比较固定的、通用的代码
  • 右边注册一个platform_device结构体,里面是硬件资源
    • 可以在C文件中注册platform_device;
    • 也可以使用设备树创建一个节点,内核解析设备树时注册platform_device;
      在这里插入图片描述

二、SPI设备驱动

1. 数据结构

  SPI子系统中涉及2类硬件:SPI控制器SPI设备
在这里插入图片描述

SPI控制器有驱动程序,提供SPI的传输能力。
SPI设备也有自己的驱动程序,提供SPI设备的访问能力:

  • 它知道怎么访问这个设备,它知道这个设备的数据含义是什么
  • 它会调用SPI控制器的函数来收发数据。

1.1 SPI控制器数据结构

  参考内核文件:include\linux\spi\spi.h, Linux中使用spi_master结构体描述SPI控制器,里面最重要的成员就是transfer函数指针:

// 定义SPI控制器的结构体
struct spi_controller {
    // 设备结构体
    struct device dev;

    // 列表头,用于链接多个SPI控制器
    struct list_head list;

    // 板载SPI总线编号,通常由SOC和板级电路决定
    s16 bus_num;

    // 芯片选择数量,与控制器硬件相关
    u16 num_chipselect;

    // DMA缓冲区对齐要求,某些SPI控制器有特定要求
    u16 dma_alignment;

    // 该控制器驱动理解的spi_device.mode标志
    u32 mode_bits;

    // 该控制器上覆盖spi_device.mode标志的标志
    u32 buswidth_override_bits;

    // 支持的bits_per_word范围的位掩码
    u32 bits_per_word_mask;

    // 转移速度的最小值和最大值
    u32 min_speed_hz;
    u32 max_speed_hz;

    // 与该驱动相关的其他约束
    u16 flags;

    // 表示这是一个SPI从控制器
    bool slave;

    // 计算最大传输和消息大小的函数指针
    size_t (*max_transfer_size)(struct spi_device *spi);
    size_t (*max_message_size)(struct spi_device *spi);

    // I/O互斥锁
    struct mutex io_mutex;

    // SPI总线锁定的自旋锁和互斥锁
    spinlock_t bus_lock_spinlock;
    struct mutex bus_lock_mutex;

    // 表示SPI总线被锁定以供专用的标志
    bool bus_lock_flag;

    // 设置模式、时钟等的函数(驱动可能会多次调用)
    int (*setup)(struct spi_device *spi);

    // 配置CS定时的函数
    int (*set_cs_timing)(struct spi_device *spi, struct spi_delay *setup,
                         struct spi_delay *hold, struct spi_delay *inactive);

    // 执行SPI数据传输的函数
    int (*transfer)(struct spi_device *spi, struct spi_message *mesg);

    // 在释放时调用以释放spi_controller分配的内存
    void (*cleanup)(struct spi_device *spi);

    // 判断是否支持DMA的函数
    bool (*can_dma)(struct spi_controller *ctlr, struct spi_device *spi,
                    struct spi_transfer *xfer);

    // 用于驱动希望使用通用控制器消息队列机制的钩子
    bool queued;
    struct kthread_worker *kworker;
    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;
    bool last_cs_enable;
    bool last_cs_mode_high;
    bool fallback;
    struct completion xfer_completion;
    size_t max_dma_len;

    // 用于准备、传输和清理硬件的函数指针
    int (*prepare_transfer_hardware)(struct spi_controller *ctlr);
    int (*transfer_one_message)(struct spi_controller *ctlr,
                                struct spi_message *mesg);
    int (*unprepare_transfer_hardware)(struct spi_controller *ctlr);
    int (*prepare_message)(struct spi_controller *ctlr,
                           struct spi_message *message);
    int (*unprepare_message)(struct spi_controller *ctlr,
                             struct spi_message *message);
    int (*slave_abort)(struct spi_controller *ctlr);

    // 用于使用核心提供的通用transfer_one_message()实现的驱动的钩子
    void (*set_cs)(struct spi_device *spi, bool enable);
    int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi,
                        struct spi_transfer *transfer);
    void (*handle_err)(struct spi_controller *ctlr,
                       struct spi_message *message);

    // 用于SPI内存操作的优化处理程序
    const struct spi_controller_mem_ops *mem_ops;

    // CS延迟
    struct spi_delay cs_setup;
    struct spi_delay cs_hold;
    struct spi_delay cs_inactive;

    // GPIO芯片选择
    int *cs_gpios;
    struct gpio_desc **cs_gpiods;
    bool use_gpio_descriptors;

    // 用于POSIX时钟读取的快照SPI传输支持
    bool ptp_sts_supported;

    // PTP系统时间戳期间的中断使能状态
    unsigned long irq_flags;

    // Android KABI保留字段
    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
};

/* Compatibility layer */
#define spi_master			spi_controller
/**
 * 结构体 spi_controller - SPI主控制器或从控制器的接口
 * @dev: 此驱动程序的设备接口
 * @list: 与全局spi_controller列表链接
 * @bus_num: 板级特定(通常是SOC特定)标识符,用于给定的SPI控制器。
 * @num_chipselect: 芯片选择用于区分个别SPI从设备,编号从零到num_chipselect。
 * 每个从设备都有一个芯片选择信号,但通常不是每个芯片选择都连接到一个从设备。
 * @dma_alignment: SPI控制器对DMA缓冲区对齐的约束。
 * @mode_bits: 此控制器驱动程序理解的标志
 * @buswidth_override_bits: 此控制器驱动程序要覆盖的标志
 * @bits_per_word_mask: 掩码,指示哪些bits_per_word值由驱动程序支持。
 * 位n表示支持bits_per_word n+1。如果设置了,SPI核心将拒绝任何具有不支持的bits_per_word的传输。
 * 如果没有设置,这个值就会被忽略,由个别驱动程序来执行任何验证。
 * @min_speed_hz: 支持的最低传输速度
 * @max_speed_hz: 支持的最高传输速度
 * @flags: 与此驱动程序相关的其他约束
 * @slave: 表示这是一个SPI从控制器
 * @max_transfer_size: 函数,返回&spi_device的最大传输大小;可以是%NULL,因此将使用默认的%SIZE_MAX。
 * @max_message_size: 函数,返回&spi_device的最大消息大小;可以是%NULL,因此将使用默认的%SIZE_MAX。
 * @io_mutex: 用于物理总线访问的互斥锁
 * @bus_lock_spinlock: SPI总线锁定的自旋锁
 * @bus_lock_mutex: 用于排除多个调用者的互斥锁
 * @bus_lock_flag: 指示SPI总线被锁定用于独占使用的标志
 * @setup: 更新设备模式和时钟记录,这些记录由设备SPI控制器使用;协议代码可能会调用这个。
 * 这必须在请求不被识别或不支持的模式时失败。除非传输在设备上挂起,否则总是可以调用这个。
 * @set_cs_timing: SPI设备可选的钩子,用于请求SPI主控制器配置特定的CS设置时间、保持时间和非活动延迟,以时钟计数为单位
 * @transfer: 将消息添加到控制器的传输队列。
 * @cleanup: 释放控制器特定状态
 * @can_dma: 确定此控制器是否支持DMA
 * @queued: 是否此控制器提供内部消息队列
 * @kworker: 指向消息泵线程结构的指针
 * @pump_messages: 用于调度工作到消息泵的工作结构
 * @queue_lock: 用于同步访问消息队列的自旋锁
 * @queue: 消息队列
 * @idling: 设备正在进入空闲状态
 * @cur_msg: 当前正在传输的消息
 * @cur_msg_prepared: 已为当前正在传输的消息调用spi_prepare_message
 * @cur_msg_mapped: 消息已映射用于DMA
 * @last_cs_enable: 上次调用set_cs时enable是否为真
 * @last_cs_mode_high: 上次调用set_cs时(mode & SPI_CS_HIGH)是否为真
 * @xfer_completion: 由核心transfer_one_message()使用
 * @busy: 消息泵正忙
 * @running: 消息泵正在运行
 * @rt: 此队列是否设置为作为实时任务运行
 * @auto_runtime_pm: 核心应在硬件准备时确保持有运行时PM引用,使用spidev的父设备
 * @max_dma_len: 设备的DMA传输的最大长度。
 * @prepare_transfer_hardware: 消息将很快从队列到达,因此子系统请求驱动程序通过发出此调用来准备传输硬件
 * @transfer_one_message: 子系统调用驱动程序传输单个消息,同时排队传输同时到达的消息。当驱动程序完成此消息时,它必须调用spi_finalize_current_message(),以便子系统可以发出下一条消息
 * @unprepare_transfer_hardware: 队列上当前没有更多消息,因此子系统通知驱动程序,它可以通过发出此调用来放松硬件
 *
 * @set_cs: 设置芯片选择线的逻辑电平。可能从中断上下文调用。
 * @prepare_message: 为单个消息设置控制器,例如进行DMA映射。从线程上下文调用。
 * @transfer_one: 传输单个spi_transfer。
 *
 *                  - 如果传输完成,返回0,
 *                  - 如果传输仍在进行中,返回1。当驱动程序完成此传输时,它必须调用spi_finalize_current_transfer(),以便子系统可以发出下一个传输。注意:transfer_one和transfer_one_message是互斥的;当两者都设置时,通用子系统不会调用你的transfer_one回调。
 * @handle_err: 子系统调用驱动程序处理在transfer_one_message()的通用实现中发生的错误。
 * @mem_ops: 与SPI内存交互的优化/专用操作。
 * @unprepare_message: 撤销prepare_message()所做的工作。
 * @slave_abort: 中断SPI从控制器上正在进行的传输请求
 * @cs_setup: 控制器在CS被断言后引入的延迟
 * @cs_hold: 控制器在CS被取消断言前引入的延迟
 * @cs_inactive: 控制器在CS被取消断言后引入的延迟。如果@spi_transfer中的@cs_change_delay被使用,则这两个延迟将被累加。
 * @cs_gpios: 过时:用作芯片选择线的GPIO描述符数组;每个CS一个。任何个别值可以是-ENOENT,对于不是GPIO的CS线(由SPI控制器本身驱动)。在新驱动程序中使用cs_gpiods。
 * @cs_gpiods: 用作芯片选择线的GPIO描述符数组;每个CS一个。任何个别值可以是NULL,对于不是GPIO的CS线(由SPI控制器本身驱动)。
 * @use_gpio_descriptors: 打开SPI核心代码以解析和抓取GPIO描述符,而不是使用全局GPIO编号。这将填充@cs_gpiods,不应再使用@cs_gpiods,SPI设备将被分配cs_gpiod而不是cs_gpio。
 * @unused_native_cs: 当使用cs_gpiods时,spi_register_controller()将用第一个未使用的原生CS填充此字段,供需要在使用GPIO CS时驱动原生CS的SPI控制器驱动程序使用。
 * @max_native_cs: 当使用cs_gpiods,并且此字段被填充时,spi_register_controller()将验证所有原生CS(包括未使用的原生CS)是否符合此值。
 * @statistics: spi_controller的统计信息
 * @dma_tx: DMA传输通道
 * @dma_rx: DMA接收通道
 * @dummy_rx: 全双工设备的虚拟接收缓冲区
 * @dummy_tx: 全双工设备的虚拟传输缓冲区
 * @fw_translate_cs: 如果引导固件使用与Linux预期不同的编号方案,此可选钩子可用于两者之间的转换。
 * @ptp_sts_supported: 如果驱动程序将此设置为true,它必须在尽可能接近传输@spi_transfer->ptp_sts_word_pre和@spi_transfer->ptp_sts_word_post的时刻提供时间快照。
 * 如果驱动程序没有设置这个,SPI核心将尽可能接近驱动程序交接时获取快照。
 * @irq_flags: 在PTP系统时间戳期间中断使能状态
 * @fallback: 如果DMA传输失败并返回SPI_TRANS_FAIL_NO_START,则回退到pio。
 *
 * 每个SPI控制器可以与一个或多个@spi_device子设备通信。这些构成了一个小总线,共享MOSI、MISO和SCK信号,但不共享芯片选择信号。
 * 每个设备可以配置为使用不同的时钟速率,因为这些共享信号在芯片未被选中时会被忽略。
 *
 * SPI控制器的驱动程序通过spi_message事务的队列管理对这些设备的访问,将数据在CPU内存和SPI从设备之间复制。
 * 对于它排队的每个这样的消息,它在事务完成后调用消息的完成函数。
 */
// 双向批量传输
// 
// + transfer() 方法本身不会休眠,其主要作用是将消息添加到队列中。
// + 目前没有从队列中移除消息的操作,也没有其他任何请求管理功能。
// + 对于一个给定的 spi_device,消息队列是纯先进先出(FIFO)的。
// 
// + 控制器的主要工作是处理其消息队列,选择一个芯片(对于主设备),然后传输数据。
// + 如果有多个 spi_device 子设备,I/O队列的仲裁算法是未指定的(轮询、FIFO、优先级、预留、抢占等)。
// 
// + 在整个消息传输过程中,Chipselect 保持激活状态(除非被 spi_transfer.cs_change != 0 修改)。
// + 消息传输使用之前通过 setup() 为这个设备建立的时钟和 SPI 模式参数。
int (*transfer)(struct spi_device *spi, struct spi_message *mesg);

1.2 SPI设备数据结构

  参考内核文件:include\linux\spi\spi.h, Linux中使用spi_device结构体描述SPI设备,里面记录有设备的片选引脚、频率、挂在哪个SPI控制器下面:

/**
 * 结构体 spi_device - SPI从设备的控制器端代理
 * @dev: 设备的驱动模型表示。
 * @controller: 与设备一起使用的SPI控制器。
 * @master: 控制器的副本,用于向后兼容。
 * @max_speed_hz: 与此芯片(在此板上)一起使用的最大时钟速率;
 * 可以由设备的驱动程序更改。
 * spi_transfer.speed_hz 可以覆盖每次传输的这个值。
 * @chip_select: 芯片选择,区分由 @controller 处理的芯片。
 * @mode: SPI模式定义了数据的时钟输出和输入方式。
 * 这可以由设备的驱动程序更改。
 * 芯片选择模式的"低电平有效"默认值可以通过指定 SPI_CS_HIGH 来覆盖;
 * 同样,传输中每个字的"高位优先"默认值也可以通过指定 SPI_LSB_FIRST 来覆盖。
 * @bits_per_word: 数据传输涉及一个或多个字;常见的字大小如八位或十二位。
 * 内存中的字大小是二的幂字节(例如,20位样本使用32位)。
 * 这可以由设备的驱动程序更改,或者保留为默认值(0),表示协议字是八位字节。
 * spi_transfer.bits_per_word 可以覆盖每次传输的这个值。
 * @rt: 使泵线程具有实时优先级。
 * @irq: 负值,或传递给 request_irq() 的数字,用于从该设备接收中断。
 * @controller_state: 控制器的运行时状态
 * @controller_data: 控制器的板级特定定义,例如 FIFO 初始化参数;来自 board_info.controller_data
 * @modalias: 与此设备一起使用的驱动程序名称,或该名称的别名。
 * 这出现在 sysfs "modalias" 属性中,用于驱动程序的冷插拔,以及用于热插拔的 uevents 中
 * @driver_override: 如果驱动程序的名称写入此属性,则设备将绑定到指定的驱动程序,并且只有指定的驱动程序。
 * @cs_gpio: 过时:芯片选择线的 GPIO 编号(可选,不使用 GPIO 线时为 -ENOENT),
 * 在新的驱动程序中通过选择 spi_master 使用 cs_gpiod。
 * @cs_gpiod: 芯片选择线的 GPIO 描述符(可选,不使用 GPIO 线时为 NULL)
 * @word_delay: 在传输中连续字之间插入的延迟
 *
 * @statistics: spi_device 的统计信息
 *
 * @spi_device 用于在 SPI 从设备(通常是离散芯片)和 CPU 内存之间交换数据。
 *
 * 在 @dev 中,platform_data 用于保存对设备协议驱动程序有意义但对其控制器无意义的关于此设备的信息。
 * 一个例子可能是具有略有不同功能的不同芯片变体的标识符;另一个可能是关于此特定板上如何连接芯片引脚的信息。
 */
 // spi_device结构体 - 用于表示SPI从设备的控制器端代理
// 该结构体包含了与SPI从设备(通常是独立芯片)和CPU内存之间交换数据所需的信息。
struct spi_device {
    struct device dev; // 设备模型表示
    struct spi_controller *controller; // 使用的SPI控制器
    struct spi_controller *master; // 兼容性层,与controller作用相同
    u32 max_speed_hz; // 与该芯片通信的最大时钟速率(在该板上),可以由设备的驱动程序更改
    u8 chip_select; // 区分由@controller处理的芯片的选择线
    u8 bits_per_word; // 协议单词的位数,默认为8位字节,可以由设备的驱动程序更改
    bool rt; // 将泵线程设置为实时优先级
    u32 mode; // SPI模式,定义了如何时钟输出和输入数据,可以由设备的驱动程序更改
    #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 // 每个单词的位顺序,LSB优先
    #define SPI_3WIRE 0x10 // 共享SI/SO信号线
    #define SPI_LOOP 0x20 // 环回模式
    #define SPI_NO_CS 0x40 // 总线上只有一个设备,没有芯片选择线
    #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根线接收
    #define SPI_CS_WORD 0x1000 // 每个单词之后切换芯片选择
    #define SPI_TX_OCTAL 0x2000 // 使用8根线发送
    #define SPI_RX_OCTAL 0x4000 // 使用8根线接收
    #define SPI_3WIRE_HIZ 0x8000 // 高阻态转换
    int irq; // 中断请求号,接收来自该设备的中断
    void *controller_state; // 控制器的运行时状态
    void *controller_data; // 控制器的板级定义,如FIFO初始化参数
    char modalias[SPI_NAME_SIZE]; // 用于驱动该设备的驱动程序名称,或其别名
    const char *driver_override; // 如果写入驱动程序名称,则设备只绑定到该驱动程序
    int cs_gpio; // 芯片选择GPIO引脚编号(可选,不使用GPIO时设置为-ENOENT)
    struct gpio_desc *cs_gpiod; // 芯片选择GPIO描述符(可选,不使用GPIO时为NULL)
    struct spi_delay word_delay; // 单词间延迟

    // 统计信息
    struct spi_statistics statistics;

    ANDROID_KABI_RESERVE(1); // Android KABI保留字段
    ANDROID_KABI_RESERVE(2);

    /*
     * 可能需要更多的钩子来表示影响控制器与每个芯片通信方式的协议选项,
     * 如:
     *  - 内存打包(12位样本进入低阶位,其他位为零)
     *  - 优先级
     *  - 芯片选择延迟
     *  - ...
     */
};

1.3 SPI设备驱动

  参考内核文件:include\linux\spi\spi.h, Linux中使用spi_driver结构体描述SPI设备驱动:

/**
 * struct spi_driver - SPI协议驱动程序的主机端实现
 * @id_table: 此驱动程序支持的SPI设备列表
 * @probe: 将此驱动程序绑定到SPI设备。驱动程序可以验证设备是否实际存在,
 *    并且可能需要配置在系统设置期间进行初始配置时不需要的特性(例如bits_per_word)。
 * @remove: 将此驱动程序与SPI设备解绑
 * @shutdown: 标准关机回调,用于在系统状态转换期间,例如电源关闭/停止和kexec。
 * @driver: SPI设备驱动程序应初始化此结构的名称和所有者字段。
 *
 * 此结构表示使用SPI消息与硬件进行交互的设备驱动程序,硬件位于SPI链接的另一端。
 * 它被称为“协议”驱动程序,因为它通过消息工作,而不是直接与SPI硬件通信(底层SPI控制器
 * 驱动程序传递这些消息时所做的工作)。这些协议在驱动程序支持的设备规范中定义。
 *
 * 通常,这些设备协议代表驱动程序支持的最低级别接口,驱动程序还将支持更高级别的接口。
 * 这些更高层次的示例包括MTD、网络、MMC、RTC、文件系统字符设备节点和硬件监控等框架。
 */
struct spi_driver {
	const struct spi_device_id *id_table; // 支持的SPI设备ID列表
	int (*probe)(struct spi_device *spi); // 探测设备并进行必要的配置
	int (*remove)(struct spi_device *spi); // 从系统中移除设备时的处理函数
	void (*shutdown)(struct spi_device *spi); // 系统关机时的处理函数
	struct device_driver driver; // 设备驱动程序的通用部分

	ANDROID_KABI_RESERVE(1); // 为Android KABI保留的空间,用于ABI稳定性
};

2. SPI驱动框架

下图请双击放大查看!!!
在这里插入图片描述

2.1 SPI控制器驱动程序

SPI控制器的驱动程序可以基于"平台总线设备驱动"模型来实现:

  • 在设备树里描述SPI控制器的硬件信息,在设备树子节点里描述挂在下面的SPI设备的信息
  • 在platform_driver中提供一个probe函数
    • 它会注册一个spi_master;
    • 还会解析设备树子节点,创建spi_device结构体;
      在这里插入图片描述

2.2 SPI设备驱动程序

跟"平台总线设备驱动模型"类似,Linux中也有一个"SPI总线设备驱动模型":

  • 左边是spi_driver,使用C文件实现,里面有id_table表示能支持哪些SPI设备,有probe函数
  • 右边是spi_device,用来描述SPI设备,比如它的片选引脚、频率
    • 可以来自设备树:比如由SPI控制器驱动程序解析设备树后创建、注册spi_device
    • 可以来自C文件:比如使用spi_register_board_info创建、注册spi_device
      在这里插入图片描述

三、SPI设备树处理过程

参考资料:

  • 内核头文件:include\linux\spi\spi.h
  • 内核文档:Documentation\devicetree\bindings\spi\spi-bus.txt
  • 内核源码:drivers\spi\spi.c
    对于SPI Master,就是SPI控制器,它下面可以连接多个SPI设备。

在设备树里,使用一个节点来表示SPI Master,使用子节点来表示挂在下面的SPI设备。

1. SPI Master

  在设备树中,对于SPI Master,必须的属性如下:

#address-cells:这个SPI Master下的SPI设备,需要多少个cell来表述它的片选引脚
#size-cells:必须设置为0
compatible:根据它找到SPI Master驱动

  可选的属性如下:

cs-gpios:SPI Master可以使用多个GPIO当做片选,可以在这个属性列出那些GPIO
num-cs:片选引脚总数

  其他属性都是驱动程序相关的,不同的SPI Master驱动程序要求的属性可能不一样。

2. SPI Device

  在SPI Master对应的设备树节点下,每一个子节点都对应一个SPI设备,这个SPI设备连接在该SPI Master下面。这些子节点中,必选的属性如下:

compatible:根据它找到SPI Device驱动
reg:用来表示它使用哪个片选引脚
spi-max-frequency:必选,该SPI设备支持的最大SPI时钟

  可选的属性如下:

spi-cpol:这是一个空属性(没有值),表示CPOL为1,即平时SPI时钟为低电平
spi-cpha:这是一个空属性(没有值),表示CPHA为1),即在时钟的第2个边沿采样数据
spi-cs-high:这是一个空属性(没有值),表示片选引脚高电平有效
spi-3wire:这是一个空属性(没有值),表示使用SPI 三线模式
spi-lsb-first:这是一个空属性(没有值),表示使用SPI传输数据时先传输最低位(LSB)
spi-tx-bus-width:表示有几条MOSI引脚;没有这个属性时默认只有1条MOSI引脚
spi-rx-bus-width:表示有几条MISO引脚;没有这个属性时默认只有1条MISO引脚
spi-rx-delay-us:单位是毫秒,表示每次读传输后要延时多久
spi-tx-delay-us:单位是毫秒,表示每次写传输后要延时多久

3. 设备树示例

/**
 * @brief SPI 设备节点配置
 * 
 * 此节点配置了 SPI 控制器及其子设备。
 * 
 * @details
 * - `#address-cells` 和 `#size-cells` 指定了地址和大小单元的数量。
 * - `compatible` 列出了兼容的 SPI 控制器型号。
 * - `reg` 指定了 SPI 控制器的寄存器地址范围。
 * - `interrupts` 和 `interrupt-parent` 指定了中断信息。
 * 
 * 子设备:
 * - `ethernet-switch@0`: 兼容 Micrel KS8995M 以太网交换芯片,最大 SPI 频率 1MHz。
 * - `codec@1`: 兼容 TI TLV320AIC26 音频编解码器,最大 SPI 频率 100kHz。
 */

spi@f00 {
		#address-cells = <1>;
		#size-cells = <0>;
		compatible = "fsl,mpc5200b-spi", "fsl,mpc5200-spi";
		reg = <0xf00 0x20>;
		interrupts = <2 13 0 2 14 0>;
		interrupt-parent = <&mpc5200_pic>;

		ethernet-switch@0 {
			compatible = "micrel,ks8995m";
			spi-max-frequency = <1000000>;
			reg = <0>;
		};

		codec@1 {
			compatible = "ti,tlv320aic26";
			spi-max-frequency = <100000>;
			reg = <1>;
		};
	};

4. 设备树实例

  在设备树里,会有一个节点用来表示SPI控制器。在这个SPI控制器下面,连接有哪些SPI设备?会在设备树里使用子节点来描述SPI设备。

4.1 使用GPIO模拟的SPI控制器

在这里插入图片描述

4.2 IMX6ULL SPI控制器

在这里插入图片描述

4.3 RK3588S的 SPI 控制器

在这里插入图片描述
在这里插入图片描述

5. 设备树处理过程

  内核源码:drivers\spi\spi.c

spi_register_controller
	of_register_spi_devices(ctlr);
		of_register_spi_device(ctlr, nc);
			of_spi_parse_dt(ctlr, spi, nc);
/*
 * 注册 SPI 设备到设备树
 *
 * 此函数的主要职责是从设备树中获取 SPI 设备的相关信息,
 * 并将其注册到系统中。它遵循一套严格的流程,包括分配设备结构体、
 * 解析设备树信息、注册设备等。
 *
 * 参数:
 * - ctlr: 指向 SPI 控制器结构体的指针
 * - nc: 指向设备树节点的指针
 *
 * 返回值:
 * - 成功时返回指向 spi_device 结构体的指针
 * - 失败时返回错误码的指针
 */
static struct spi_device *
of_register_spi_device(struct spi_controller *ctlr, struct device_node *nc)
{
	struct spi_device *spi; // spi_device 结构体指针
	int rc; // 返回代码,用于存储函数执行结果

	/* 分配 spi_device 结构体的空间 */
	spi = spi_alloc_device(ctlr);
	if (!spi) {
		/* 分配失败时输出错误信息并设置错误码 */
		dev_err(&ctlr->dev, "spi_device alloc error for %pOF\n", nc);
		rc = -ENOMEM;
		goto err_out;
	}

	/* 从设备树节点中选择设备驱动并填充 modalias */
	rc = of_modalias_node(nc, spi->modalias,
				sizeof(spi->modalias));
	if (rc < 0) {
		/* 无法找到 modalias 时输出错误信息 */
		dev_err(&ctlr->dev, "cannot find modalias for %pOF\n", nc);
		goto err_out;
	}

	/* 解析设备树中的 SPI 配置信息 */
	rc = of_spi_parse_dt(ctlr, spi, nc);
	if (rc) {
		/* 解析失败时跳转到错误处理块 */
		goto err_out;
	}

	/* 将设备树节点指针存储到设备结构体中 */
	of_node_get(nc);
	spi->dev.of_node = nc;
	spi->dev.fwnode = of_fwnode_handle(nc);

	/* 注册新的 SPI 设备 */
	rc = spi_add_device(spi);
	if (rc) {
		/* 注册失败时输出错误信息 */
		dev_err(&ctlr->dev, "spi_device register error %pOF\n", nc);
		goto err_of_node_put;
	}

	/* 注册成功,返回 spi_device 结构体指针 */
	return spi;

	/* 错误处理块,用于在设备树节点注册失败时释放资源 */
err_of_node_put:
	of_node_put(nc);
	/* 错误处理块,用于在各种错误情况下释放资源并返回错误码的指针 */
err_out:
	spi_dev_put(spi);
	return ERR_PTR(rc);
}
#if defined(CONFIG_OF)
/**
 * 从设备树中解析 SPI 配置信息
 * 
 * 此函数根据设备树节点中的属性来配置 SPI 设备的模式、总线宽度、设备地址和速度
 * 
 * @param ctlr SPI 控制器结构体指针
 * @param spi SPI 设备结构体指针
 * @param nc 设备树节点指针
 * 
 * @return 0 表示成功,非零表示错误代码
 */
static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
			   struct device_node *nc)
{
    u32 value;
    int rc;

    // 根据设备树属性设置 SPI 模式(时钟相位、极性等)
    if (of_property_read_bool(nc, "spi-cpha"))
        spi->mode |= SPI_CPHA;
    if (of_property_read_bool(nc, "spi-cpol"))
        spi->mode |= SPI_CPOL;
    if (of_property_read_bool(nc, "spi-3wire"))
        spi->mode |= SPI_3WIRE;
    if (of_property_read_bool(nc, "spi-lsb-first"))
        spi->mode |= SPI_LSB_FIRST;
    if (of_property_read_bool(nc, "spi-cs-high"))
        spi->mode |= SPI_CS_HIGH;

    // 根据设备树属性设置 SPI 设备的发送总线宽度
    if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {
        switch (value) {
        case 1:
            break;
        case 2:
            spi->mode |= SPI_TX_DUAL;
            break;
        case 4:
            spi->mode |= SPI_TX_QUAD;
            break;
        case 8:
            spi->mode |= SPI_TX_OCTAL;
            break;
        default:
            dev_warn(&ctlr->dev,
                "spi-tx-bus-width %d not supported\n",
                value);
            break;
        }
    }

    // 根据设备树属性设置 SPI 设备的接收总线宽度
    if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {
        switch (value) {
        case 1:
            break;
        case 2:
            spi->mode |= SPI_RX_DUAL;
            break;
        case 4:
            spi->mode |= SPI_RX_QUAD;
            break;
        case 8:
            spi->mode |= SPI_RX_OCTAL;
            break;
        default:
            dev_warn(&ctlr->dev,
                "spi-rx-bus-width %d not supported\n",
                value);
            break;
        }
    }

    // 如果 SPI 控制器是从模式,检查设备树节点名称是否为 'slave'
    if (spi_controller_is_slave(ctlr)) {
        if (!of_node_name_eq(nc, "slave")) {
            dev_err(&ctlr->dev, "%pOF is not called 'slave'\n",
                nc);
            return -EINVAL;
        }
        return 0;
    }

    // 读取并设置 SPI 设备地址
    rc = of_property_read_u32(nc, "reg", &value);
    if (rc) {
        dev_err(&ctlr->dev, "%pOF has no valid 'reg' property (%d)\n",
            nc, rc);
        return rc;
    }
    spi->chip_select = value;

    // 读取并设置 SPI 设备的最大速度
    if (!of_property_read_u32(nc, "spi-max-frequency", &value))
        spi->max_speed_hz = value;

    return 0;
}

6. 设备树部分知识补充

#address-cells

  • 定义#address-cells 是一个属性,用于指定节点的子节点中地址的单元数。它定义了在设备树中地址字段的数量。
  • 作用:告诉系统该节点的地址信息由多少个单元(通常是32位或64位)组成。这对于正确解析和处理节点的地址信息至关重要。
  • 使用场景:例如,在设备树的根节点或总线节点中,通常会定义 #address-cells 属性来描述子节点(如设备节点)的地址格式。

#size-cells

  • 定义#size-cells 是一个属性,用于指定节点的子节点中大小的单元数。它定义了在设备树中大小字段的数量。
  • 作用:告诉系统该节点的大小信息由多少个单元组成。这对于正确解析和处理节点的资源大小信息至关重要。
  • 使用场景#size-cells 通常与 #address-cells 配合使用。在描述内存区域、寄存器区间等时,需要指定资源的大小。

  考虑一个设备树的片段,其中定义了一个总线节点,并且包含两个子节点:一个内存节点和一个设备节点。下面是如何定义 #address-cells#size-cells 的示例:

/ {
    #address-cells = <2>;  // 总线上的地址由2个单元组成
    #size-cells = <1>;     // 总线上的大小由1个单元组成
    
    memory@80000000 {
        reg = <0x80000000 0x40000000>;  // 地址是0x80000000,大小是0x40000000
    };

    device@1 {
        reg = <0x10 0x1000>;  // 地址0x10,大小0x1000
    };
};

在这个例子中:

  • #address-cells = <2> 表示 reg 属性中地址字段由2个单元组成,通常是64位地址。
  • #size-cells = <1> 表示 reg 属性中大小字段由1个单元组成,通常是32位大小。

  在设备树(Device Tree)中,#size-cells 属性的值为0表示子节点中不需要提供大小信息。这通常意味着该节点的资源大小是隐含的,或者不需要明确地在设备树中指定资源的大小。

#size-cells 为0时

  1. 隐含大小

    • 如果 #size-cells 为0,表示该节点的资源大小信息在设备树中不被显式地描述。系统可能会根据其他上下文信息来确定资源的大小。
  2. 资源不需要大小

    • 这种情况通常适用于那些没有明确的大小要求的节点,例如某些配置或控制寄存器,它们可能只需要地址,而不需要明确的大小信息。

  假设有一个设备树的片段,其中 #size-cells 为0,表示这个节点的子节点不需要大小信息:

/ {
    #address-cells = <1>;  // 子节点的地址由1个单元组成
    #size-cells = <0>;     // 子节点不需要大小信息

    simple_device@1000 {
        reg = <0x1000>;  // 只有地址,没有大小
    };
};

在这个例子中:

  • #address-cells = <1> 表示子节点的地址字段由1个单元组成。
  • #size-cells = <0> 表示子节点的 reg 属性中没有大小信息。

解释

  • simple_device@1000 节点只有一个地址字段 0x1000,而没有大小字段。这可能表示这个设备或寄存器只需要一个地址,而不涉及具体的大小。

典型应用场景

  1. 寄存器映射

    • 一些硬件寄存器映射中,寄存器的地址可能是唯一的,没有固定的区域大小。对于这些寄存器,只需要提供地址即可。
  2. 配置节点

    • 某些配置节点(如总线、控制器等)可能不需要大小信息,因为它们的资源配置是动态的或者由其他方式确定。

  本文章参考了韦东山老师驱动大全部分笔记,其余内容为自己整理总结而来。水平有限,欢迎各位在评论区指导交流!!!

标签:struct,框架,SPI,spi,device,驱动,驱动程序,设备
From: https://blog.csdn.net/weixin_45842280/article/details/141788156

相关文章

  • Salt Function Flow:深度研发经验的沉淀,打造轻量级高效流程编排框架
    在开发者的世界里,业务流程编排是一个既复杂又关键的环节。如何高效地管理和编排这些流程,直接影响着系统的性能和可维护性。本次介绍一款基于大量研发实践经验而打造的流程编排框架——SaltFunctionFlow。它不仅轻量、强大,更是将多年实践中的最佳经验沉淀于其中,为开发者提......
  • Java日志框架:Log4j2与SLF4J的比较与选择
    Java日志框架:Log4j2与SLF4J的比较与选择大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!日志记录是Java应用程序中一个重要的功能,它帮助开发者监控应用的运行状态和调试问题。Log4j2和SLF4J是Java中两个广泛使用的日志框架,它们各有特点和优势。本文将......
  • Prism:框架介绍与安装
    Prism:框架介绍与安装Prism:框架介绍与安装什么是Prism?Prism是一个用于在WPF、XamarinForm、Uno平台和WinUI中构建松散耦合、可维护和可测试的XAML应用程序框架Githubhttps://github.com/PrismLibrary/PrismNuGethttps://www.nuget.org/packages/Prism.WpfVS202......
  • Langchain框架中的Agents全解析:类型、工具与自定义实践
    文章目录前言一、什么是Agents?举个栗子......
  • Go入门:gin框架极速搭建图书管理系统
    Go入门:gin框架极速搭建图书管理系统前言本项目适合Golang初学者,通过简单的项目实践来加深对Golang的基本语法和Web开发的理解。项目源码请私信,欢迎前往博主博客torna.top免费查看。项目结构D:.├─go.mod├─go.sum│├─cmd│└─main│......
  • 041.CI4框架CodeIgniter,控制器过滤器Filter的使用
    01、我们在Filters目录,创建一个MyFilter.php文件<?phpnamespaceApp\Filters;useCodeIgniter\Filters\FilterInterface;useCodeIgniter\HTTP\RequestInterface;useCodeIgniter\HTTP\ResponseInterface;classMyFilterimplementsFilterInterface{publicfu......
  • stm32 TIM输出比较(PWM驱动LED呼吸灯&&PWM驱动舵机&&PWM驱动直流电机)
    理论1.输出比较简介OC(OutputCompare)输出比较输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形每个高级定时器和通用定时器都拥有4个输出比较通道高级定时器的前3个通道额外拥有死区生成和互补输出的功......
  • 【ADXL373、ADXL372】超低功耗加速度计的驱动代码测试
     一、概述    前言:基于对大G值加速度传感计的开发需求,我先后接触了ADXL375、ADXL373、ADXL372,其中ADXL375的示例代码比较丰富,另外两个相对较少,所以我后续就根据数据手册对ADXL373的驱动代码进行了编写(ADXL372的寄存器和ADXL373相似度极高),最终完成了对两种芯片的驱动......
  • Java 入门指南:Java 并发编程 —— AQS、AQLS、AOS 锁与同步器的框架
    AQSAQS是AbstractQueuedSynchronizer的缩写,即抽象队列同步器,是Java.util.concurrent中的一个基础工具类,用于实现同步器(Synchronizer)的开发。AQS提供了一种实现锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的同步器,使得开发者能够更方便地编写线程安全的......
  • 基于yolov10的学生课堂行为检测系统,支持图像检测,也支持视频和摄像实时检测(pytorch框架
       更多目标检测和图像分类识别项目可看我主页其他文章功能演示:基于yolov10的学生课堂行为检测系统,支持图像、视频和摄像实时检测【pytorch框架、python】_哔哩哔哩_bilibili(一)简介基于yolov10的学生课堂行为检测系统是在pytorch框架下实现的,这是一个完整的项目,包括代码......