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

imx6ull-驱动开发篇43——I.MX6U 的 I2C 驱动分析

目录

I2C 控制器驱动

IIC设备树节点

i2c_imx_probe 函数

i2c_imx_algo 结构体

i2c_imx_xfer 函数

I2C 设备驱动编写流程

 I2C 设备信息描述

I2C 设备数据收发处理流程

i2c_transfer 函数

i2c_msg 结构体

i2c_master_send函数

i2c_master_recv函数


在上一讲内容里,imx6ull-驱动开发篇42——Linux I2C 驱动框架简介,我们了解了 Linux 下的 I2C 驱动框架,重点分为 I2C 适配器驱动和 I2C 设备驱动。

I2C 设备驱动是需要用户根据不同的 I2C 设备去编写,而 I2C 适配器驱动一般都是 SOC 厂商去编写的,比如 NXP 就编写好了 I.MX6U 的I2C 适配器驱动。

I2C 控制器驱动

IIC设备树节点

打开我们自己移植的linux源码路径下的 /arch/arm/boot/dts/imx6ull.dtsi找到 I.MX6U 的 I2C1 控制器节点,节点内容如下所示:

这里i2c1节点的compatible属性值有两个:“fsl,imx6ul-i2c”和“fsl,imx21- i2c”,在 Linux 源码中搜索这两个字符串即可找到对应的驱动文件。

I.MX6U 的 I2C 适配器驱动文件为 drivers/i2c/busses/i2c-imx.c,在此文件中有如下内容:

