最近参与了一款交换互联芯片的驱动开发设计,其中有一个需求是通过i2c读写交换互联芯片寄存器。由于之前做过DBF芯片的I2C驱动,对怎样根据不同的芯片I2C访问协议修改驱动比较熟悉,所以开发起来不是很难。

原厂提供了交换互联芯片在Linux用户应用程序中通过I2C访问寄存器的代码,如下:

#include <sys/types.h> 
#include <sys/stat.h> 
#include <string.h> 
#include <stdio.h> 
#include <fcntl.h> 
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <assert.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/types.h>
int higher2lower(char c);
int htoi(char s[]);
#define MSB_0_7_OF_U32(DATA) ((DATA >> 24) & 0xFF)
#define MSB_8_15_OF_U32(DATA) ((DATA >> 16) & 0xFF)
#define MSB_16_23_OF_U32(DATA) ((DATA >> 8) & 0xFF)
#define MSB_24_31_OF_U32(DATA) ((DATA >> 0) & 0xFF)
#define REG_VAL_32_BIT(VAL_8_BIT_0, VAL_8_BIT_1, VAL_8_BIT_2, VAL_8_BIT_3) \
		((VAL_8_BIT_0 << 24) | (VAL_8_BIT_1 << 16) | (VAL_8_BIT_2 << 8) | VAL_8_BIT_3)
int main(int argc, char *argv[]) 
{ 
	int fd, i, j, ret; 
	char *errorinfo;			 
	struct i2c_rdwr_ioctl_data work_queue;
	unsigned int i2c_adaptor, slave_address, reg_address, byte_count, reg_val, format_1, reg_num, rw_flag;
	unsigned char val;
 
//	access_tran access;
	i2c_adaptor = htoi(argv[1]);
	slave_address = htoi(argv[2]);
	reg_address = htoi(argv[3]);
	reg_val = htoi(argv[4]);
	rw_flag = htoi(argv[5]);
	printf("i2c_adaptor : %#x\nslave_address : %#x\nreg_address : %#x\nreg_val : %#x\nrw_flag : %#x\n", \
				i2c_adaptor, slave_address, reg_address, reg_val, rw_flag);
	switch(i2c_adaptor)
	{
		case 0:
		fd = open("/dev/i2c-0", O_RDWR);
		break;
 
		case 1:
		fd = open("/dev/i2c-1", O_RDWR);
		break;
 
		case 2:
		fd = open("/dev/i2c-2", O_RDWR);
		break;
 
		case 3:
		fd = open("/dev/i2c-3", O_RDWR);
		break;
 
		default:
		fd = open("/dev/i2c-1", O_RDWR);
		break;
	}
	if(!fd)
	{
		printf("Error on opening the device file\n");
		return 0;
	}
	
	//IIC configuration
	ioctl(fd, I2C_TIMEOUT, 2);
	ioctl(fd, I2C_RETRIES, 1);
 
	/*
	 * IIC operation
	 * rw_flag = 1; READ
	 * rw_flag = 0; WRITE
	 */
	if(rw_flag)
	{
		work_queue.nmsgs = 2;
		work_queue.msgs = (struct i2c_msg *)malloc(work_queue.nmsgs * sizeof(struct i2c_msg));
 
		if(!work_queue.msgs)
		{
			printf("memory alloc error\n");
			close(fd);
			return 0;
		}
 
		reg_num = 1;	
		format_1 = (rw_flag << 31) | ((reg_num - 1) << 22) | (reg_address >> 2);
		printf("format_1 : %#x\n", format_1);
		(work_queue.msgs[0]).len = 4;
		(work_queue.msgs[0]).addr = slave_address;
		(work_queue.msgs[0]).flags = 0;//w
		(work_queue.msgs[0]).buf = (unsigned char *)malloc(4);
		(work_queue.msgs[0]).buf[0] = MSB_0_7_OF_U32(format_1);
		(work_queue.msgs[0]).buf[1] = MSB_8_15_OF_U32(format_1);
		(work_queue.msgs[0]).buf[2] = MSB_16_23_OF_U32(format_1);
		(work_queue.msgs[0]).buf[3] = MSB_24_31_OF_U32(format_1);
//		(work_queue.msgs[0]).buf[3] = MSB_0_7_OF_U32(format_1);
//		(work_queue.msgs[0]).buf[2] = MSB_8_15_OF_U32(format_1);
//		(work_queue.msgs[0]).buf[1] = MSB_16_23_OF_U32(format_1);
//		(work_queue.msgs[0]).buf[0] = MSB_24_31_OF_U32(format_1);
	//	(work_queue.msgs[0]).buf[0] = MSB_16_23_OF_U32(reg_address);
	//	(work_queue.msgs[0]).buf[1] = MSB_24_31_OF_U32(reg_address);
 
		(work_queue.msgs[1]).len = 4;
		(work_queue.msgs[1]).flags = I2C_M_RD;//R
		(work_queue.msgs[1]).addr = slave_address;
		(work_queue.msgs[1]).buf = (unsigned char *)malloc(4);
		
		ret = ioctl(fd, I2C_RDWR, (unsigned long)&work_queue);
		reg_val = REG_VAL_32_BIT(work_queue.msgs[1].buf[0], work_queue.msgs[1].buf[1], \
						work_queue.msgs[1].buf[2], work_queue.msgs[1].buf[3]);
		if(ret < 0)
		  printf("error code : %d\n", ret);
		else
		  printf("slave_addr : %#x  reg_addr : %#x  reg_val : %#x\n", slave_address, reg_address, reg_val);
		free(work_queue.msgs);
	}
	else
	{
		work_queue.nmsgs = 1;
		work_queue.msgs = (struct i2c_msg *)malloc(work_queue.nmsgs * sizeof(struct i2c_msg));
		printf("work_queue.msgs addr* : %#x\n", (unsigned int*)work_queue.msgs);
 
		if(!work_queue.msgs)
		{
			printf("memory alloc error\n");
			close(fd);
			return 0;
		}
		reg_num = 1;	
		format_1 = (rw_flag << 31) | ((reg_num - 1) << 22) | (reg_address >> 2);
		printf("format_1 : %#x\n", format_1);
		(work_queue.msgs[0]).len = 8;//reg_addr 4 + reg_val 4
		(work_queue.msgs[0]).addr = slave_address;
		(work_queue.msgs[0]).flags = 0;//w
		(work_queue.msgs[0]).buf = (unsigned char *)malloc(8);
		(work_queue.msgs[0]).buf[0] = MSB_0_7_OF_U32(format_1);
		(work_queue.msgs[0]).buf[1] = MSB_8_15_OF_U32(format_1);
		(work_queue.msgs[0]).buf[2] = MSB_16_23_OF_U32(format_1);
		(work_queue.msgs[0]).buf[3] = MSB_24_31_OF_U32(format_1);
		(work_queue.msgs[0]).buf[4] = MSB_0_7_OF_U32(reg_val);
		(work_queue.msgs[0]).buf[5] = MSB_8_15_OF_U32(reg_val);
		(work_queue.msgs[0]).buf[6] = MSB_16_23_OF_U32(reg_val);
		(work_queue.msgs[0]).buf[7] = MSB_24_31_OF_U32(reg_val);
 
		
		ret = ioctl(fd, I2C_RDWR, (unsigned long)&work_queue);
		if(ret < 0)
		  printf("error code : %d\n", ret);
		free(work_queue.msgs);
	}
 
	close(fd);
	return ret;
} 
																	
