I2C 是NXP公司设计的一种总线协议,其使用两条数据线在主控制器和从机之间进行数据通信。一条是 SCL(串行时钟线),另外一条是 SDA(串行数据线),这两条数据线需要接上拉电阻,即总线空闲的时候 SCL 和 SDA 处于高电平。I2C协议包括起始信号、终止信号和应答信号等。I2C 支持一个主控制器下挂载多个从设备,主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C设备。
在Linux中,I2C驱动框架主要包括I2C总线驱动、I2C设备驱动和I2C核心三部分。I2C 总线和 platform 总线类似,只是platform 总线是虚拟出来的一条总线,而 I2C 总线是实际存在的。对于使用 I2C 通信的设备,在驱动中直接使用 I2C 总线挂载即可。I2C 总线驱动的重点是 I2C 适配器,也即SOC 的 I2C 接口控制器,主要涉及到两个结构体:i2c_adapter 和 i2c_algorithm。在 Linux 内核中用 i2c_adapter 结构体来表示 I2C 适配器。i2c_algorithm 则提供I2C 适配器与 IIC 设备进行通信的方法,也即API函数。
在 I2C 设备驱动中主要也有两个重要的结构体:i2c_client 和 i2c_driver。i2c_client 用来描述设备信息,一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个 i2c_client。i2c_driver 用来描述驱动具体内容,类似于 platform_driver。
I2C核心层完成的则是I2C设备和I2C驱动的匹配过程。当设备和驱动匹配成功后,会执行 probe 函数,probe 函数中就可以选择执行字符设备驱动的一套流程了。初始化 I2C 设备即对 I2C 设备寄存器进行特定的读写操作,要用到i2c_transfer 函数实现数据收发。
本次实验采用设备树方式实现,从机选用开发板上的RTC芯片hym8563,其挂载在i2c4下面。本次实验仅使用该芯片进行I2C驱动通信的测试,不涉及RTC驱动相关内容。
第一步,设备树配置
hym8563挂载在i2c4下面,设备树中需要在i2c4节点下面添加hym8563相关配置,其器件地址为0x51,在主设备树中配置如下:
&i2c4 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c4m1_xfer>;
hym8563: hym8563@51 {
compatible = "haoyu,hym8563";
reg = <0x51>;
clock-frequency = <32768>;
clock-output-names = "hym8563";
};
};
由于开发板内核本身自带有hym8563的原厂驱动,为了实验顺利进行,需要在内核中将该驱动进行屏蔽。通过编译脚本打开图形化配置界面,在Device Drivers下面找到HYM8563相关配置项,取消其选中,如图所示,再次编译内核并烧写。

