基于I2C协议的驱动开发

1、linux下的I2C协议框架

一般i2c_adapter以下的特定适配器硬件相关操作代码由芯片厂家开发完成。只需要完成在I2C协议基础上的二次开发,比方说基于I2C协议实现网络数据的传输,或者I2C外围器件的控制,或者blalalala

2、I2C协议的读写时序图

I2C设备挂载I2C总线以后,I2C设备的设备地址由硬件确定。同一条I2C总线上的不同I2C设备的设备地址不同。I2C协议传输的单元是一个字节(有些是10bit)。

写时序

img

 一次I2C的写操作包括设备地址+写操作;寄存器地址,数据。

每次CPU发送一个字节的数据后,I2C设备把DATA线拉低,做ACK

读时序

img

 读时序相对写时序稍微复杂一点点。分成两部分,第一步写操作,告诉I2C设备需要读寄存器的地址。第二步读操作,第二次发送I2C设备地址,等待I2C设备ACK后,完成数据读取。【此时CPU成为接收端,当完成读取一个字节的数据后,CPU可以发送ACK。假如有ACK,从设备会继续发送寄存器的数据(大部分I2C设备支持多地址读取,发送一次寄存器地址,连续接下来的多个寄存器数据);假如NOACK,I2C设备停止发送数据】

3、代码框架

抛个问题:一个SOC上有好几路IIC,一路IIC上可以挂多个从设备。怎么指定使用的是哪一路IIC呢?

3.1、i2c设备节点的创建

如图1所示,其实一路IIC对应单独的i2c_adapter和i2c_client。可以使用

struct i2c_adapter *i2c_get_adapter(int nr)

i2c-core-base.c - drivers/i2c/i2c-core-base.c - Linux source code (v5.18.13) - Bootlin

实现i2c_adapter的创建,函数的入参刚好是IIC通道号。在i2c_adapter创建后,再使用

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)

完成i2c_client的创建。

见以下代码,其中i2c_info下的0x45就是从设备的地址:

//需要和i2c_board_info中的名字xxxx一样
static const struct i2c_device_id module_id[] = {
	{ "xxxx", 0 },
	{ /* END OF LIST */ }
};
MODULE_DEVICE_TABLE(i2c, module_id);

static struct i2c_driver module_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = "i2c-module",
	},
	.id_table = module_id,
	.probe = module_probe,
	.remove = module_remove,
};

static struct i2c_board_info i2c_info[] = {
	{
		I2C_BOARD_INFO("xxxx", 0x45),
		.platform_data = 0,
	},
};


int module_init(void)
{
    ......

	adapter = i2c_get_adapter(channel);
	if (!adapter) {
		printk("failed to get I2C adapter %d\n", channel);
		return -1;
	}

	device_client = i2c_new_device(adapter, i2c_info);
	if (!device_client) {
		printk("i2c new device fail!\n");
		return -1;
	}

	ret = i2c_add_driver(&module_driver);

    ......
}

初始化成功后,系统就会调用module_driver下的module_probe函数实现初始化。

在/sys/class/i2c可以看到节点

待补充

3.2、i2c协议的读写函数,统一使用下面这个函数

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

写函数的封装。从上面时序得知,写操作只有写动作,只需要一个msg,但是可以一次写好几个数据。

typedef struct reg {
	uint8_t *buf;
	int len;
}reg_t;

typedef struct buf {
	uint8_t *buf;
	int len;
}buf_t;

int i2c_module_write_data(void *handle, const reg_t *s_reg)
{
	int ret = 0;
	struct i2c_client *client = (struct i2c_client *)handle;
	struct i2c_msg msg;

	memset(&msg, 0x00, sizeof(struct i2c_msg));
	msg.addr 	= client->addr;        //从设备的地址
	msg.flags 	= 0;                   //写标记
	msg.len   	= s_reg->len;          //发送的数据长度,不考虑第一个从设备地址
	msg.buf 	= s_reg->buf;
	
	ret = i2c_transfer(client->adapter, &msg, 1);
	if(ret <= 0)
	{
		printk("transfer cmd data error.\n");
	}

	return ret;
}

读函数的封装。从上面时序可知,读操作有两个动作,先一个写操作,后一个读操作,需要两个msg。

int i2c_module_read_data(void *handle, const reg_t *s_reg, const buf_t *r_buf)
{
	int ret = 0;
	struct i2c_client *client = (struct i2c_client *)handle;
    struct i2c_msg msg[] = {
		{
			.addr  = client->addr,
			.flags = 0,
			.len   = s_reg->len,     		//表示寄存器地址字节长度,以byte为单位
			.buf   = s_reg->buf,
		},
		{
			.addr  = client->addr,
			.flags = I2C_M_RD,              //读标记
			.len   = r_buf->len, 			//表示期望读到数据的字节长度,以byte为单位
			.buf   = r_buf->buf, 			//将读取到的数据保存在buffer中
		},
	};
	
	ret = i2c_transfer(client->adapter, msg, sizeof(msg)/sizeof(msg[0]));
    if(ret != (sizeof(msg)/sizeof(msg[0]))) {
		printk("read regs from chip error.\n");
		
		return -1;
	}
    
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值