int higher2lower(char c)
{
	if(c >= 'A' && c <= 'Z')
		return c + 'a' - 'A';
	else
	  return c;
}
 
int htoi(char s[])
{
	int i;
	int n = 0;
	if(s[0] == '0' && (s[1] == 'x' || s[1] == 'x'))
	  i = 2;
	else
	  i = 0;
 
//	if((s[i] >= '0' && s[i] <= '9') || (s[i] >= 'a' && s[i] <= 'f') \
			|| (s[i] >= 'A' && s[i] <= 'F'))
//	{
	for(;(s[i] >= '0' && s[i] <= '9') || (s[i] >= 'a' && s[i] <= 'f') \
			|| (s[i] >= 'A' && s[i] <= 'F'); ++i)
	{
		if(higher2lower(s[i] > '9'))
		{
			n = 16 * n +(10 + higher2lower(s[i]) - 'a');
		}
		else
		  n = 16 * n + (higher2lower(s[i] - '0'));
	}
//	}
	return n;
}

上面代码中最重要的就是format_1这个字段,这代表了通过i2c访问芯片的协议,根据这个应用程序就可以修改驱动了,代码如下:

/*
 * cetc bus driver
 *
 * Copyright (C) 2019 Intel Corp.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
 
#define DEBUG
 
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/delay.h>
 
#include <linux/serial_core.h>
 
/* Each client has this additional data */
#define USER_EEPROM_SIZE	0xFFFF48
#define USER_XFER_MAX_COUNT	0x8
 
/* Addresses to scan */
static const unsigned short cetc_i2c[] = { 0x3, I2C_CLIENT_END };
 
static unsigned read_timeout = 25;
module_param(read_timeout, uint, 0);
MODULE_PARM_DESC(read_timeout, "Time (in ms) to try reads (default 25)");
 
