Linux I2C 设备驱动

I2C设备驱动的整体结构

设备驱动的抽象

使用 i2c_driver 结构体来表示一个I2C设备驱动。

I2C从设备的抽象

使用 i2c_device_id 结构体来表示I2C驱动所支持的设备的列表,由 i2c_driver 的 id_table 成员指向这个结构体的一个实例化的数组。

static const struct i2c_device_id xxx_table[] = {
	{ "xxx" },
	{ },
};
MODULE_DEVICE_TABLE(i2c, xxx_table);

使用 i2c_client 结构体来表示一个I2C总线上匹配到的从设备,通常它被包含在设备的私有数据结构体中;在设备驱动中操作I2C设备时,需要先获得此结构体的实例。

I2C数据传输的抽象

使用 i2c_msg 结构体来表示一次I2C传输,它指定了一次传输中包括的消息数目(通常读为2,写为1),从设备地址,消息的标志(如,读或写等),传输的数据的缓冲区的地址指针,以及数据的字节数。

注意,如果是使用 SMBus接口进行传输,是不需要使用 i2c_msg 结构体的。

驱动代码分析

init, exit

在设备的 init 函数中注册 i2c 设备驱动;在设备的 exit 函数中注销 i2c 设备驱动。

在驱动注册之前 i2c_driver 结构体需要被正确地初始化,有4个成员要求必须被初始化:

static struct i2c_driver xxx_driver = {
    .driver	= {
		.name	= "xxx",
	},
	.probe		= xxx_probe,
	.remove		= xxx_remove,
	.id_table	= xxx_table,
};

init 和 exit 函数:

static int __init xxx_init(void)
{
   return i2c_add_driver(&xxx_driver);
}

static void __exit xxx_exit(void)
{
    i2c_del_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);

也可以直接使用 module_i2c_driver 宏来注册驱动

module_i2c_driver(xxx_driver);

probe, remove

一个I2C设备在设备树中的 reg 属性提供其从设备地址,如:

&i2c1 {
	status = "okay";
	max17040:max17040@36{
		compatible = "max17040";
		reg = <0x36>;
		status = "okay";
	};
};

设备与驱动匹配之后,i2c_client 的 adapter 成员指向对应的主机控制器,设备名、从设备地址被填入 i2c_client 的 name 和 addr 成员。

然后,已被初始化的 i2c_client 被传入其匹配的驱动的 probe 函数。

I2C 设备的 probe 和 remove 函数的原型如下:

static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id);
static int __devexit xxx_remove(struct i2c_client *client);
probe() 函数的主要工作

(1) 为设备私有数据的结构体分配内存,设置其中的client成员为传入的client

(2) 若有必要,检查I2C适配器支持的传输方法,以决定之后的I2C通信使用哪一种传输方法。目前有两种传输方法:smbus_xfer,master_xfer。

(3) 设置和初始化结构体的其它成员

(4) 申请硬件资源(中断、GPIO等),与硬件相关的初始化操作

remove() 函数的主要工作

(1) 与硬件相关的退出操作,释放硬件资源

(2) 注销私有数据结构体中的成员,释放驱动申请的内存等

(3) 释放私有数据结构体

相关API
/* linux/i2c.h */
static inline void i2c_set_clientdata(struct i2c_client *dev, void *data);
static inline int i2c_check_functionality(struct i2c_adapter *adap, u32 func);

/* drivers/i2c/i2c-core.c */
void i2c_unregister_device(struct i2c_client *client)

I2C 数据传输

设备驱动可以在文件系统中创建设备节点,而使得用户空间的程序可以通过 ioctl, read, write 等函数访问设备节点来完成的I2C设备的数据传输的控制;drivers/i2c/i2c-dev.c 就是这样的一个例子,它向用户空间提供了一个通用的接口来访问 i2c驱动。

用户空间的 ioctl, read, write 等函数,对应驱动中 file_operation 的相应函数;file_operation 中的相应函数会去调用更底层的 i2c 数据传输函数。通常在设备驱动中构造这样的函数,以适应不同 i2c 设备本身不同的通信要求,比如设备的寄存器地址长度、数据的长度、调用 i2c_transfer() 或者 smbus API 等。

目前适配器主要支持两种传输方法:master_xfer和 smbus_xfer。一般来说,如果设配器支持了 master_xfer 那么它也可以模拟支持 smbus 的传输。但如果只实现smbus_xfer,则不支持一些i2c的传输。

设备驱动通过 i2c_trasfer() 来调用适配器的 master_xfer方法,它需要先初始化好 i2c_msg;设备驱动通过 i2c_smbus_xxx 函数组来调用适配器的 smbus_xfer 方法。