static struct platform_device_id imx_i2c_devtype[] = {{.name = "imx1-i2c",.driver_data = (kernel_ulong_t)&imx1_i2c_hwdata,}, {.name = "imx21-i2c",.driver_data = (kernel_ulong_t)&imx21_i2c_hwdata,}, {/* sentinel */}
};
MODULE_DEVICE_TABLE(platform, imx_i2c_devtype);static const struct of_device_id i2c_imx_dt_ids[] = {{ .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },{ .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },{ .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, i2c_imx_dt_ids);static struct platform_driver i2c_imx_driver = {.probe = i2c_imx_probe,.remove = i2c_imx_remove,.driver = {.name = DRIVER_NAME,.owner = THIS_MODULE,.of_match_table = i2c_imx_dt_ids,.pm = IMX_I2C_PM,},.id_table = imx_i2c_devtype,
};static int __init i2c_adap_imx_init(void)
{return platform_driver_register(&i2c_imx_driver);
}
subsys_initcall(i2c_adap_imx_init);static void __exit i2c_adap_imx_exit(void)
{platform_driver_unregister(&i2c_imx_driver);
}
module_exit(i2c_adap_imx_exit);

当设备和驱动匹配成功以后 ,i2c_imx_probe 函数就会执行, i2c_imx_probe 函数就会完成 I2C 适配器初始化工作。

i2c_imx_probe 函数

i2c_imx_probe 函数内容如下所示(有省略):

static int i2c_imx_probe(struct platform_device *pdev)
{// 1. 获取设备树匹配数据(如果存在)const struct of_device_id *of_id = of_match_device(i2c_imx_dt_ids, &pdev->dev);struct imx_i2c_struct *i2c_imx;struct resource *res;struct imxi2c_platform_data *pdata = dev_get_platdata(&pdev->dev);void __iomem *base;int irq, ret;dma_addr_t phy_addr;dev_dbg(&pdev->dev, "<%s>\n", __func__); // 调试日志// 2. 获取硬件资源irq = platform_get_irq(pdev, 0);          // 获取中断号
......// 获取 I2C1 控制器寄存器物理基地址,也就是 0X021A0000res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); // 内存映射if (IS_ERR(base))return PTR_ERR(base);phy_addr = (dma_addr_t)res->start;        // 物理地址(用于DMA)// 3. 分配驱动数据结构i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL);if (!i2c_imx)return -ENOMEM;// 4. 初始化硬件数据(优先设备树,其次平台ID)if (of_id)i2c_imx->hwdata = of_id->data;       // 从设备树获取硬件配置elsei2c_imx->hwdata = (struct imx_i2c_hwdata *)platform_get_device_id(pdev)->driver_data; // 传统平台ID方式// 5. 设置I2C适配器基础信息strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name));i2c_imx->adapter.owner = THIS_MODULE;i2c_imx->adapter.algo = &i2c_imx_algo;   // 绑定算法(i2c_imx_algo实现master_xfer等)i2c_imx->adapter.dev.parent = &pdev->dev;i2c_imx->adapter.nr = pdev->id;          // 适配器编号i2c_imx->adapter.dev.of_node = pdev->dev.of_node;i2c_imx->base = base;                    // 寄存器基地址// 6. 时钟管理i2c_imx->clk = devm_clk_get(&pdev->dev, NULL); // 获取时钟
......ret = clk_prepare_enable(i2c_imx->clk);  // 启用时钟
.......// 7. 中断设置ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr,IRQF_NO_SUSPEND, pdev->name, i2c_imx);
......// 8. 初始化等待队列(用于中断处理)init_waitqueue_head(&i2c_imx->queue);// 9. 设置适配器私有数据i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);// 10. 配置时钟频率(优先设备树,其次平台数据)i2c_imx->bitrate = IMX_I2C_BIT_RATE;     // 默认速率 = 100Kret = of_property_read_u32(pdev->dev.of_node,"clock-frequency", &i2c_imx->bitrate);if (ret < 0 && pdata && pdata->bitrate)i2c_imx->bitrate = pdata->bitrate;   // 使用平台数据覆盖// 11. 初始化硬件寄存器imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,i2c_imx, IMX_I2C_I2CR); // 禁用中断imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR); // 清除状态寄存器// 12. 注册I2C适配器ret = i2c_add_numbered_adapter(&i2c_imx->adapter);if (ret < 0) {dev_err(&pdev->dev, "adapter registration failed\n");goto clk_disable;}// 13. 设置驱动私有数据platform_set_drvdata(pdev, i2c_imx);clk_disable_unprepare(i2c_imx->clk);     // 临时关闭时钟(后续使用时再开启)
......// 14. 可选DMA初始化i2c_imx_dma_request(i2c_imx, phy_addr);  // 如果支持DMA则配置return 0; // 成功返回clk_disable:clk_disable_unprepare(i2c_imx->clk);     // 错误处理:关闭时钟return ret;
}

i2c_imx_probe 函数主要的工作就是两点:

  • 初始化 i2c_adapter,设置 i2c_algorithm 为 i2c_imx_algo,最后向 Linux 内核注册i2c_adapter。
  • 初始化 I2C1 控制器的相关寄存器。

i2c_imx_algo 结构体

i2c_imx_algo 包含 I2C1 适配器与 I2C 设备的通信函数 master_xfer,

i2c_imx_algo 结构体定义如下:

static struct i2c_algorithm i2c_imx_algo = {.master_xfer = i2c_imx_xfer,      // 标准I2C消息传输函数.functionality = i2c_imx_func,    // 查询适配器支持的功能
};

functionality用于返回此I2C适配器支持什么样的通信协议,在这里 functionality 就是 i2c_imx_func 函数.

i2c_imx_func 函数内容如下:

/*** i2c_imx_func - 声明I2C适配器支持的功能* @adapter: 关联的I2C适配器** 返回值:I2C_FUNC_*标志的组合,指示硬件支持的功能*/
static u32 i2c_imx_func(struct i2c_adapter *adapter)
{return I2C_FUNC_I2C |                // 支持标准I2C协议I2C_FUNC_SMBUS_EMUL |        // 支持SMBus基本命令模拟I2C_FUNC_SMBUS_READ_BLOCK_DATA; // 支持SMBus块读取(I2C_SMBUS_BLOCK_DATA)
}

i2c_imx_xfer 函数

i2c_imx_algo结构体里还注册了i2c_imx_xfer 函数,最终就是通过此函数来完成与 I2C 设备通信的。

i2c_imx_xfer 函数内容如下(有省略):

/*** i2c_imx_xfer - I2C 总线消息传输主函数* @adapter: I2C 适配器指针* @msgs: I2C 消息数组指针* @num: 消息数量* * 返回值: 成功传输的消息数,错误时返回负的错误码*/
static int i2c_imx_xfer(struct i2c_adapter *adapter,struct i2c_msg *msgs, int num)
{unsigned int i, temp;int result;bool is_lastmsg = false;  // 标记是否为最后一条消息struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);  // 获取适配器私有数据dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);  // 调试信息/* 1. 启动 I2C 传输 */result = i2c_imx_start(i2c_imx);  // 发送起始条件if (result)goto fail0;  // 启动失败跳转到错误处理/* 2. 处理所有消息 */for (i = 0; i < num; i++) {/* 标记最后一条消息 */if (i == num - 1)is_lastmsg = true;/* 3. 非第一条消息需要发送重复起始条件 */if (i) {dev_dbg(&i2c_imx->adapter.dev, "<%s> repeated start\n", __func__);temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);  // 读取控制寄存器temp |= I2CR_RSTA;  // 设置重复起始位imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);  // 写回控制寄存器result = i2c_imx_bus_busy(i2c_imx, 1);  // 检查总线是否繁忙if (result)goto fail0;  // 总线忙则跳转到错误处理}dev_dbg(&i2c_imx->adapter.dev, "<%s> transfer message: %d\n", __func__, i);
....../* 4. 根据消息标志选择读/写操作 */if (msgs[i].flags & I2C_M_RD) {/* 读操作 */result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);} else {/* 写操作:根据数据长度选择 DMA 或寄存器方式 */if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD) {result = i2c_imx_dma_write(i2c_imx, &msgs[i]);  // DMA 写} else {result = i2c_imx_write(i2c_imx, &msgs[i]);  // 寄存器写}}if (result)goto fail0;  // 传输失败跳转到错误处理}fail0:/* 5. 停止 I2C 传输 */i2c_imx_stop(i2c_imx);  // 发送停止条件/* 6. 输出调试信息并返回结果 */dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n",__func__, (result < 0) ? "error" : "success msg",(result < 0) ? result : num);return (result < 0) ? result : num;  // 返回传输结果
}
  • 调用 i2c_imx_start 函数,开启 I2C 通信。
  • 如果是从 I2C 设备读数据的话,就调用 i2c_imx_read 函数
  • 向 I2C 设备写数据,如果要用 DMA 的话就使用 i2c_imx_dma_write 函数来完成写数据。如果不使用 DMA 的话就使用 i2c_imx_write 函数完成写数据。
  • I2C 通信完成以后,调用 i2c_imx_stop 函数停止 I2C 通信。

I2C 设备驱动编写流程

 I2C 设备信息描述

使用设备树的时候,添加 I2C 设备信息就在设备树里创建相应的节点。

比如 NXP 官方的 EVK 开发板在 I2C1 上接了 mag3110 这个磁力计芯片,因此必须在 i2c1 节点下创建 mag3110 子节点,然后在这个子节点内描述 mag3110 这个芯片的相关信息,如图:

I2C 设备节点的创建重点是 compatible 属性和 reg 属性的设置,一个用于匹配驱动,一个用于设置器件地址。

I2C 设备数据收发处理流程

一般需要在 probe 函数里面初始化 I2C 设备,要初始化 I2C 设备就必须能够对 I2C 设备寄存器进行读写操作。

i2c_transfer 函数

i2c_transfer 函数,最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数,对于 I.MX6U 而言就是i2c_imx_xfer 这个函数。

i2c_transfer 函数原型如下:

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
  • adap: 所使用的 I2C 适配器, i2c_client 会保存其对应的 i2c_adapter。
  • msgs: I2C 要发送的一个或多个消息。
  • num: 消息数量,也就是 msgs 的数量。
  • 返回值: 负值,失败,其他非负值,发送的 msgs 数量。

msgs 参数,是一个 i2c_msg 类型的指针参数,Linux 内核使用 i2c_msg 结构体来描述一个消息。

i2c_msg 结构体

i2c_msg 结构体定义在 include/uapi/linux/i2c.h 文件中,结构体内容如下:

/*** struct i2c_msg - I2C 消息结构体* @addr:  从机地址(7位或10位,取决于I2C_M_TEN标志)* @flags: 消息标志位(见下方定义)* @len:   消息数据长度(字节数)* @buf:   消息数据缓冲区指针** 描述I2C总线上的单个消息帧,用于读/写操作*/
struct i2c_msg {__u16 addr;  /* 从机地址(低7/10位有效) */__u16 flags; /* 标志位组合(位掩码) */
#define I2C_M_TEN         0x0010  // 使用10位地址模式(默认为7位)
#define I2C_M_RD          0x0001  // 读操作(1=读,0=写)
#define I2C_M_STOP        0x8000  // 强制在本消息后发送STOP条件
#define I2C_M_NOSTART     0x4000  // 不发送START条件(需前一条消息无STOP)
#define I2C_M_REV_DIR_ADDR 0x2000 // 方向位反转(某些特殊设备需要)
#define I2C_M_IGNORE_NAK  0x1000  // 忽略从机NAK应答(强制继续传输)
#define I2C_M_NO_RD_ACK   0x0800  // 读操作时不发送ACK(用于最后字节)
#define I2C_M_RECV_LEN    0x0400  // 首字节包含接收长度(SMBus块读)__u16 len;   /* 消息数据长度(buf的有效字节数) */__u8 *buf;   /* 数据缓冲区(写入时为发送数据,读取时为接收缓冲区) */
};

使用 i2c_transfer 函数发送数据之前要先构建好 i2c_msg。

使用 i2c_transfer 进行 I2C 数据收发,示例代码如下:

/* 设备结构体 */
struct xxx_dev {......void *private_data; /* 私有数据,一般会设置为 i2c_client */
};/** @description : 读取 I2C 设备多个寄存器数据* @param – dev : I2C 设备* @param – reg : 要读取的寄存器首地址* @param – val : 读取到的数据* @param – len : 要读取的数据长度* @return : 操作结果*/
static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val, int len)
{int ret;struct i2c_msg msg[2];struct i2c_client *client = (struct i2c_client *)dev->private_data;/* msg[0],第一条写消息,发送要读取的寄存器首地址 */msg[0].addr = client->addr; /* I2C 器件地址 */msg[0].flags = 0; /* 标记为发送数据 */msg[0].buf = &reg; /* 读取的首地址 */msg[0].len = 1; /* reg 长度 *//* msg[1],第二条读消息,读取寄存器数据 */msg[1].addr = client->addr; /* I2C 器件地址 */msg[1].flags = I2C_M_RD; /* 标记为读取数据 */msg[1].buf = val; /* 读取数据缓冲区 */msg[1].len = len; /* 要读取的数据长度 */ret = i2c_transfer(client->adapter, msg, 2);if(ret == 2) {ret = 0;} else {ret = -EREMOTEIO;}return ret;
}/** @description : 向 I2C 设备多个寄存器写入数据* @param – dev : 要写入的设备结构体* @param – reg : 要写入的寄存器首地址* @param – buf : 要写入的数据缓冲区* @param – len : 要写入的数据长度* @return : 操作结果*/
static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf, u8 len)
{u8 b[256];struct i2c_msg msg;struct i2c_client *client = (struct i2c_client *)dev->private_data;b[0] = reg; /* 寄存器首地址 */memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */msg.addr = client->addr; /* I2C 器件地址 */msg.flags = 0; /* 标记为写数据 */msg.buf = b; /* 要发送的数据缓冲区 */msg.len = len + 1; /* 要发送的数据长度 */return i2c_transfer(client->adapter, &msg, 1);
}