static unsigned write_timeout = 25;
module_param(write_timeout, uint, 0);
MODULE_PARM_DESC(write_timeout, "Time (in ms) to try writes (default 25)");
 
struct cetc_data {
	struct mutex	lock;
	u8	*data;
};
 
static ssize_t cetc_eeprom_read( struct i2c_client *client,
	char *buf, unsigned offset, size_t count)
{
	struct i2c_msg msg[2];
	u8 msgbuf[5];
	unsigned long timeout, transfer_time;
	unsigned int format_1;
	int status;
 
	memset(msg, 0, sizeof(msg));
 
	format_1 = (1 << 31) | (offset >> 2);
	
	msgbuf[0] = (u8)((format_1 >> 24) & 0xff);
	msgbuf[1] = (u8)((format_1 >> 16) & 0xff);
	msgbuf[2] = (u8)((format_1 >>  8) & 0xff);
	msgbuf[3] = (u8)((format_1 >>  0) & 0xff);
 
	msg[0].addr = client->addr;
	msg[0].flags = 0;
	msg[0].buf = msgbuf;
	msg[0].len = 4;
 
	msg[1].addr = client->addr;
	msg[1].flags = I2C_M_RD;
	msg[1].buf = buf;
	msg[1].len = count;
 
	/*
	 * Reads fail if the previous write didn't complete yet. We may
	 * loop a few times until this one succeeds, waiting at least
	 * long enough for one entire page write to work.
	 */
	timeout = jiffies + msecs_to_jiffies(read_timeout);
	do {
		transfer_time = jiffies;
 
		status = i2c_transfer(client->adapter, msg, 2);
		if (status == 2)
			status = count;
 
		dev_dbg(&client->dev, "read %ld@0x%lx --> %d (%ld)\n",
				count, (unsigned long)offset, status, jiffies);
 
		if (status == count)
			return count;
 
		/* REVISIT: at HZ=100, this is sloooow */
		msleep(1);
	} while (time_before(transfer_time, timeout));
 
	return -ETIMEDOUT;
 
 }
 
 
static ssize_t cetc_read(struct file *filp, struct kobject *kobj,
			    struct bin_attribute *bin_attr,
			    char *buf, loff_t offset, size_t count)
{
	struct i2c_client *client = kobj_to_i2c_client(kobj);
	struct cetc_data *data = i2c_get_clientdata(client);
 
	ssize_t retval = 0;
 
	if (offset > USER_EEPROM_SIZE)
		return 0;
 
	if (offset + count > USER_EEPROM_SIZE)
		count = USER_EEPROM_SIZE - offset;
 
	mutex_lock(&data->lock);
 
	dev_dbg(&client->dev, "cetc start read %ld@0x%lx ..\n", count, (unsigned long)offset);
 
	while (count > 0) {
		ssize_t	status = count>USER_XFER_MAX_COUNT?USER_XFER_MAX_COUNT:count;
		status = cetc_eeprom_read(client, buf, offset, status);
		if (status <= 0) {
			if (retval == 0)
				retval = status;
			break;
		}
		buf += status;
		offset += status;
		count -= status;
		retval += status;
	}
 
	dev_dbg(&client->dev, "cetc end read %ld@0x%lx  !\n", retval, (unsigned long)offset);
 
	mutex_unlock(&data->lock);
 
	return retval;
 
 }
 
static ssize_t cetc_eeprom_write(
	struct i2c_client *client,
	struct cetc_data *data,
	char *buf, unsigned offset, size_t count)
{
	struct i2c_msg msg[1];
	u8 *msgbuf;
	unsigned long timeout, transfer_time;
	unsigned int format_1;
	int status;
 
	memset(msg, 0, sizeof(msg));
	format_1 = offset >> 2;
 
	msgbuf = data->data;
 
	msgbuf[0] = (u8)((format_1 >> 24) & 0xff);
	msgbuf[1] = (u8)((format_1 >> 16) & 0xff);
	msgbuf[2] = (u8)((format_1 >>  8) & 0xff);
	msgbuf[3] = (u8)((format_1 >>  0) & 0xff);
	
	memcpy(msgbuf+4, buf, count);
 
	msg[0].addr = client->addr;
	msg[0].buf = msgbuf;
	msg[0].len = 4 + count;
 
	/*
	 * Reads fail if the previous write didn't complete yet. We may
	 * loop a few times until this one succeeds, waiting at least
	 * long enough for one entire page write to work.
	 */
	timeout = jiffies + msecs_to_jiffies(write_timeout);
	do {
		transfer_time = jiffies;
 
		status = i2c_transfer(client->adapter, msg, 1);
		if (status == 1)
			status = count;
 
		dev_dbg(&client->dev, "write %ld@0x%lx --> %d (%ld)\n",
				count, (unsigned long)offset, status, jiffies);
 
		if (status == count)
			return count;
 
		/* REVISIT: at HZ=100, this is sloooow */
		msleep(1);
	} while (time_before(transfer_time, timeout));
 
	return -ETIMEDOUT;
 }
 
