目录
- 一、回顾平台总线设备驱动模型
- 二、SPI设备驱动
- 1. 数据结构
- 1.1 SPI控制器数据结构
- 1.2 SPI设备数据结构
- 1.3 SPI设备驱动
- 2. SPI驱动框架
- 2.1 SPI控制器驱动程序
- 2.2 SPI设备驱动程序
- 三、SPI设备树处理过程
- 1. SPI Master
- 2. SPI Device
- 3. 设备树示例
- 4. 设备树实例
- 4.1 使用GPIO模拟的SPI控制器
- 4.2 IMX6ULL SPI控制器
- 4.3 RK3588S的 SPI 控制器
- 5. 设备树处理过程
- 6. 设备树部分知识补充
- `#address-cells`
- `#size-cells`
- 当 `#size-cells` 为0时
一、回顾平台总线设备驱动模型
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时
-
隐含大小:
- 如果
#size-cells
为0,表示该节点的资源大小信息在设备树中不被显式地描述。系统可能会根据其他上下文信息来确定资源的大小。
- 如果
-
资源不需要大小:
- 这种情况通常适用于那些没有明确的大小要求的节点,例如某些配置或控制寄存器,它们可能只需要地址,而不需要明确的大小信息。
假设有一个设备树的片段,其中 #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
,而没有大小字段。这可能表示这个设备或寄存器只需要一个地址,而不涉及具体的大小。
典型应用场景
-
寄存器映射:
- 一些硬件寄存器映射中,寄存器的地址可能是唯一的,没有固定的区域大小。对于这些寄存器,只需要提供地址即可。
-
配置节点:
- 某些配置节点(如总线、控制器等)可能不需要大小信息,因为它们的资源配置是动态的或者由其他方式确定。
本文章参考了韦东山老师驱动大全部分笔记,其余内容为自己整理总结而来。水平有限,欢迎各位在评论区指导交流!!!😁😁😁