另外还有两个API函数分别用于I2C数据的收发操作,这两个函数最终都会调用i2c_transfer。

i2c_master_send函数

首先来看一下 I2C 数据发送函数 i2c_master_send,函数原型如下:

int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
  • client: I2C 设备对应的 i2c_client。
  • buf:要发送的数据。
  • count: 要发送的数据字节数,要小于 64KB, 因为 i2c_msg 的 len 成员变量是一个 u16(无符号 16 位)类型的数据。
  • 返回值: 负值,失败,其他非负值,发送的字节数。

i2c_master_recv函数

I2C 数据接收函数为 i2c_master_recv,函数原型如下:

int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
  • client: I2C 设备对应的 i2c_client。
  • buf:要接收的数据。
  • count: 要接收的数据字节数,要小于 64KB, 因为 i2c_msg 的 len 成员变量是一个 u16(无
  • 符号 16 位)类型的数据。
  • 返回值: 负值,失败,其他非负值,发送的字节数。

下讲内容我们学习怎么编写AP3216C 这个 I2C 设备的 Linux 驱动。

http://www.xdnf.cn/news/18781.html

相关文章:

  • 软件开发技术栈
  • 集成电路学习:什么是ResNet深度残差网络
  • LeetCode 2140. 解决智力问题
  • JavaScript常用的算法详解
  • 8.26网络编程——Modbus TCP
  • 【跨国数仓迁移最佳实践7】基于MaxCompute多租的大数据平台架构
  • 发力低空经济领域,移动云为前沿产业加速崛起注入云端动能
  • Tomcat下载历史版本
  • 前沿技术趋势与应用:探索数字世界的下一个十年
  • 【第三章】软件测试缺陷管理:从判断到回归的全流程实践指南​
  • 支持向量机学习
  • 33.ansible 比较重要的配置文件
  • 可口可乐考虑出售Costa咖世家!加上星巴克中国、Peet’s皮爷咖啡,三大国际咖啡品牌“纷纷卖身”!咖啡行业格局彻底改写!
  • MyBatis-Flex是如何避免不同数据库语法差异的?
  • 微服务-23.网关登录校验-自定义GlobalFilter
  • 数据结构青铜到王者第五话---LinkedList与链表(2)
  • 洛谷: CF632D Longest Subsequence-普及+/提高
  • 相机激光安全等级和人眼安全
  • 亚马逊云科技免费套餐新政解析与实战:数据分析与可视化平台
  • 机器学习(二)特征工程
  • 深度剖析初始化vue项目文件结构!!【前端】
  • (MySQL索引事务) 本节目标 索引 事务
  • 机器学习--支持向量机
  • 数据结构(一):算法的时间复杂度和空间复杂度
  • 在使用spring ai进行llm处理的rag的时候,选择milvus还是neo4j呢?
  • 固定资产管理系统核心模块拆解:全流程管理逻辑
  • 30.LSTM-长短时记忆单元
  • 视频号存在争议了...
  • 从零开始的 Docker 之旅
  • 嵌入式系统学习Day23(进程)