第二步,驱动程序编写
驱动程序采用字符设备流程实现读写hym8563的日期数据寄存器,包括秒,分,时等7个寄存器,在打开文件时分别向这些寄存器中写入1-7的数据,然后再分别由应用程序读出来并打印,验证I2C驱动的数据收发功能。
#define HYM8563_SEC 0x02
#define HYM8563_MIN 0x03
#define HYM8563_HOUR 0x04
#define HYM8563_DAY 0x05
#define HYM8563_WEEKDAY 0x06
#define HYM8563_MONTH 0x07
#define HYM8563_YEAR 0x08
#define HYM8563_CNT 1
#define HYM8563_NAME "hym8563"
struct hym8563_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
unsigned char time[7];
};
static struct hym8563_dev hym8563cdev;
//读寄存器函数
static int hym8563_read_reg(struct hym8563_dev *dev,u8 reg_addr,int len)
{
u8 data;
int ret;
struct i2c_client *hym8563_client = (struct i2c_client *)dev->private_data;
struct i2c_msg msgs[] = {
[0] = {
.addr = hym8563_client->addr,
.flags = 0,
.len = sizeof(reg_addr),
.buf = ®_addr,
},
[1] = {
.addr = hym8563_client->addr,
.flags = 1,
.len = sizeof(data),
.buf = &data,
},
};
ret = i2c_transfer(hym8563_client->adapter, msgs, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d\n",ret);
ret = -EREMOTEIO;
}
return data;
}
//写寄存器函数
static void hym8563_write_reg(struct hym8563_dev *dev,u8 reg_addr, u8 data, u8 len)
{
u8 buff[256];
struct i2c_client *hym8563_client = (struct i2c_client *)dev->private_data;
struct i2c_msg msgs[] = {
[0] = {
.addr = hym8563_client->addr,
.flags = 0,
.len = len + 1,
.buf = buff,
},
};
buff[0] = reg_addr;
memcpy(&buff[1], &data, len);
i2c_transfer(hym8563_client->adapter, msgs, 1);
}
void hym8563_readdata(struct hym8563_dev *dev)
{
unsigned char i =0;
unsigned char buf[7];
/* 循环读取所有数据 */
for(i = 0; i < 7; i++)
{
buf[i] = hym8563_read_reg(dev, HYM8563_SEC + i,1);
dev->time[i] = buf[i];
}
}
static int hym8563_open(struct inode *inode, struct file *filp)
{
unsigned char i = 0;
filp->private_data = &hym8563cdev;
/* 循环写入所有数据 */
for(i = 0;i < 7;i++)
{
hym8563_write_reg(&hym8563cdev,HYM8563_SEC + i,i+1,1);
}
return 0;
}
static ssize_t hym8563_read(struct file *filp, char __user *buf,size_t cnt, loff_t *off)
{
unsigned char data[7];
long err = 0;
unsigned char i = 0;
struct hym8563_dev *dev = (struct hym8563_dev *)filp->private_data;
hym8563_readdata(dev);
for(i = 0;i < 7;i++)
{
data[i] = dev->time[i];
}
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
static int hym8563_release(struct inode *inode, struct file *filp)
{
return 0;
}
static const struct file_operations hym8563c_ops = {
.owner = THIS_MODULE,
.open = hym8563_open,
.read = hym8563_read,
.release = hym8563_release,
};
static const struct of_device_id hym8563_id[] = {
{.compatible = "haoyu,hym8563", 0},
{}};
int hym8563_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id)
{
printk("This is hym8563_probe\n");
/* 1、构建设备号 */
if (hym8563cdev.major) {
hym8563cdev.devid = MKDEV(hym8563cdev.major, 0);
register_chrdev_region(hym8563cdev.devid, HYM8563_CNT,HYM8563_NAME);
} else {
alloc_chrdev_region(&hym8563cdev.devid, 0, HYM8563_CNT,HYM8563_NAME);
hym8563cdev.major = MAJOR(hym8563cdev.devid);
}
/* 2、注册设备 */
cdev_init(&hym8563cdev.cdev, &hym8563c_ops);
cdev_add(&hym8563cdev.cdev, hym8563cdev.devid, HYM8563_CNT);
/* 3、创建类 */
hym8563cdev.class = class_create(THIS_MODULE, HYM8563_NAME);
if (IS_ERR(hym8563cdev.class)) {
return PTR_ERR(hym8563cdev.class);
}
/* 4、创建设备 */
hym8563cdev.device = device_create(hym8563cdev.class, NULL,hym8563cdev.devid, NULL, HYM8563_NAME);
if (IS_ERR(hym8563cdev.device)) {
return PTR_ERR(hym8563cdev.device);
}
hym8563cdev.private_data = i2c_client;
return 0;
}
int hym8563_remove(struct i2c_client *i2c_client)
{
/* 删除设备 */
cdev_del(&hym8563cdev.cdev);
unregister_chrdev_region(hym8563cdev.devid, HYM8563_CNT);
/* 注销掉类和设备 */
device_destroy(hym8563cdev.class, hym8563cdev.devid);
class_destroy(hym8563cdev.class);
printk("Remove sucessed!\n");
return 0;
}
//定义一个 i2c_driver 的结构体
static struct i2c_driver hym8563_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "hym8563_test",
.of_match_table = hym8563_id,
},
.probe = hym8563_probe,
.remove = hym8563_remove,
.id_table = hym8563_id_ts
};
static int hym8563_driver_init(void)
{
int ret;
// 注册 i2c_driver
ret = i2c_add_driver(&hym8563_driver);
if (ret < 0)
{
printk(" i2c_add_driver is error \n");
return ret;
}
printk("This is hym8563_driver_init\n");
return 0;
}
static void hym8563_driver_exit(void)
{
// 注销 i2c_driver
i2c_del_driver(&hym8563_driver);
printk("This is hym8563_driver_exit\n");
}
module_init(hym8563_driver_init);
module_exit(hym8563_driver_exit);
MODULE_LICENSE("GPL");
第三步,测试程序编写
测试程序实现通过I2C读取上述7个寄存器中的值并打印,来验证I2C驱动写入数据是否成功。
int main(int argc, char *argv[])
{
int fd;
char *filename;
unsigned char databuf[7];
unsigned char sec, min, hour, day, week, mon, year;
int ret = 0;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("can't open file %s\r\n", filename);
return -1;
}
while (1) {
ret = read(fd, databuf, sizeof(databuf));
if(ret == 0) {
sec = databuf[0];
min = databuf[1];
hour = databuf[2];
day = databuf[3];
week = databuf[4];
mon = databuf[5];
year = databuf[6];
printf("sec = %d, min = %d, hour = %d, day = %d, week = %d, mon = %d, year = %d\r\n", sec, min, hour, day, week, mon, year);
}
usleep(200000); /* 200ms */
}
close(fd);
return 0;
}
第四步,运行测试
将驱动编译成功的模块文件和测试程序一并拷入开发板,在开发板上使用gcc工具编译测试程序。

查看hym8563设备是否存在

加载驱动

可以看出驱动成功加载并打印出提示信息,运行测试程序验证数据是否写入

可以看出,每次运行测试程序都会对上述寄存器写入1-7这7个数据,而读取出来的秒寄存器值会不断累加,间隔正好接近一秒(测试程序每0.2秒进行一次打印),这可能是由于RTC芯片工作后就会启动秒计时,其具体原因本篇不作分析。接下来卸载驱动。

总结
此次实验通过裁剪内核驱动,利用开发板自带的RTC芯片编写I2C驱动,实现了I2C从机读写寄存器,数据通信的功能。
1298

被折叠的 条评论
为什么被折叠?



