Linux I2C总线框架(RX8010SJ rtc时钟添加到zynq)
Linux内核开发者为了让驱动开发工程师可以在内核中方便添加自己的I2C设备的驱动程序,从而可以更容易的在linux下驱动自己的I2C接口硬件,进而引入I2C总线框架,与linux下的platform不同,I2C是实际的物理总线。
I2C 简介
I2C是很常见的一种总线协议,由NXP公司设计。I2C使用两条线在主控制器和从机之间进行数据通信。一条是SCL(串行时钟线),另一条是SDA(串行数据线),这两条数据线需要接上拉电阻,总线空闲的时候SCL和SDA处于高电平。I2C总线标准模式下速度可以达到100kb/s,快速模式下可以达到500kb/s。
I2C总线工作是按照一定协议来的,接下来介绍I2C的协议:
起始位
起始位,也就是I2C通信起始标志为,通过这个起始位就可以告诉I2C从机,要开始进行I2C通信。
在SCL为高电平的时候,SDA出现下降沿就表示为起始位
停止位
停止位就是停止I2C通信的标志位,和起始位的功能相反。在SCL位高电平的时候,SDA出现上升沿就表示为停止位
数据传输
I2C总线在数据传输的过程中要保证SCL高电平期间,SDA数据稳定,因此SDA上的数据变化只能在SCL低电平期间发生
应答信号
当I2C主机发送完8位数据以后会将SDA设置为输入状态,等待I2C从机应答,也就是等到I2C从机告诉主机它接受到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完8位数据以后紧跟着一个时钟信号就是给应答信号使用的,从机通过将SDA拉低来表示发出应答信号。
I2C写时序
主机通过I2C总线与从机之间通信不外乎两个操作:写和读,I2C总线写时序的步骤:
- 开始信号
- 发送I2C设备地址,每个I2C器件都有一个设备地址,通过发送具体的设备地址来决定访问哪一个I2C器件。这是一个8位数据,高7位是设备地址,最后1位是读写位,为1的话表示这是一个读操作,为0的话表示一个写操作
- I2C器件后面跟着一个读写为,为0表示写操作,为1表示读操作
- 从机发送的ACK应答信号
- 重新发送开始信号
- 发送要写入数据的寄存器地址
- 从机发送的ACK应答信号
- 发送要写入寄存器的数据
- 从机发送的ACK应答信号
- 停止信号
I2C读时序
I2C单字节读时序比写时序要复杂一点,首先发送设备地址,然后发送要读取的寄存器地址,重新发送设备地址,最后一步就是I2C从器件输出要读取的寄存器值,具体看一下这一步
- 主机发送起始信号
- 主机发送要读取的I2C从设备地址
- 读写控制位,因为是向I2C设备发送数据,因此是写信号
- 从机发送的ACK应答信号
- 重新发送START信号
- 主机发送要读取的寄存器地址
- 从机发送的ACK应答信号
- 重新发送START信号
- 重新发送要读取的I2C从设备地址
- 读写控制位,这里是读信号,表示接下来是I2C从设备里面读取数据
- 从机发送的ACK应答信号
- 从I2C器件里面读取到的数据
- 主机发出NO ACK信号,表示读取完成,不需要从机发送ACK信号
- 主机发送STOP信号,停止I2C通信
rtc时钟
这里用的是RX8010SJ rtc时钟关于它的数据手册后续会更新具体阐述。
Linux I2C 总线框架简介
使用逻辑的方式编写一个I2C器件的驱动程序,我们一般要实现两部分:
- I2C主机驱动
- I2C设备驱动
I2C主机驱动也就是soc的I2C控制器对应的驱动程序,I2C设备驱动其实就是挂在I2C总线下的具体设备对应的驱动程序,例如eeprom 触摸屏IC 传感器IC等等。对于主机驱动来说,一旦编写完成就不要进行修改,其他的I2C设备直接调用主机驱动提供的API函数完成读写操作就好。这个符合Linux驱动分离与分层的思想,因此Linux内核也将I2C驱动分为两部分。
为了让驱动开发工程师在内核中添加自己的I2C设备的驱动程序,从而可以更容易的在Linux下驱动自己的I2C接口器件,进而引入I2C总线框架,我们一般也叫做I2C子系统。
I2C总线驱动
I2C总线驱动重点是I2C适配器驱动,这里用到两个重要的数据结构
- i2c_adapter
- i2c_algorithm
I2C子系统将Soc的I2C适配器抽象成一个i2c_adapter结构体,结构体定义在include/linux/i2c.h文件中,具体内容如下
struct i2c_adapter{
struct module *owner;
unsigned int class;
/* 这个是I2C适配器 I2C适配器与IIC设备进行通信的方法 */
const struct i2c_algorithm *algo;
void *algo_data;
const struct i2c_lock_operations *lock_ops;
struct rt_mutex bus_lock;
struct rt_mutex mux_lock;
int timeout;
int retries;
struct device dev;
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
struct irq_domain *host_notify_domain;
}
I2C 总线设备
IIC设备驱动重点关注两个数据结构:i2c_client 和 i2c_driver,结构体定义在include/linux/i2c.h文件中
struct i2c_client{
unsigned short flags;
unsigned short addr;
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter;
struct device dev;
int irq;
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb;
#endif
};
I2C总线下的从设备对应一个i2c_client结构体变量,每检测到一个I2C从设备就会给这个设备分配一个i2c_client。
struct i2c_driver{
unsigned int class;
int(*attach_adapter)(struct i2c_adapter *)__deprecated;
int(*probe)(struct i2c_client *,const struct i2c_device_id *);
int(*remove)(struct i2c_client *);
int(*probe_new)(struct i2c_client *);
void(*alert)(struct i2c_client *,enum i2c_alert_protocol protocol,unsigned int data);
int (*command)(struct i2c_client *client,unsigned int cmd,void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
int(*detect)(struct i2c_client *,struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
bool disable_i2c_core_irq_mapping;
};
对于I2C设备驱动编写人员来说,重点工作就是构建i2c_driver,构建完成以后需要向I2C子系统注册这个i2c_driver。i2c_driver注册函数
int i2c_register_driver(struct module *owner,
struct i2c_driver *driver);
#define i2c_add_driver(driver)\
i2c_register_dirver(THIS_MODULE,driver)
/* 也就是说宏定义来实现i2c_driver注册 */
同样注销I2C设备驱动也需要用到注销函数
vodi i2c_del_driver(struct i2c_driver *driver);
总结
i2c_driver 的注册实例代码
/* i2c驱动的Probe函数 */
static int xxx_probe(struct i2c_client *client,const struct i2c_device_id *id){
/* 函数具体程序 */
return 0;
}
/* i2c 驱动的remove函数 */
static int rx8010sj_remove(struct i2c_client *client){
/* 函数具体程序 */
return 0;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id xxx_id[] = {
{"xxx",0},
{/**/}
};
/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{.compatible = "xxx"},
{/* Sentinel */}
};
/* i2c驱动结构体 */
static struct i2c_driver xxx_driver = {
.probe = xxx_probe,
.remove = rx8010sj_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
.of_match_table = xxx_of_match,
},
.id_table = xxx_id,
};
/* 驱动入口函数 */
static int __init(void){
int ret = 0;
ret = i2c_add_driver(&xxx_driver);
return ret;
}
/* 驱动出口函数 */
static void __exit(void){
i2c_del_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
/* 或者使用 module_i2c_driver(xxx_driver) */
pcf8563驱动编写
这里引用正点原子的pcf8563驱动编写来系统学习I2C设备驱动编写
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/cdev.h>
#include <linux/bcd.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "pcf8563"
#define
#define PCF8563_CTL_STATUS_1 0X00
#define PCF8563_CTL_STATUS_2 0X01
#define PCF8563_VL_SECONDS 0X02
#define PCF8563_MINUTES 0X03
#define PCF8563_HOURS 0X04
#define PCF8563_DAYS 0X05
#define PCF8563_WEEKDAYS 0X06
#define PCF8563_CENTURY_MONTHS 0X07
#define PCF8563_YEARS 0X08
#define YEAR_BASE 2000
/* 自定义结构体 表示时间和日期信息 */
struct pcf8563_time{
int sec;
int min;
int hour;
int day;
int wday;
int mon;
int year;
};
/* 自定义结构体pcf8563_dev 描述pcf8563设备 */
struct pcf8563_dev{
struct i2c_client *client;
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
};
static pcf8563_dev pcf8563;
static int pcf8563_write_reg(struct pcf8563_dev *dev,u8 reg,u8 *buf,u8 len){
struct i2c_client *client = dev->client;
struct i2c_msg msg;
u8 send_buf[17] = {0};
int ret;
if(16 < len){
dev_err(&client->dev,"%s:error:Invalid transfer byte length %d\n",
__func__,len);
return -EINVAL;
}
send_buf[0] = reg;
memcpy(&send_buf[1],buf,len);
msg.addr = client->addr;
msg.flags = client->flags;
msg.buf = send_buf;
msg.len = len + 1;
ret = i2c_transfer(client->adapter,&msg,1);
if(1 != ret){
dev_err(&client->dev,"%s:error reg=0x%x,len=0x%x\n",__func__,reg,len);
return -EIO;
}
return 0;
}
static int pcf8563_read_reg(struct pcf8563_dev *dev,u8 reg,u8 *buf,u8 len){
struct i2c_client *client = dev->client;
struct i2c_msg msg[2];
int ret;
/* msg[0] 发送信息*/
msg[0].addr = client->addr;
msg[0].flags = client->flags;
msg[0].buf = ®
msg[0].len = 1;
/* msg[1] 接受信息 */
msg[1].addr = client->addr;
msg[1].flags = client->flags | I2C_M_RD;
msg[1].buf = buf;
msg[1].len = len;
ret = i2c_transfer(client->adapter,msg,2);
if(2!=reg){
dev_err(&client->dev,"%s:error reg=0x%x,len=0x%x\n",__func__,reg,len);
return -EIO;
}
return 0;
}
static int pcf8563_open(struct inode *inode,struct file *filp){
filp->private_data = &pcf8563;
return 0;
}
static ssize_t pcf8563_read(struct file *filp,char __user *buf,size_t cnt,loff_t *off){
struct pcf8563_dev *dev = filp->private_data;
struct i2c_client *client = dev->client;
struct pcf8563_time time = {0};
u8 read_buff[9] = {0};
int ret;
ret = pcf8563_read_reg(dev,PCF8563_CTL_STATUS_1,read_buf,9);
if(ret)
return ret;
/* 校验时钟完整性 */
if(read_buff[PCF8563_VL_SECONDS] & 0X80){
dev_err(&client->dev,
"low voltage detected,data / time is not reliable\n");
return -EINVAL;
}
time.sec = bcd2bin(read_buf[PCF8563_VL_SECONDS] & 0x7F);
time.min = bcd2bin(read_buf[PCF8563_MINUTES] & 0x7F);
time.hour= bcd2bin(read_buf[PCF8563_HOURS] & 0x3F);
time.day = bcd2bin(read_buf[PCF8563_DAYS] & 0x3F);
time.wday= read_buf[PCF8563_WEEKDAYS] & 0x07;
time.mon = bcd2bin(read_buf[PCF8563_CENTURY_MONTHS] & 0x1F);
time.year= bcd2bin(read_buf[PCF8563_YEARS] + YEAR_BASE);
return copy_to_user(buf,&time,sizeof(struct pcf8563_time));
}
static ssize_t pcf8563_write(struct file *filp,const char __user *buf,
size_t cnt,loff_t *offt){
struct pcf8563_dev *dev = filp->private_data;
struct pcf8563_time time = {0};
u8 write_buf[9] = {0};
int ret;
ret = copy_from_user(&time,buf,cnt);
if(0 > ret)
return -EFAULT;
write_buf[pcf8563_VL_SECONDS] = bin2bcd(time.sec);
write_buf[pcf8563_MINUTES] = bin2bcd(time.min);
write_buf[pcf8563_HOURS] = bin2bcd(time,hour);
write_buf[pcf8563_DAYS] = bin2bcd(time.day);
write_buf[pcf8563_WEEKDAYS] = time.wday & 0x07;
write_buf[pcf8563_CENTURY_MONTHS] = bin2bcd(time.mon);
write_buf[pcf8563_YEARS] = bin2bcd(time.year % 100);
ret = pcf8563_write_reg(dev,PCF8563_VL_SECONDS,&write_buf[PCF8563_VL_SECONDS],7);
if(ret)
return ret;
return cnt;
}
static int pcf8563_release(struct inode *inode,struct file *filp){
return 0;
}
static const struct file_operations pcf8563_ops = {
.owner = THIS_MODULE,
.open = pcf8563_open,
.read = pcf8563_read,
.write = pcf8563_write,
.release= pcf8563_release,
};
/* 对pcf8563设备进行初始化 */
static int pcf8563_init(struct pcf8563_dev *dev){
u8 val;
int ret;
/* 读 VL_SECONDS 寄存器 */
ret = pcf8563_read_reg(dev,PCF8563_VL_SECONDS,&val,1);
if(ret)
return ret;
val &= 0x7F;
return pcf8563_write_reg(dev,PCF8563_VL_SECONDS,&val,1);
}
static int pcf8563_probe(struct i2c_client *client,const struct i2c_device_id *id){
int ret;
/* 初始化8563 */
pcf8563.client = client;
ret = pcf8563_init(&pcf8563);
if(ret)
return ret;
/* 申请设备号 */
ret = alloc_chrdev_region(&pcf8563.devid,0,1,DEVICE_NAME);
if(ret)
return ret;
/* 初始化字符设备cdev */
pcf8563.cdev.owner = THIS_MODULE;
cdev_init(&pcf8563.cdev,pcf8563_ops);
/* 添加字符设备cdev */
cdev_add(*pcf8563.cdev,&pcf8563.devid,1);
if(ret)
goto out1;
/* 创建类class */
pcf8563.class = class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(pcf8563.class)){
ret = PTR_ERR(pcf8563.class);
goto out2;
}
/* 创建设备 */
pcf8563.device = device_create(pcf8563.class,&client->dev,pcf8563.devid,NULL,DEVICE_NAME);
if(IS_ERR(pcf8563.device)){
ret = PTR_ERR(pcf8563.device);
goto out3;
}
i2c_set_clientdata(client,&pcf8563);
return 0;
out3:
class_destroy(pcf8563.class);
out2:
cdev_del(&pcf8563.cdev);
out1:
unregister_chrdev_region(pcf8563.devid,1);
return ret;
}
static int pcf8563_remove(struct i2c_client *client){
struct pcf8563_dev *pcf8563 = i2c_get_clientdata(client);
/* 注销设备 */
device_destroy(pcf8563->class,pcf8563->devid);
/* 注销类 */
class_destroy(pcf8563->class);
/* 删除cdev */
cdev_del(&pcf8563->cdev);
/* 注销设备号 */
unregister_chrdev_region(pcf8563->devid,1);
return 0;
}
/* 匹配列表 */
static const struct of_device_id pcf8563_of_match[] = {
{.compatible = "zynq-pcf8563"},
{/* Sentinel */}
};
static struct i2c_driver pcf8563_driver = {
.driver = {
.name = "pcf8563",
.of_match_table = pcf8563_of_match,
},
.probe = pcf8563_probe,
.remove = pcf8563_remove,
};
module_i2c_driver(pcf8563_driver);
MODULE_AUTHOR("");
MODULE_DESCRIPTION("PCF8563 RTC driver");
MODULE_LICENSE("GPL");
总结
关键结构体
struct pcf8563_dev{
struct i2c_client *client;
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
};
static struct pcf8563_dev pcf8563;
软件逻辑
- module_i2c_driver 注册IIC设备
- of_device_id pcf8563_of_match[] = {};
- static struct i2c_driver pcf8563_driver = {};
- 注册完IIC设备后,对应执行设备驱动,开始执行pcf8563_probe()函数
- 首先初始化pcf8563设备
- 申请设备号
- 初始化字符设备
- 添加cdev设备
- 创建类
- 创建设备
- 将自定义的pcf8563结构体保存到client
这一部分驱动还是相对而言比较简单的,但是还是需要好好理解。这样才可以在项目中学习到东西,然后一直进步!