static ssize_t cetc_write(struct file *filp, struct kobject *kobj,
			    struct bin_attribute *bin_attr,
			    char *buf, loff_t offset, size_t count)
{
	struct i2c_client *client = kobj_to_i2c_client(kobj);
	struct cetc_data *data = i2c_get_clientdata(client);
 
	ssize_t retval = 0;
 
	if (offset > USER_EEPROM_SIZE)
		return 0;
 
	if (offset + count > USER_EEPROM_SIZE)
		count = USER_EEPROM_SIZE - offset;
 
	mutex_lock(&data->lock);
 
	dev_dbg(&client->dev, "cetc start write %ld@0x%lx ..\n", count, (unsigned long)offset);
 
	while (count > 0) {
		ssize_t	status = count>USER_XFER_MAX_COUNT?USER_XFER_MAX_COUNT:count;
		status = cetc_eeprom_write(client, data, buf, offset, status);
		if (status <= 0) {
			if (retval == 0)
				retval = status;
			break;
		}
		buf += status;
		offset += status;
		count -= status;
		retval += status;
	}
 
	dev_dbg(&client->dev, "cetc end write %ld@0x%lx  !\n", retval, (unsigned long)offset);
 
	mutex_unlock(&data->lock);
 
	return retval;
 
 }
 
static struct bin_attribute user_eeprom_attr = {
	.attr = {
		.name = "eeprom",
		.mode = (S_IRUSR | S_IWUSR),
	},
	.size = USER_EEPROM_SIZE,
	.read = cetc_read,
	.write = cetc_write,
};
 
/* Return 0 if detection is successful, -ENODEV otherwise */
static int cetc_detect(struct i2c_client *client, struct i2c_board_info *info)
{
	struct i2c_adapter *adapter = client->adapter;
 
	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_dbg(&client->dev, "cetc detect error for BYTE access !\n");
		return -ENODEV;
	}
 
	strlcpy(info->type, "eeprom", I2C_NAME_SIZE);
 
	return 0;
 }
 
static int cetc_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = client->adapter;
	struct cetc_data *data;
	int err ;
 
	dev_notice(&client->dev, "cetc driver: " __DATE__ " " __TIME__ " \n" );
 
	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&client->dev, "cetc driver:  BYTE DATA not supported! \n" );
		return -ENODEV;
	}
 
	if (!(data = kzalloc(sizeof(struct cetc_data), GFP_KERNEL))) {
		dev_err(&client->dev, "cetc driver:  Memory alloc error ! \n" );
		return -ENOMEM;
	}
 
	/* alloc buffer */
	data->data = devm_kzalloc(&client->dev, USER_XFER_MAX_COUNT + 8, GFP_KERNEL);
	if (!data->data) {
		dev_err(&client->dev, "cetc driver:  Memory alloc error ! \n" );
		err = -ENOMEM;
		goto exit_kfree;
	}
 
	/* Init real i2c_client */
	i2c_set_clientdata(client, data);
	mutex_init(&data->lock);
 
	err = sysfs_create_bin_file(&client->dev.kobj, &user_eeprom_attr);
	if (err) {
		dev_err(&client->dev, "cetc driver:  sysfs create error ! \n" );
		goto exit_kfree;
	}
 
	return 0;
 
exit_kfree:
	if(data->data)
		kfree(data->data);
	kfree(data);
	return err;
 }
 
static int cetc_remove(struct i2c_client *client)
{
	struct cetc_data *data = i2c_get_clientdata(client);
 
	sysfs_remove_bin_file(&client->dev.kobj, &user_eeprom_attr);
	if(data->data)
		kfree(data->data);
	kfree(data);
 
	return 0;
 }
 
static const struct i2c_device_id cetc_id[] = {
	{ "cetc", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, cetc_id);
 
static struct i2c_driver cetc_driver = {
	.driver = {
		.name	= "cetc",
	},
	.probe		= cetc_probe,
	.remove		= cetc_remove,
	.id_table	= cetc_id,
 
	.class		= I2C_CLASS_SPD,
	.detect		= cetc_detect,
	.address_list	= cetc_i2c,
};
 
module_i2c_driver(cetc_driver);
 
MODULE_AUTHOR("Felven");
MODULE_DESCRIPTION("cetc driver");
MODULE_LICENSE("GPL");

将该驱动编进内核,然后修改devicetree指定好设备地址即可,这样系统启动起来就直接可用读写文件的通用接口进行访问了,上面的应用程序自然也就不需要了。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