IIC 设备linux驱动编写

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出现下降沿就表示为起始位

IIC起始位

停止位

停止位就是停止I2C通信的标志位,和起始位的功能相反。在SCL位高电平的时候,SDA出现上升沿就表示为停止位

IIC停止位

数据传输

I2C总线在数据传输的过程中要保证SCL高电平期间,SDA数据稳定,因此SDA上的数据变化只能在SCL低电平期间发生

IIC数据变化

应答信号

当I2C主机发送完8位数据以后会将SDA设置为输入状态,等待I2C从机应答,也就是等到I2C从机告诉主机它接受到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完8位数据以后紧跟着一个时钟信号就是给应答信号使用的,从机通过将SDA拉低来表示发出应答信号。

I2C写时序

主机通过I2C总线与从机之间通信不外乎两个操作:写和读,I2C总线写时序的步骤:

  1. 开始信号
  2. 发送I2C设备地址,每个I2C器件都有一个设备地址,通过发送具体的设备地址来决定访问哪一个I2C器件。这是一个8位数据,高7位是设备地址,最后1位是读写位,为1的话表示这是一个读操作,为0的话表示一个写操作
  3. I2C器件后面跟着一个读写为,为0表示写操作,为1表示读操作
  4. 从机发送的ACK应答信号
  5. 重新发送开始信号
  6. 发送要写入数据的寄存器地址
  7. 从机发送的ACK应答信号
  8. 发送要写入寄存器的数据
  9. 从机发送的ACK应答信号
  10. 停止信号
I2C读时序

I2C单字节读时序比写时序要复杂一点,首先发送设备地址,然后发送要读取的寄存器地址,重新发送设备地址,最后一步就是I2C从器件输出要读取的寄存器值,具体看一下这一步

  1. 主机发送起始信号
  2. 主机发送要读取的I2C从设备地址
  3. 读写控制位,因为是向I2C设备发送数据,因此是写信号
  4. 从机发送的ACK应答信号
  5. 重新发送START信号
  6. 主机发送要读取的寄存器地址
  7. 从机发送的ACK应答信号
  8. 重新发送START信号
  9. 重新发送要读取的I2C从设备地址
  10. 读写控制位,这里是读信号,表示接下来是I2C从设备里面读取数据
  11. 从机发送的ACK应答信号
  12. 从I2C器件里面读取到的数据
  13. 主机发出NO ACK信号,表示读取完成,不需要从机发送ACK信号
  14. 主机发送STOP信号,停止I2C通信
rtc时钟

这里用的是RX8010SJ rtc时钟关于它的数据手册后续会更新具体阐述。

Linux I2C 总线框架简介

使用逻辑的方式编写一个I2C器件的驱动程序,我们一般要实现两部分:

  1. I2C主机驱动
  2. I2C设备驱动

I2C主机驱动也就是soc的I2C控制器对应的驱动程序,I2C设备驱动其实就是挂在I2C总线下的具体设备对应的驱动程序,例如eeprom 触摸屏IC 传感器IC等等。对于主机驱动来说,一旦编写完成就不要进行修改,其他的I2C设备直接调用主机驱动提供的API函数完成读写操作就好。这个符合Linux驱动分离与分层的思想,因此Linux内核也将I2C驱动分为两部分。

为了让驱动开发工程师在内核中添加自己的I2C设备的驱动程序,从而可以更容易的在Linux下驱动自己的I2C接口器件,进而引入I2C总线框架,我们一般也叫做I2C子系统。

I2C总线驱动

I2C总线驱动重点是I2C适配器驱动,这里用到两个重要的数据结构

  1. i2c_adapter
  2. 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 = &reg;
    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;

软件逻辑

  1. module_i2c_driver 注册IIC设备
    1. of_device_id pcf8563_of_match[] = {};
    2. static struct i2c_driver pcf8563_driver = {};
  2. 注册完IIC设备后,对应执行设备驱动,开始执行pcf8563_probe()函数
    1. 首先初始化pcf8563设备
    2. 申请设备号
    3. 初始化字符设备
    4. 添加cdev设备
    5. 创建类
    6. 创建设备
    7. 将自定义的pcf8563结构体保存到client

这一部分驱动还是相对而言比较简单的,但是还是需要好好理解。这样才可以在项目中学习到东西,然后一直进步!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值