Linux驱动 | I2C设备驱动(以ds1337.c为例)

本文以 Linux-2.6.22.6 内核树下的 drivers/i2c/chips/ds1337.c 为例, 讨论Linux系统下的I2C设备的驱动代码的主要构成。

ds1337是一款 i2c 接口的串行实时时钟芯片。

i2c_driver 和 i2c_client 是I2C设备驱动的核心数据结构。I2C 设备驱动要使用 i2c_driver 和 i2c_client 数据结构并填充其中的成员函数。

另外值得一提的是,i2c_adapter和i2c_algorithm这两者则为I2C总线驱动的核心数据结构。以上这四个数据结构都在include/linux/i2c.h中被定义。i2c_client是硬件上的i2c芯片/设备(比如:本文的ds1337芯片)的抽象,而i2c_driver是i2c_client的一系列操作方法的一个封装;类似的,i2c_adapter是硬件上的i2c_适配器(比如:s3c2440的I2C控制器)的抽象,而i2c_algorithm则是对i2c_adapter的一系列操作方法的一个封装。

i2c_client 被包含在设备的私有信息结构体 ds1337_data 中,而 i2c_driver 则被定义为全局变量并初始化。如下:

struct ds1337_data {
	struct i2c_client client;
	struct list_head list;
};

static struct i2c_driver ds1337_driver = {
	.driver = {
		.name	= "ds1337",
	},
	.attach_adapter	= ds1337_attach_adapter,
	.detach_client	= ds1337_detach_client,
	.command	= ds1337_command,
};

另外,内核定义了i2c_msg 结构体来用于I2C 适配器和 I2C 设备之间的数据传输

struct i2c_msg {
	__u16 addr;	/* slave address			*/
	__u16 flags;
#define I2C_M_TEN	0x10	/* we have a ten bit chip address	*/
#define I2C_M_RD	0x01
#define I2C_M_NOSTART	0x4000
#define I2C_M_REV_DIR_ADDR	0x2000
#define I2C_M_IGNORE_NAK	0x1000
#define I2C_M_NO_RD_ACK		0x0800
#define I2C_M_RECV_LEN		0x0400 /* length will be first received byte */
	__u16 len;		/* msg length				*/
	__u8 *buf;		/* pointer to msg data			*/
}; 

可见,它其实就是I2C通信协议的数据格式在内核中的抽象。其成员:addr对应于从设备的地址;flags对应于读写控制位;len为消息长度;buf指针则指向主机中的缓存。


一、 I2C设备驱动模块加载与卸载

static int __init ds1337_init(void)
{
	return i2c_add_driver(&ds1337_driver);
}

static void __exit ds1337_exit(void)
{
	i2c_del_driver(&ds1337_driver);
}

加载

通过 I2C 核心的 i2c_add_driver()函数添加 i2c_driver。

卸载

通过 I2C 核心的  i2c_del_driver()函数删除 i2c_driver。

 i2c_add_driver()函数和i2c_del_driver()函数最终都通过i2c_driver中的 probe, remove, attach_adapter, detach_adapter, detach_client 这些成员去实现真正的操作。

模块加载过程和卸载过程引发的一系列反应如下图所示,在下文会讨论具体的过程。


二、 i2c_driver的成员函数

static struct i2c_driver ds1337_driver = {
	.driver = {
		.name	= "ds1337",
	},
	.attach_adapter	= ds1337_attach_adapter,
	.detach_client	= ds1337_detach_client,
	.command	= ds1337_command,
};

1. ds1337_attach_adapter

I2C 设备驱动的加载函数进行i2c_add_driver(&ds1337_driver)的调用,会引发i2c_driver 结构体中ds1337_attach_adapter()函数的执行,它简单地调用 I2C 核心的i2c_probe()函数。ds1337_detect()函数的指针作为参数传入i2c_probe()中;i2c_probe()函数调用i2c_probe_address(),从而引发ds1337_detect()函数的调用。

static int ds1337_attach_adapter(struct i2c_adapter *adapter)
{
	return i2c_probe(adapter, &addr_data, ds1337_detect);
}

在 ds1337_detect()函数中初始化i2c_client结构体的成员;调用i2c_attach_client()函数来建立i2c_client和i2c_adapter 的依附关系(把它加入队列的尾部:list_add_tail(&client->list, &adapter->clients));调用ds1337_init_client()函数来实现具体硬件相关的初始化;最后,把client加入本地链表(list_add(&data->list, &ds1337_clients))。

