imx6ull-驱动开发篇45——Linux 下 SPI 驱动框架简介
目录
SPI 主机驱动
spi_master 结构体
spi_master 申请与释放
spi_master 的注册与注销
SPI 设备驱动
spi_driver 结构体
spi_driver注册与注销
SPI 设备和驱动匹配过程
spi_bus_type结构体
spi_match_device函数
SPI 驱动框架和 I2C 很类似,都分为主机控制器驱动和设备驱动,主机控制器也就是 SOC的 SPI 控制器接口。
SPI 主机驱动
spi_master 结构体
Linux 内核使用 spi_master 表示 SPI 主机驱动, spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件中。
spi_master 结构体内容如下(有缩减):
struct spi_master {struct device dev; // 内嵌的设备结构体,用于设备模型管理struct list_head list; // 链表节点,用于将主控制器连接到全局链表......s16 bus_num; // SPI总线编号(如spi0、spi1等)/* 片选信号配置:许多控制器内置片选,有些使用板级GPIO */u16 num_chipselect; // 支持的片选信号数量/* DMA对齐要求:有些SPI控制器对DMA缓冲区有对齐要求 */u16 dma_alignment; // DMA缓冲区所需的最小对齐字节数/* 控制器支持的SPI模式标志 */u16 mode_bits; // 支持的SPI模式(SPI_CPOL、SPI_CPHA等)/* 支持的传输字长位掩码 */u32 bits_per_word_mask; // 例如BIT(8) | BIT(16)表示支持8位和16位传输....../* 传输速度限制 */u32 min_speed_hz; // 最小传输频率(Hz)u32 max_speed_hz; // 最大传输频率(Hz)/* 驱动相关的其他约束标志 */u16 flags; // 附加功能标志位....../* SPI总线锁机制:用于独占访问控制 */spinlock_t bus_lock_spinlock; // 自旋锁用于中断上下文保护struct mutex bus_lock_mutex; // 互斥锁用于进程上下文保护/* 标志指示SPI总线是否被独占锁定 */bool bus_lock_flag; // true表示总线已被锁定....../* 设置SPI设备参数的回调函数 */int (*setup)(struct spi_device *spi); // 配置设备模式、速度等参数....../* 核心传输函数(异步) */int (*transfer)(struct spi_device *spi, struct spi_message *mesg);....../* 单消息传输函数(同步/异步实现) */int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);......
};
其中,transfer 函数,控制器数据传输函数。
/* 核心传输函数(异步) */int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message, SPI 的数据会打包成 spi_message,然后以队列方式发送出去。
/* 单消息传输函数(同步/异步实现) */int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg);
SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册spi_master。
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 主机驱动的时候,就需要释放掉前面申请的 spi_master, 释放函数spi_master_put 函数原型如下:
void spi_master_put(struct spi_master *master)
- master:要释放的 spi_master。
spi_master 的注册与注销
当 spi_master 初始化完成以后,就需要将其注册到 Linux 内核。
spi_master 注册函数为spi_register_master,函数原型如下:
int spi_register_master(struct spi_master *master)
- master:要注册的 spi_master。
- 返回值: 0,成功;负值,失败。
如果要注销 spi_master 的话,使用 spi_unregister_master 函数,此函数原型为:
void spi_unregister_master(struct spi_master *master)
- master:要注销的 spi_master。
I.MX6U 的 SPI 主机驱动会采用 spi_bitbang_start 这个 API 函数来完成 spi_master 的注册, spi_bitbang_start 函数内部,其实也是通过调用 spi_register_master 函数来完成注册。
如果使用 spi_bitbang_start 注册 spi_master 的话,就要使用 spi_bitbang_stop 来注销掉spi_master。
SPI 设备驱动
spi_driver 结构体
Linux 内核使用 spi_driver 结构体来表示 spi 设备驱动,我们在编写 SPI 设备驱动的时候需要实现 spi_driver 。
spi_driver 结构体定义在include/linux/spi/spi.h 文件中,结构体内容如下:
struct spi_driver {const struct spi_device_id *id_table; // 设备ID匹配表,用于驱动与SPI设备的匹配int (*probe)(struct spi_device *spi); // 设备探测函数(必须实现)int (*remove)(struct spi_device *spi); // 设备移除函数(必须实现)void (*shutdown)(struct spi_device *spi); // 设备关机回调函数(可选)struct device_driver driver; // 内嵌的设备驱动结构体
};
当 SPI 设备和驱动匹配成功以后, probe 函数就会执行。
spi_driver注册与注销
spi_driver 初始化完成以后需要向 Linux 内核注册, spi_driver 注册函数为spi_register_driver。
spi_register_driver函数原型如下:
int spi_register_driver(struct spi_driver *sdrv)
- sdrv: 要注册的 spi_driver。
- 返回值: 0,注册成功;赋值,注册失败。
注销 SPI 设备驱动以后,也需要注销掉前面注册的 spi_driver,使用 spi_unregister_driver 函数完成 spi_driver 的注销。
spi_unregister_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);
SPI 设备和驱动匹配过程
spi_bus_type结构体
SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,SPI总线为 spi_bus_type,定义在 drivers/spi/spi.c 文件中。
spi_bus_type结构体内容如下:
struct bus_type spi_bus_type = {.name = "spi", // 总线名称,出现在/sys/bus/spi.dev_groups = spi_dev_groups, // SPI设备默认的sysfs属性组.match = spi_match_device, // 设备与驱动匹配的关键函数.uevent = spi_uevent, // 生成用户空间事件(如udev事件)
};
其中,SPI 设备和驱动的匹配函数为 spi_match_device。
spi_match_device函数
spi_match_device函数内容如下:
static int spi_match_device(struct device *dev, struct device_driver *drv)
{// 1. 转换设备类型const struct spi_device *spi = to_spi_device(dev); // 转换为SPI设备结构体const struct spi_driver *sdrv = to_spi_driver(drv); // 转换为SPI驱动结构体/* 2. 优先尝试设备树(OF)匹配 */if (of_driver_match_device(dev, drv))return 1; // 设备树compatible属性匹配成功/* 3. 尝试ACPI匹配 */if (acpi_driver_match_device(dev, drv))return 1; // ACPI ID匹配成功/* 4. ID表匹配(传统方式) */if (sdrv->id_table)return !!spi_match_id(sdrv->id_table, spi); // 检查驱动id_table是否包含设备/* 5. 最后回退到名称匹配 */return strcmp(spi->modalias, drv->name) == 0; // 比较设备modalias和驱动名称
}
- 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 成员变量是否相等。