Zynq-Linux移植学习笔记之37-交换互联芯片I2C驱动
·
最近参与了一款交换互联芯片的驱动开发设计,其中有一个需求是通过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指定好设备地址即可,这样系统启动起来就直接可用读写文件的通用接口进行访问了,上面的应用程序自然也就不需要了。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)