当前位置: 首页 > news >正文

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 成员变量是否相等。
http://www.xdnf.cn/news/1374841.html

相关文章:

  • d435i相机读取镜头内参和相对之间的外参
  • 艾体宝新闻 | 98%好评率!KnowBe4 连续5年蝉联第一,现开放免费钓鱼测试等你解锁
  • 内网应用如何实现外网访问?外地通过公网地址访问内网服务器的设置方法
  • 遗传算法:模拟自然选择的优化智慧
  • Spring Boot项目集成日志系统使用完整指南
  • 欧洲数字化养殖平台 Herdwatch 借力 Iceberg + StarRocks 提升分析能力
  • 嵌入式开发学习 C++:day01
  • 动态规划:硬币兑换(有趣)
  • LeetCode - 739. 每日温度
  • 线性回归原理推导与应用(十一):多重共线性
  • 获取服务器指标的信息
  • bin log 和 redo log有什么区别
  • Mybatis总结
  • 【如何解决Java中的ClassCastException类转换异常问题】
  • 基于Matlab结合肤色检测与卷积神经网络的人脸识别方法研究
  • 基于MATLAB/Simulink的单机带负荷仿真系统搭建
  • 分布式2PC理论
  • 使用 html2canvas + jspdf 实现页面元素下载为pdf文件
  • UE5 查找组件
  • 云原生安全架构设计与零信任实践
  • 预测模型及超参数:1.传统机器学习:SVR与KNN
  • 工业网络安全:保护制造系统和数据
  • HIVE的Window functions窗口函数【二】
  • 【Hadoop】Zookeeper、HBase、Sqoop
  • 全球位置智能软件CR10为73%,市场集中度高
  • Java中高效获取IP地域信息方案全解析:从入门到生产实践
  • jQuery版EasyUI的ComboBox(下拉列表框)问题
  • JS(面试)
  • Proxmox VE 中启用 CentOS 虚拟机的串口终端(xterm.js 控制台)
  • 深度剖析HTTP和HTTPS