1747 字
9 分钟
Linux I2C驱动通用框架和编写细节

I2C子系统架构#

I2C子系统采用分层设计:

  • I2C核心层
    • 提供总线协议实现、设备注册/注销、总线通信接口(如i2c_transfer
    • struct i2c_adapter:抽象化的I2C控制器
    • struct i2c_client:连接到总线的I2C从机
    • 管理适配器和设备,提供用户空间接口(/dev/i2c-*)
  • I2C总线驱动
    • 控制物理I2C控制器硬件,实现底层时序和寄存器控制
    • 填充struct i2c_algorithm,底层数据收发
    • 注册适配器(i2c_add_adapter),通常和平台设备驱动结合,依赖设备树
  • I2C设备驱动
    • 驱动特定的I2C设备
    • struct i2c_driver:定义驱动名称、设备ID表、proberemove方法
    • struct i2c_client:保存设备地址、适配器指针等

设备树配置#

  1. 定义I2C控制器节点,通常由SoC厂商提供
  2. 在节点下添加I2C从设备,需保证一个从机地址只有一个设备,不能复用
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
magnetometer@e {
compatible = "fsl,mag3110";
reg = <0x0e>;
vdd-supply = <&reg_peri_3v3>;
vddio-supply = <&reg_peri_3v3>;
position = <2>;
};
/*
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
}; */
/* I2C从设备 */
ap3216c@1e {
compatible = "alientek,ap3216c";
reg = <0x1e>;
};
};

设备驱动#

  1. 定义驱动结构体i2c_driver
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.owner = THIS_MODULE,
.name = "ap3216c",
.of_match_table = ap3216c_of_match,
},
};
  1. 实现匹配表(兼容设备树),{ /* Sential */ }必须添加
static const struct of_device_id ap3216c_of_match[] = {
{.compatible = "alientek,ap3216c"},
{ /* Sential */ }
};
  1. 实现probe函数
static int ap3216c_probe(struct i2c_client *client)
{
printk("driver and device has been matched!\r\n");
if (ap3216cdev.major){
ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
}else{
alloc_chrdev_region(&ap3216cdev.devid, 0, 1, AP3216C_NAME);
ap3216cdev.major = MAJOR(ap3216cdev.devid);
}
printk("reg ok, major = %d, minor = %d\r\n", ap3216cdev.major, 0);
cdev_init(&ap3216cdev.cdev, &ap3216c_fops);
cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
ap3216cdev.class = class_create(AP3216C_NAME);
ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
ap3216cdev.private_data = client;
return 0;
}
  1. 实现数据传输 i2c的数据传输采用类似于“填充”的形式,具体为定义一个i2c_msg类型的待发送消息msg,然后填充msg中的addr(从机地址)、flags(发送/接收标志位)、buf(发送/接收缓冲区)和len(数据长度)这四个成员变量,最后使用i2c_transfer()函数进行发送。

以接收数据为例,先发送目标寄存器地址,然后读取数据:

static int ap3216c_read_regs(struct ap3216c_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;
/* fill msg1, send addr signal */
msg[0].addr = client->addr;
msg[0].flags = 0; /* send data */
msg[0].buf = &reg; /* TX buffer */
msg[0].len = 1;
/* fill msg2, read len byte data to val */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD; /* read data */
msg[1].buf = val;
msg[1].len = len;
/* send msg */
ret = i2c_transfer(client->adapter, msg, 2);
if (ret == 2){
ret = 0;
}else{
printk("i2c RD failed = %d reg = %06x led = %d\r\n", ret ,reg, len);
ret = -EREMOTEIO;
}
return ret;
}

注意:AP3216C要求写数据的时序为: AP3216C Read Protocal 即发送从机设备地址->发送待读取寄存器地址->重新发送从机设备地址->从机输出待读取寄存器值。但是很显然上面的代码里面没有发送从机设备地址的这条msg,这是因为linux底层会根据msg.addr来自动处理发送设备地址的这一步操作,我们只需要处理发送待读取寄存器地址从机输出待读取寄存器值这两步。

写操作也是类似的: AP3216C Write Protocal

static s32 ap3216c_write_regs(struct ap3216c_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; /* reg addr */
memcpy(&b[1], buf, len); /* copy tx data to b[1] */
/* fill msg */
msg.addr = client->addr;
msg.flags = 0; /* send data */
msg.buf = b; /* tx buffer */
msg.len = len + 1;
/* transfer data */
return i2c_transfer(client->adapter, &msg, 1);
}

Linux kernel会自动处理发送时序,而且发送数据时不需要再停下来重新发送一遍寄存器地址再等待读取,而是可以连着发送,因此代码把待发送的数据接到了设备地址b[0]的后面,也就是b[1]里。实际发送时把设备地址和待发送数据合到一条msg里发送,长度也就是len + 1,即发送数据长度(len字节) + 寄存器地址长度(1字节)

  1. 注册驱动模块