static int ds1337_detect(struct i2c_adapter *adapter, int address, int kind)
{
	struct i2c_client *new_client;
	struct ds1337_data *data;
	int err = 0;
	const char *name = "";

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
				     I2C_FUNC_I2C))
		goto exit;

	if (!(data = kzalloc(sizeof(struct ds1337_data), GFP_KERNEL))) {
		err = -ENOMEM;
		goto exit;
	}
	INIT_LIST_HEAD(&data->list);

	/* The common I2C client data is placed right before the
	 * DS1337-specific data. 
	 */
	new_client = &data->client;
	i2c_set_clientdata(new_client, data);
	new_client->addr = address;
	new_client->adapter = adapter;
	new_client->driver = &ds1337_driver;
	new_client->flags = 0;

	/* Default to an DS1337 if forced */
	if (kind == 0)
		kind = ds1337;

	if (kind < 0) {		/* detection and identification */
		u8 data;

		/* Check that status register bits 6-2 are zero */
		if ((ds1337_read(new_client, DS1337_REG_STATUS, &data) < 0) ||
		    (data & 0x7c))
			goto exit_free;

		/* Check for a valid day register value */
		if ((ds1337_read(new_client, DS1337_REG_DAY, &data) < 0) ||
		    (data == 0) || (data & 0xf8))
			goto exit_free;

		/* Check for a valid date register value */
		if ((ds1337_read(new_client, DS1337_REG_DATE, &data) < 0) ||
		    (data == 0) || (data & 0xc0) || ((data & 0x0f) > 9) ||
		    (data >= 0x32))
			goto exit_free;

		/* Check for a valid month register value */
		if ((ds1337_read(new_client, DS1337_REG_MONTH, &data) < 0) ||
		    (data == 0) || (data & 0x60) || ((data & 0x0f) > 9) ||
		    ((data >= 0x13) && (data <= 0x19)))
			goto exit_free;

		/* Check that control register bits 6-5 are zero */
		if ((ds1337_read(new_client, DS1337_REG_CONTROL, &data) < 0) ||
		    (data & 0x60))
			goto exit_free;

		kind = ds1337;
	}

	if (kind == ds1337)
		name = "ds1337";

	/* We can fill in the remaining client fields */
	strlcpy(new_client->name, name, I2C_NAME_SIZE);

	/* Tell the I2C layer a new client has arrived */
	if ((err = i2c_attach_client(new_client)))
		goto exit_free;

	/* Initialize the DS1337 chip */
	ds1337_init_client(new_client);

	/* Add client to local list */
	list_add(&data->list, &ds1337_clients);

	return 0;

exit_free:
	kfree(data);
exit:
	return err;
}

2. ds1337_detach_client

I2C 设备驱动的卸载函数进行 i2c_del_driver(&ds1337_driver)调用后,会引发与ds1337_driver 关联的每个 i2c_client 与之解除关联,即 ds1337_detach_client()函数将因此而被调用。

static int ds1337_detach_client(struct i2c_client *client)
{
	int err;
	struct ds1337_data *data = i2c_get_clientdata(client);

	if ((err = i2c_detach_client(client)))
		return err;

	list_del(&data->list);
	kfree(data);
	return 0;
} 

3. ds1337_command

i2c_driver的command成员函数实现了针对设备的控制命令。具体的控制命令是设备相关的,如对于本文的ds1337实时钟而言,命令将是设置时间和获取时间,而对于视频 AD 设备而言,命令会是设置采样方式、选择通道等。

ds1337 设备接受 DS1337_GET_DATE 和DS1337_GET_DATE 这两种命令,而处理这两个命令的函数分别为ds1337_get_datetimeds1337_set_datetime,如下:

static int ds1337_command(struct i2c_client *client, unsigned int cmd, void *arg)
{
	dev_dbg(&client->dev, "%s: cmd=%d\n", __FUNCTION__, cmd);

	switch (cmd) {
	case DS1337_GET_DATE:
		return ds1337_get_datetime(client, arg);

	case DS1337_SET_DATE:
		return ds1337_set_datetime(client, arg);

	default:
		return -EINVAL;
	}
}

具体命令的实现是通过组件 i2c_msg 消息数组,并调用 I2C 核心的i2c_transfer()。i2c_transfer()寻找到i2c_adapter 对应的 i2c_algorithm,并使用 i2c_algorithm 的master_xfer()函数真正驱动硬件流程,来实现 I2C 适配器和 I2C 设备之间的一组消息交互。

static int ds1337_get_datetime(struct i2c_client *client, struct rtc_time *dt)
{
	int result;
	u8 buf[7];
	u8 val;
	struct i2c_msg msg[2];
	u8 offs = 0;

	if (!dt) {
		dev_dbg(&client->dev, "%s: EINVAL: dt=NULL\n", __FUNCTION__);
		return -EINVAL;
	}

/*构造一条“写消息”*/
	msg[0].addr = client->addr;
	msg[0].flags = 0;
	msg[0].len = 1;
	msg[0].buf = &offs;
/*构造一条“读消息”*/
	msg[1].addr = client->addr;
	msg[1].flags = I2C_M_RD;
	msg[1].len = sizeof(buf);
	msg[1].buf = &buf[0];

/*完成数据的传输*/
	result = i2c_transfer(client->adapter, msg, 2);

/*具体的硬件操作:读取缓存中接收到的BCD码,转换成二进制码*/
	dev_dbg(&client->dev, "%s: [%d] %02x %02x %02x %02x %02x %02x %02x\n",
		__FUNCTION__, result, buf[0], buf[1], buf[2], buf[3],
		buf[4], buf[5], buf[6]);

	if (result == 2) {
		dt->tm_sec = BCD2BIN(buf[0]);
		dt->tm_min = BCD2BIN(buf[1]);
		val = buf[2] & 0x3f;
		dt->tm_hour = BCD2BIN(val);
		dt->tm_wday = BCD2BIN(buf[3]) - 1;
		dt->tm_mday = BCD2BIN(buf[4]);
		val = buf[5] & 0x7f;
		dt->tm_mon = BCD2BIN(val) - 1;
		dt->tm_year = BCD2BIN(buf[6]);
		if (buf[5] & 0x80)
			dt->tm_year += 100;

		dev_dbg(&client->dev, "%s: secs=%d, mins=%d, "
			"hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n",
			__FUNCTION__, dt->tm_sec, dt->tm_min,
			dt->tm_hour, dt->tm_mday,
			dt->tm_mon, dt->tm_year, dt->tm_wday);

		return 0;
	}

	dev_err(&client->dev, "error reading data! %d\n", result);
	return -EIO;
}
以上讨论的,即是Linux系统中I2C设备驱动的基本构成,至于其中跟底层的和具体硬件相关的代码,可以参阅Linux-2.6.22.6内核树下的  drivers/i2c/chips/ds1337.c 文件及其它相关源码。









  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值