1、 背景介绍
板子上通过I2C总线与zynq相连的是三片1848
如上图所示,zynq通过I2C总线与3片CPS-1848交换芯片相连,3片1848芯片的I2C地址分别为2,4,8.
目前zynq上Linux I2C驱动采用的是i2c-cadence(drivers/i2c/buses),对应于i2c驱动中的bus driver(总线驱动,也称为适配器驱动)。需要实现的是i2c驱动中的设备驱动,类似于eeprom驱动(drivers/misc/eeprom)。
2、devicetree配置
706的devicetree中关于I2C的部分如下:
i2c@e0004000 {
compatible = "cdns,i2c-r1p10";
status = "okay";
clocks = <0x1 0x26>;
interrupt-parent = <0x3>;
interrupts = <0x0 0x19 0x4>;
reg = <0xe0004000 0x1000>;
#address-cells = <0x1>;
#size-cells = <0x0>;
clock-frequency = <0x61a80>;
pinctrl-names = "default";
pinctrl-0 = <0xb>;
i2cswitch@74 {
compatible = "nxp,pca9548";
#address-cells = <0x1>;
#size-cells = <0x0>;
reg = <0x74>;
i2c@0 {
#address-cells = <0x1>;
#size-cells = <0x0>;
reg = <0x0>;
clock-generator@5d {
#clock-cells = <0x0>;
compatible = "silabs,si570";
temperature-stability = <0x32>;
reg = <0x5d>;
factory-fout = <0x9502f90>;
clock-frequency = <0x8d9ee20>;
};
};
i2c@1 {
#address-cells = <0x1>;
#size-cells = <0x0>;
reg = <0x1>;
hdmi-tx@39 {
compatible = "adi,adv7511";
reg = <0x39>;
adi,input-depth = <0x8>;
adi,input-colorspace = "yuv422";
adi,input-clock = "1x";
adi,input-style = <0x3>;
adi,input-justification = "evenly";
};
};
i2c@2 {
#address-cells = <0x1>;
#size-cells = <0x0>;
reg = <0x2>;
eeprom@54 {
compatible = "at,24c08";
reg = <0x54>;
};
};
i2c@3 {
#address-cells = <0x1>;
#size-cells = <0x0>;
reg = <0x3>;
gpio@21 {
compatible = "ti,tca6416";
reg = <0x21>;
gpio-controller;
#gpio-cells = <0x2>;
};
};
i2c@4 {
#address-cells = <0x1>;
#size-cells = <0x0>;
reg = <0x4>;
rtc@51 {
compatible = "nxp,pcf8563";
reg = <0x51>;
};
};
i2c@7 {
#address-cells = <0x1>;
#size-cells = <0x0>;
reg = <0x7>;
ucd90120@65 {
compatible = "ti,ucd90120";
reg = <0x65>;
};
};
};
};
参考706中的devicetree,通过i2c控制三片1848, devicetree格式修改如下:
i2c@e0004000 {
compatible = "cdns,i2c-r1p10";
status = "okay";
clocks = <0x1 0x26>;
interrupt-parent = <0x3>;
interrupts = <0x0 0x19 0x4>;
reg = <0xe0004000 0x1000>;
#address-cells = <0x1>;
#size-cells = <0x0>;
clock-frequency = <0x61a80>;
cps1848@2 {
compatible = "cps1848";
reg = <0x2>;
};
cps1848@4 {
compatible = "cps1848";
reg = <0x4>;
};
cps1848@8 {
compatible = "cps1848";
reg = <0x8>;
};
};
如上就配置好了三个地址分别为2,4,8的设备,暂时给它们起名叫cps1848.
3、 kernel配置
kernel中xilinx已经实现了i2c的bus driver,我们只需要实现device driver。由于eeprom为标准i2c设备,可以将eeprom为模板实现1848的设备驱动。修改过程中注意匹配设备的name和驱动id_table中的name,设备name就是devicetree中的cps1848.
实现后的cps1848代码如下:
/*
* CPS1848 bus driver
*
* Copyright (C) 2014 CGT 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
#include
#include
#include
#include
#include
#include
/* Each client has this additional data */
#define USER_EEPROM_SIZE 0xFFFF48
#define USER_XFER_MAX_COUNT 0x8
/* Addresses to scan */
static const unsigned short cps1848_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 cps1848_data {
struct mutex lock;
u8 *data;
};
static ssize_t cps1848_eeprom_read( struct i2c_client *client,
char *buf, unsigned offset, size_t count)
{
struct i2c_msg msg[2];
u8 msgbuf[4];
unsigned long timeout, transfer_time;
int status;
memset(msg, 0, sizeof(msg));
msgbuf[0] = (u8)((offset >> 18) & 0x3f);
msgbuf[1] = (u8)((offset >> 10) & 0xff);
msgbuf[2] = (u8)((offset >> 2) & 0xff);
msg[0].addr = client->addr;
msg[0].buf = msgbuf;
msg[0].len = 3;
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 cps1848_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 cps1848_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, "cps1848 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 = cps1848_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, "cps1848 end read %ld@0x%lx !\n", retval, (unsigned long)offset);
mutex_unlock(&data->lock);
return retval;
}
static ssize_t cps1848_eeprom_write(
struct i2c_client *client,
struct cps1848_data *data,
char *buf, unsigned offset, size_t count)
{
struct i2c_msg msg[1];
u8 *msgbuf;
unsigned long timeout, transfer_time;
int status;
memset(msg, 0, sizeof(msg));
msgbuf = data->data;
msgbuf[0] = (u8)((offset >> 18) & 0x3f);
msgbuf[1] = (u8)((offset >> 10) & 0xff);
msgbuf[2] = (u8)((offset >> 2) & 0xff);
memcpy(msgbuf+3, buf, count);
msg[0].addr = client->addr;
msg[0].buf = msgbuf;
msg[0].len = 3 + 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 cps1848_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 cps1848_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, "cps1848 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 = cps1848_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, "cps1848 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 = cps1848_read,
.write = cps1848_write,
};
/* Return 0 if detection is successful, -ENODEV otherwise */
static int cps1848_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, "cps1848 detect error for BYTE access !\n");
return -ENODEV;
}
strlcpy(info->type, "eeprom", I2C_NAME_SIZE);
return 0;
}
static int cps1848_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = client->adapter;
struct cps1848_data *data;
int err ;
dev_notice(&client->dev, "CPS1848 driver: " __DATE__ " " __TIME__ " \n" );
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&client->dev, "CPS1848 driver: BYTE DATA not supported! \n" );
return -ENODEV;
}
if (!(data = kzalloc(sizeof(struct cps1848_data), GFP_KERNEL))) {
dev_err(&client->dev, "CPS1848 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, "CPS1848 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, "CPS1848 driver: sysfs create error ! \n" );
goto exit_kfree;
}
return 0;
exit_kfree:
if(data->data)
kfree(data->data);
kfree(data);
return err;
}
static int cps1848_remove(struct i2c_client *client)
{
struct cps1848_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 cps1848_id[] = {
{ "cps1848", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, cps1848_id);
static struct i2c_driver cps1848_driver = {
.driver = {
.name = "cps1848",
},
.probe = cps1848_probe,
.remove = cps1848_remove,
.id_table = cps1848_id,
.class = I2C_CLASS_SPD,
.detect = cps1848_detect,
.address_list = cps1848_i2c,
};
module_i2c_driver(cps1848_driver);
MODULE_AUTHOR("RobinLee");
MODULE_DESCRIPTION("CPS1848 driver");
MODULE_LICENSE("GPL");
将该代码命名为i2c-1848放在drivers/i2c/muxes下
修改muxes的Kconfig文件以及Makefile文件,加入针对1848的配置选项
在编译内核菜单中能看到新增加配置选项
选择以后进行编译,这样kernel配置就完成了。
4、 i2c驱动测试
系统上电启动,加载devicetree,kernel,uramdisk,能看到kernel启动时已经加载了cps1848驱动。
上图中列出了系统检测到的三个i2c设备,名称为cps1848,地址为0002,0004,0008.
为了针对cps1848进行测试,首先要知道三片1848在系统中的位置(在linux中所有设备都是以文件形式挂载)。最终在sys/class/i2c-dev/i2c-0/device下找到了三个设备。
根据该设备修改测试程序(app-cps1848.tar.gz 下载地址见:http://download.csdn.net/detail/jj12345jj198999/9837954 )中的文件位置
app-cps1848/cps1848/app/cps1848.c
编译测试程,得到cps1848可执行文件
将cps1848放进uramdisk中,参考:rootfs修改
产生新的uramdisk.image.gz,重新加载linux
在/usr/sbin下运行cps1848,进入cps1848控制界面
输入get 0,获取1848地址为0寄存器的值,该值为0x38007403
查看cps1848的datasheet,发现值确实是这个,大小端颠倒一下。
重复上述测试过程,再测试0002和0004位置的1848,最终实现对1848驱动的测试。
有奖活动 | |
---|---|
【有奖活动】分享技术经验,兑换京东卡 | |
话不多说,快进群! | |
请大声喊出:我要开发板! | |
【有奖活动】EEPW网站征稿正在进行时,欢迎踊跃投稿啦 | |
奖!发布技术笔记,技术评测贴换取您心仪的礼品 | |
打赏了!打赏了!打赏了! |
打赏帖 | |
---|---|
vscode+cmake搭建雅特力AT32L021开发环境被打赏30分 | |
【换取逻辑分析仪】自制底板并驱动ArduinoNanoRP2040ConnectLCD扩展板被打赏47分 | |
【分享评测,赢取加热台】RISC-V GCC 内嵌汇编使用被打赏38分 | |
【换取逻辑分析仪】-基于ADI单片机MAX78000的简易MP3音乐播放器被打赏48分 | |
我想要一部加热台+树莓派PICO驱动AHT10被打赏38分 | |
【换取逻辑分析仪】-硬件SPI驱动OLED屏幕被打赏36分 | |
换逻辑分析仪+上下拉与多路选择器被打赏29分 | |
Let'sdo第3期任务合集被打赏50分 | |
换逻辑分析仪+Verilog三态门被打赏27分 | |
换逻辑分析仪+Verilog多输出门被打赏24分 |