module_i2c_driver(ap3216c_driver);

完整代码#

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/libata.h> /* 新版kernel不再支持ide.h */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include "ap3216creg.h"
#define AP3216C_CNT 1
#define AP3216C_NAME "ap3216c"
/* device dev */
struct ap3216c_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
unsigned short ir, als, ps; /* ap3216c reg */
};
static struct ap3216c_dev ap3216cdev;
/* ap3216c read from multipy regs */
static int ap3216c_read_regs(struct ap3216c_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;
/* fill msg1, send addr signal */
msg[0].addr = client->addr;
msg[0].flags = 0; /* send data */
msg[0].buf = &reg; /* TX buffer */
msg[0].len = 1;
/* fill msg2, read len byte data to val */
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD; /* read data */
msg[1].buf = val;
msg[1].len = len;
/* send msg */
ret = i2c_transfer(client->adapter, msg, 2);
if (ret == 2){
ret = 0;
}else{
printk("i2c RD failed = %d reg = %06x led = %d\r\n", ret ,reg, len);
ret = -EREMOTEIO;
}
return ret;
}
/* ap3216c write data to reg */
static s32 ap3216c_write_regs(struct ap3216c_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; /* reg addr */
memcpy(&b[1], buf, len); /* copy tx data to b[1] */
/* fill msg */
msg.addr = client->addr;
msg.flags = 0; /* send data */
msg.buf = b; /* tx buffer */
msg.len = len + 1;
/* transfer data */
return i2c_transfer(client->adapter, &msg, 1);
}
/* ap3216c read data from one reg */
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
u8 data = 0;
ap3216c_read_regs(dev, reg, &data, 1);
return data;
}
/* ap3216c write data to one reg */
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ap3216c_write_regs(dev, reg, &buf, 1);
}
/* read data from ap3216c reg */
void ap3216c_readdata(struct ap3216c_dev *dev)
{
unsigned char i = 0;
unsigned char buf[6];
/* read six regs in cycle */
for (i = 0; i < 6; i++)
{
buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
}
/* read IR */
if ((buf[0] >> 7) == 0x1){ /* IR is invalid */
dev->ir = 0;
}else{ /* read ir */
dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0x03); /* IR*/
}
/* read ALS */
dev->als = ((unsigned short)buf[3] << 8) | buf[2];
/* read PS */
if ((buf[4] >> 6) == 1){
dev->ps = 0;
}else{
dev->ps = (((unsigned short)buf[5] & 0x3F) << 4) | (buf[4] & 0x0F);
}
}
/* ap3216c initialization */
static int ap3216c_open(struct inode *inode, struct file *filp)
{
filp->private_data = &ap3216cdev;
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);
mdelay(50);
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x03);
return 0;
}
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
short data[3];
long err = 0;
struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
ap3216c_readdata(dev);
data[0] = dev->ir;
data[1] = dev->als;
data[2] = dev->ps;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
static int ap3216c_release(struct inode *inode, struct file *filp)
{
return 0;
}
static const struct file_operations ap3216c_fops = {
.owner = THIS_MODULE,
.open = ap3216c_open,
.read = ap3216c_read,
.release = ap3216c_release,
};
static int ap3216c_probe(struct i2c_client *client)
{
printk("driver and device has been matched!\r\n");
if (ap3216cdev.major){
ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
}else{
alloc_chrdev_region(&ap3216cdev.devid, 0, 1, AP3216C_NAME);
ap3216cdev.major = MAJOR(ap3216cdev.devid);
}
printk("reg ok, major = %d, minor = %d\r\n", ap3216cdev.major, 0);
cdev_init(&ap3216cdev.cdev, &ap3216c_fops);
cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
ap3216cdev.class = class_create(AP3216C_NAME);
ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
ap3216cdev.private_data = client;
return 0;
}
static void ap3216c_remove(struct i2c_client *client)
{
cdev_del(&ap3216cdev.cdev);
unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
device_destroy(ap3216cdev.class, ap3216cdev.devid);
class_destroy(ap3216cdev.class);
}
static const struct of_device_id ap3216c_of_match[] = {
{.compatible = "alientek,ap3216c"},
{ /* Sential */ }
};
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.owner = THIS_MODULE,
.name = "ap3216c",
.of_match_table = ap3216c_of_match,
},
};
module_i2c_driver(ap3216c_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("aki");
Linux I2C驱动通用框架和编写细节
https://arkmostima.pages.dev/posts/linuxi2c/
作者
Aki
发布于
2025-04-01
许可协议
CC BY-NC-SA 4.0