i2c_transfer
/* drivers/i2c/i2c-core.c */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);

在使用 i2c_transfer() 之前,需要先构造好要传输的消息,i2c_msg 有4个成员:

成员含义
__u16 addr目标设备的从设备地址
__u16 flags消息标志,常用的有,0表示写,I2C_M_RD表示读
__u16 len消息的长度,以字节为单位
__u8 *buf消息内容的缓冲区地址

以下是之前调试 tc358775 这个芯片(16位寄存器地址,32位数据)写的数据传输函数:

/**
 * tc358775_i2c_read: 从指定的16位寄存器中读出32位数据
 * @data: 设备私有数据的结构体
 * @reg: 16位寄存器地址
 * @value: 读取出来的32位数据的缓冲区指针
 * return: 成功则返回发送的消息数,失败则返回-1
 */
static int tc358775_i2c_read(struct tc358775_data *data, u16 reg, u32 *value)
{
    struct i2c_msg msg[2];
    u8 msgbuf[2];
    u8 buf[4];
    int status;
    struct i2c_client *client = data->client;

    memset(msg, 0, sizeof(msg));
	
    /* 组装寄存器地址 */
    msgbuf[0] = reg >> 8;
    msgbuf[1] = reg;
    
    //msg0: 传输要读取的寄存器地址
    msg[0].addr = client->addr;
    msg[0].buf = msgbuf;
    msg[0].len = 2;
    //msg1: 从设备读出数据,存放在buf
    msg[1].addr = client->addr;
    msg[1].flags = I2C_M_RD;
    msg[1].buf = buf;
    msg[1].len = 4;
    
    //transfer
    status = i2c_transfer(client->adapter, msg, 2);
    if(status != 2) {
        printk("%s: i2c_transfer reg=%d, error(%d)\n", __func__, reg, status);
        return -1;
    }
    memcpy(value, buf, 4);

    return status;
}

/**
 * tc358775_i2c_write: 向指定的16位寄存器中写入32位数据
 * @data: 设备私有数据的结构体
 * @reg: 16位寄存器地址
 * @value: 要写入的32位数据
 * return: 成功则返回发送的消息数,失败则返回-1
 */
static int tc358775_i2c_write(struct tc358775_data *data, u16 reg, u32 value)
{
    struct i2c_msg msg;
    int status;
    struct i2c_client *client = data->client;

    msg.addr = client->addr;
    msg.flags = 0;
    msg.buf = data->writebuf;
    
    /* 组装寄存器地址 */
    msg.buf[0] = reg >> 8;
    msg.buf[1] = reg;
    /* 组装消息内容 */
    memcpy(&msg.buf[2], &value, 4);
    
    msg.len = 6;

    status = i2c_transfer(client->adapter, &msg, 1);
    if(status != 1)
        printk("%s: i2c_transfer reg=%d, error(%d)\n", __func__, reg, status);

    return status;
}

另外,可参考 ili210x 触摸屏芯片的驱动:

/* drivers/input/touchscreen/ili210x.c */
static int ili210x_read_reg(struct i2c_client *client, u8 reg, void *buf,
			    size_t len)
{
	struct i2c_msg msg[2] = {
		{
			.addr	= client->addr,
			.flags	= 0,
			.len	= 1,
			.buf	= &reg,
		},
		{
			.addr	= client->addr,
			.flags	= I2C_M_RD,
			.len	= len,
			.buf	= buf,
		}
	};

	if (i2c_transfer(client->adapter, msg, 2) != 2) {
		dev_err(&client->dev, "i2c transfer failed\n");
		return -EIO;
	}

	return 0;
}
SMBus 接口

SMBus 是在 I2C 总线的基础上实现的,SMBus 具有超时功能,工作频率在 10kHz~100kHz

/* drivers/i2c/i2c-core.c */
s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client, u8 command,
				  u8 length, u8 *values);
s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client, u8 command,
				   u8 length, const u8 *values);
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);
s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command,
			      u8 value);

at24.c 的例子:

/* drivers/misc/eeprom/at24.c */
static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
		unsigned offset, size_t count)
{
  switch (at24->use_smbus) {
	case I2C_SMBUS_I2C_BLOCK_DATA:
		/* Smaller eeproms can work given some SMBus extension calls */
		if (count > I2C_SMBUS_BLOCK_MAX)
			count = I2C_SMBUS_BLOCK_MAX;
		break;
	case I2C_SMBUS_WORD_DATA:
		count = 2;
		break;
	case I2C_SMBUS_BYTE_DATA:
		count = 1;
		break;
	default:
          ...
  	}
    timeout = jiffies + msecs_to_jiffies(write_timeout);
	do {
		read_time = jiffies;
		switch (at24->use_smbus) {
         /* 读取超出 2 Bytes 的数据 */
		case I2C_SMBUS_I2C_BLOCK_DATA:
			status = i2c_smbus_read_i2c_block_data(client, offset,
					count, buf);
			break;
		 /* 读取 2 Bytes 的数据 */
		case I2C_SMBUS_WORD_DATA:
			status = i2c_smbus_read_word_data(client, offset);
			if (status >= 0) {
				buf[0] = status & 0xff;
				buf[1] = status >> 8;
				status = count;
			}
			break;
         /* 读取 1 Byte 的数据 */
		case I2C_SMBUS_BYTE_DATA:
			status = i2c_smbus_read_byte_data(client, offset);
			if (status >= 0) {
				buf[0] = status;
				status = count;
			}
			break;
		default:
			status = i2c_transfer(client->adapter, msg, 2);
			if (status == 2)
				status = count;
		}
		dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)\n",
				count, offset, status, jiffies);

		if (status == count)
			return count;

		/* REVISIT: at HZ=100, this is sloooow */
		msleep(1);
	} while (time_before(read_time, timeout));
}

static ssize_t at24_eeprom_write(struct at24_data *at24, const char *buf,
		unsigned offset, size_t count)
{
    ...
    timeout = jiffies + msecs_to_jiffies(write_timeout);
	do {
		write_time = jiffies;
		if (at24->use_smbus) {
            /* 调用smbus接口,不需要使用 */
            /* offset:偏移地址; count:数据长度; buf: 数据缓冲区指针 */
			status = i2c_smbus_write_i2c_block_data(client,
					offset, count, buf);
			if (status == 0)
				status = count;
		} else {
			status = i2c_transfer(client->adapter, &msg, 1);
			if (status == 1)
				status = count;
		}
		dev_dbg(&client->dev, "write %zu@%d --> %zd (%ld)\n",
				count, offset, status, jiffies);

		if (status == count)
			return count;

		/* REVISIT: at HZ=100, this is sloooow */
		msleep(1);
	} while (time_before(write_time, timeout));
    ...
}

max17040_battery.c 的例子:

/* drivers/power/max17040_battery.c */
static int max17040_write_reg(struct i2c_client *client, int reg, u8 value)
{
	int ret;

	ret = i2c_smbus_write_byte_data(client, reg, value);

	if (ret < 0)
		dev_err(&client->dev, "%s: err %d\n", __func__, ret);

	return ret;
}

static int max17040_read_reg(struct i2c_client *client, int reg)
{
	int ret;

	ret = i2c_smbus_read_byte_data(client, reg);

	if (ret < 0)
		dev_err(&client->dev, "%s: err %d\n", __func__, ret);

	return ret;
}

参考源码

linux/i2c.h

drivers/i2c/i2c-core.c

drivers/i2c/i2c-dev.c

drivers/misc/eeprom/at24.c

drivers/power/max17040_battery.c

drivers/input/touchscreen/ili210x.c

Linux I2C设备驱动Linux内核中的一个子系统,用于处理I2C总线上的设备驱动程序。I2C是一种串行通信协议,通常用于连接各种外设,例如传感器、LCD屏幕、EEPROM、温度传感器等。 Linux I2C设备驱动程序通常包括以下几个部分: 1. i2c_driver结构体:定义I2C设备驱动的属性和操作函数。这个结构体包含了设备的名称、ID等信息,以及设备的初始化函数、读写函数等。通过注册这个结构体,将I2C设备驱动程序和I2C总线绑定在一起。 2. i2c_client结构体:定义I2C设备的属性和操作函数。这个结构体包含了设备的地址、名称等信息,以及设备的读写函数等。通过这个结构体,可以访问I2C设备,读写寄存器等。 3. probe函数:用于初始化I2C设备。当I2C总线扫描到一个新的设备时,会调用该函数,完成设备的初始化工作。 4. remove函数:用于卸载I2C设备。当I2C总线上的设备被移除时,会调用该函数,完成设备的清理工作。 5. ioctl函数:用于实现设备的特殊操作。例如,设置I2C设备的工作模式、读取设备的状态等。 通过实现这些函数,可以编写一个完整的Linux I2C设备驱动程序。在驱动程序中,可以使用Linux内核提供的函数,例如i2c_transfer函数、i2c_smbus_read_byte函数等,来实现I2C设备的读写操作。同时,也可以使用Linux调试工具来调试驱动程序,例如dmesg命令、insmod命令等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值