Linux·i2c驱动示例

I2C 是很常用的一个串行通信接口,常用于连接各种外设、传感器等器件。

一、Linux I2C 驱动框架

Linux 内核将 I2C 驱动分为两部分:
①、 I2C 总线驱动, I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动。
②、 I2C 设备驱动, I2C 设备驱动就是针对具体的 I2C 设备而编写的驱动。
一般 SOC 的 I2C 总线驱动都是由半导体厂商编写的,设备驱动开发者只要专注于 I2C 设备驱动即可。 
 
I2C 设备驱动重点关注两个数据结构: i2c_client 和 i2c_driver,i2c_client 就是描述设备信息的,         i2c_driver 描述驱动内容,类似于 platform_driver。
1)一个设备对应一个 i2c_client ,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个 i2c_client。
2) i2c_driver 类似 platform_driver,是我们编写 I2C 设备驱动重点要处理的内容。


对于我们 I2C 设备驱动编写人来说,重点工作就是构建 i2c_driver,构建完成以后需要向Linux 内核注册这个 i2c_driver。
注册i2c设备驱动 常使用 int i2c_register_driver,i2c_add_driver函数。
注销i2c 设备驱动使用i2c_del_driver 函数。 
 
i2c_driver 注册流程如下 
 

1 /* i2c 驱动的 probe 函数 */
2 static int xxx_probe(struct i2c_client *client,
const struct i2c_device_id *id) 
3 { 
4 /* 函数具体程序 */
5 return 0; 
6 } 
7 
8 /* i2c 驱动的 remove 函数 */
9 static int xxx_remove(struct i2c_client *client)
10 {
11 /* 函数具体程序 */
12 return 0;
13 }
14
15 /* 传统匹配方式 ID 列表 */
16 static const struct i2c_device_id xxx_id[] = {
17 {"xxx", 0}, 
18 {}
19 };
20
21 /* 设备树匹配列表 */
22 static const struct of_device_id xxx_of_match[] = {
23 { .compatible = "xxx" },
24 { /* Sentinel */ }
25 };
26
27 /* i2c 驱动结构体 */
28 static struct i2c_driver xxx_driver = {
29 .probe = xxx_probe,
30 .remove = xxx_remove,
31 .driver = {
32 .owner = THIS_MODULE,
33 .name = "xxx",
34 .of_match_table = xxx_of_match,
35 },
36 .id_table = xxx_id,
37 };
38 
39 /* 驱动入口函数 */
40 static int __init xxx_init(void)
41 {
42 int ret = 0;
43
44 ret = i2c_add_driver(&xxx_driver);
45 return ret;
46 }
47
48 /* 驱动出口函数 */
49 static void __exit xxx_exit(void)
50 {
51 i2c_del_driver(&xxx_driver);
52 }
53
54 module_init(xxx_init);
55 module_exit(xxx_exit);

设备和驱动的匹配过程是由 I2C 总线完成的,具体参考drivers/i2c/i2c-core.c 下 i2c_device_match函数。 

二、I2C 设备驱动编写流程

2.1 I2C 设备信息描述

1)未使用设备树的时候,使用 i2c_board_info 结构体来描述一个具体的 I2C 设备。type 和 addr 这两个成员变量是必须要设置的,一个是 I2C 设备的名字,一个是 I2C 设备的器件地址。

例如:OV2640 的 I2C 设备信息

 #define I2C_BOARD_INFO(dev_type, dev_addr) \
 .type = dev_type, .addr = (dev_addr)
 
 static struct i2c_board_info mx27_3ds_i2c_camera = {
 I2C_BOARD_INFO("ov2640", 0x30),
 };


2)使用设备树的时候,通过在设备树中创建相应的节点就行了。
例如:mag3110磁力计子节点

1 &i2c1 {
2 clock-frequency = <100000>;
3 pinctrl-names = "default"; 
4 pinctrl-0 = <&pinctrl_i2c1>;
5 status = "okay"; 
6 
7 mag3110@0e { 
8 compatible = "fsl,mag3110"; 
9 reg = <0x0e>;
10 position = <2>;
11 };
......
20 };

现在基本上都使用设备树


2.2 I2C 设备数据收发处理流程

通过 i2c_transfer 函数对I2C 设备寄存器进行读写操作。使用 i2c_transfer 函数发送数据之前要先构建好 i2c_msg。

示例代码: I2C 设备多寄存器数据读写 
 

1 /* 设备结构体 */
2 struct xxx_dev { 
3 ......
4 void *private_data; /* 私有数据,一般会设置为 i2c_client */
5 };
6 
7 /*
8 * @description : 读取 I2C 设备多个寄存器数据
9 * @param – dev : I2C 设备
10 * @param – reg : 要读取的寄存器首地址
11 * @param – val : 读取到的数据
12 * @param – len : 要读取的数据长度
13 * @return : 操作结果
14 */
15 static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val,int len)
16 {
17 int ret;
18 struct i2c_msg msg[2];
19 struct i2c_client *client = (struct i2c_client *)dev->private_data;
20
21 /* msg[0],第一条写消息,发送要读取的寄存器首地址 */
22 msg[0].addr = client->addr; /* I2C 器件地址 */
23 msg[0].flags = 0; /* 标记为发送数据 */
24 msg[0].buf = &reg; /* 读取的首地址 */
25 msg[0].len = 1; /* reg 长度 */
26
27 /* msg[1],第二条读消息,读取寄存器数据 */
28 msg[1].addr = client->addr; /* I2C 器件地址 */
29 msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
30 msg[1].buf = val; /* 读取数据缓冲区 */
31 msg[1].len = len; /* 要读取的数据长度 */
32
33 ret = i2c_transfer(client->adapter, msg, 2);
34 if(ret == 2) {
35 ret = 0;
36 } else {
37 ret = -EREMOTEIO;
38 }
39 return ret;
40 }
41
42 /*
43 * @description : 向 I2C 设备多个寄存器写入数据
44 * @param – dev : 要写入的设备结构体
45 * @param – reg : 要写入的寄存器首地址
46 * @param – buf : 要写入的数据缓冲区
47 * @param – len : 要写入的数据长度
48 * @return : 操作结果
49 */
50 static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf,u8 len)
51 {
52 u8 b[256];
53 struct i2c_msg msg;
54 struct i2c_client *client = (struct i2c_client *)dev->private_data;
55 
56 b[0] = reg; /* 寄存器首地址 */
57 memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */
58 
59 msg.addr = client->addr; /* I2C 器件地址 */
60 msg.flags = 0; /* 标记为写数据 */
61
62 msg.buf = b; /* 要发送的数据缓冲区 */
63 msg.len = len + 1; /* 要发送的数据长度 */
64
65 return i2c_transfer(client->adapter, &msg, 1);
66 }


 另外还有两个 API 函数分别用于 I2C 数据的收发操作,这两个函数最终都会调用i2c_transfer。
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)// I2C 数据发送函数
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)// I2C 数据接收函数


三、AP3216C三合一环境传感器驱动示例代码 

3.1设备树

 pinctrl_i2c1: i2c1grp {
            fsl,pins = <
                MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
                MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
            >;
        };
pinctrl_i2c1 就是 I2C1 的 IO 节点,这里将 UART4_TXD 和 UART4_RXD 这两个 IO 分别复用为 I2C1_SCL 和 I2C1_SDA ,电气属性都设置为 0x4001b8b0 。
&i2c1 {
    clock-frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";
 
        ap3216c@1e {
        compatible = "alientek,ap3216c";
        reg = <0x1e>;
    };
};


AP3216C 连接在 I2C1 上的,因此需要在 i2c1 节点下添加 ap3216c 的设备子节点。


3.2示例代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include "ap3216creg.h"
 
#define AP3216C_CNT 1
#define AP3216C_NAME "ap3216c"
 
struct ap3216c_dev
{
    int major;
    int minor;
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    void *private_data;
    unsigned short ir, als, ps;
};
 
static struct ap3216c_dev ap3216cdev;
 

 
/******************************I2C 设备数据收发处理***********************************/
 
/* 读取AP3216C的N个寄存器值 */
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{
    struct i2c_msg msg[2];
 
    struct i2c_client *client = (struct i2c_client *)dev->private_data;
 
    /* msg[0]发送要读取的寄存器首地址 */
    msg[0].addr = client->addr; /* 从机地址,也就是AP3216C地址*/
    msg[0].flags = 0;           /* 表示为要发送的数据*/
    msg[0].buf = &reg;          /* 表示为要发送的数据,也就是寄存器地址*/
    msg[0].len = 1;             /* 要发送的寄存器地址长度为1*/
 
    /* msg[1]读取数据 */
    msg[1].addr = client->addr; /* 从机地址,也就是AP3216C地址*/
    msg[1].flags = I2C_M_RD;    /* 表示读数据*/
    msg[1].buf = val;           /* 接收到的从机发送的数据*/
    msg[1].len = len;           /* 要读取的寄存器长度*/
 
    return i2c_transfer(client->adapter, msg, 2);
}
 
/* 向AP3216C写N个寄存器的数据 */
static int ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
    u8 b[256];
    struct i2c_msg msg;
 
    struct i2c_client *client = (struct i2c_client *)dev->private_data;
 
    /*构建要发送的数据,也就是寄存器首地址+实际的数据*/
    b[0] = reg;
    memcpy(&b[1], buf, len);
 
    /* msg[0]发送要读取的寄存器首地址 */
    msg.addr = client->addr; /* 从机地址,也就是AP3216C地址*/
    msg.flags = 0;           /* 表示为要发送的数据*/
    msg.buf = b;             /* 表示为要发送的数据,也就是寄存器地址*/
    msg.len = len + 1;       /* 要发送的数据长度:寄存器地址长度+实际的数据长度*/
 
    return i2c_transfer(client->adapter, &msg, 1);
}
 
/*读取AP3216C 一个寄存器*/
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
    u8 data = 0;
 
    ap3216c_read_regs(dev, reg, &data, 1);
 
    return data;
}
 
/*向AP3216C 一个寄存器写数据*/
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
    u8 buf = 0;
    buf = data;
    ap3216c_write_regs(dev, reg, &buf, 1);
}
 
/*ap3216c数据读取*/
void ap3216c_readdata(struct ap3216c_dev *dev)
{
    unsigned char buf[6] = {0};
    unsigned char i = 0;
 
    /*循环的读取数据*/
    for (i = 0; i < 6; i++)
    {
        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);
    }
 
    if (buf[0] & 0x80) /*为真表示IR 和PS数据无效*/
    {
        dev->ir = 0;
        dev->ps = 0;
    }
    else
    {
        dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0x03);
        dev->ps = (((unsigned short)buf[5] & 0x3F) << 4) | ((unsigned short)buf[4] & 0x0F);
    }
 
    dev->als = ((unsigned short)buf[3] << 8) | buf[2];
}
 
 
 
/*************************************文件操作集****************************************/
 
static int ap3216c_open(struct inode *inode, struct file *filp)
{
    unsigned char value = 0;
 
    filp->private_data = &ap3216cdev;
 
    /*初始化AP3216C*/
    ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x4); /* 复位 */
    mdelay(50);
    ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x3); /* 复位 */
 
    value = ap3216c_read_reg(&ap3216cdev, AP3216C_SYSTEMCONG);
    printk("AP3216C_SYSTEMCONG=%#x\r\n", value);
 
    return 0;
}
 
static int ap3216c_release(struct inode *inode, struct file *filp)
{
    //struct ap3216c_dev *dev = (struct ap3216c_dev*)filp->private_data;
    printk("ap3216c_release\r\n");
    return 0;
}
 
ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    long err = 0;
    short data[3];
 
    struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
 
    /*向应用返回AP3216C的原始数据*/
    ap3216c_readdata(dev);
    data[0] = dev->ir;
    data[1] = dev->als;
    data[2] = dev->ps;
 
    err = copy_to_user(buf, data, sizeof(data));
    return 0;
}
 
static const struct file_operations ap3216c_fops = {
    .owner = THIS_MODULE,
    .open = ap3216c_open,
    .read = ap3216c_read,
    .release = ap3216c_release,
};
 
 
/*************************************i2c设备驱动注册与注销*****************************/
 
/*
 * @description : i2c 驱动的 probe 函数,当驱动与
 * 设备匹配以后此函数就会执行
 * @param - client : i2c 设备
 * @param - id : i2c 设备 ID
 * @return : 0,成功;其他负值,失败
 */
static int ap3216c_probe(struct i2c_client *client,
                         const struct i2c_device_id *id)
{
    int ret = 0;
    printk("ap3216c_probe!\r\n");
 
    /* 搭建字符设备驱动框架,在/dev/下 */
    /* 1、构建设备号 */
    ap3216cdev.major = 0; /* 由系统分配主设备号 */
 
    if (ap3216cdev.major)
    { /* 给定主设备号 */
        ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
        ret = register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
    }
    else
    { /* 没有给定主设备号 */
        ret = alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
        ap3216cdev.major = MAJOR(ap3216cdev.devid);
        ap3216cdev.minor = MINOR(ap3216cdev.devid);
    }
    if (ret < 0)
    {
        printk("ap3216c chrdev_region err!\r\n");
        goto fail_devid;
    }
    printk("ap3216c major=%d, minor=%d\r\n", ap3216cdev.major, ap3216cdev.minor);
 
    /* 2、注册设备 */
    ap3216cdev.cdev.owner = THIS_MODULE;
    cdev_init(&ap3216cdev.cdev, &ap3216c_fops);
    ret = cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
    if (ret < 0)
    {
        goto fail_cdev;
    }
 
    /******** 自动创建设备节点 *******/
 
    /* 3、创建类 */
    ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
    if (IS_ERR(ap3216cdev.class))
    {
        ret = PTR_ERR(ap3216cdev.class);
        goto fail_class;
    }
 
    /* 4、创建设备 */
    ap3216cdev.device = device_create(ap3216cdev.class, NULL,
                                      ap3216cdev.devid, NULL, AP3216C_NAME);
    if (IS_ERR(ap3216cdev.device))
    {
        ret = PTR_ERR(ap3216cdev.device);
        goto fail_device;
    }
 
    ap3216cdev.private_data = client;
 
    return 0;
 
fail_device:
    class_destroy(ap3216cdev.class);
fail_class:
    cdev_del(&ap3216cdev.cdev);
fail_cdev:
    unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
fail_devid:
    return ret;
}
 
/*
* @description : i2c 驱动的 remove 函数,移除 i2c 驱动此函数会执行
* @param – client : i2c 设备
* @return : 0,成功;其他负值,失败
*/
static int ap3216c_remove(struct i2c_client *client)
{
    /* 1,删除字符设备 */
    cdev_del(&ap3216cdev.cdev);
 
    /* 2,注销设备号 */
    unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
 
    /* 3,摧毁设备 */
    device_destroy(ap3216cdev.class, ap3216cdev.devid);
 
    /* 4,摧毁类 */
    class_destroy(ap3216cdev.class);
 
    return 0;
}
 
/* 传统匹配方式 ID 列表 */
static struct i2c_device_id ap3216c_id[] = {
    {"alientek,ap3216c", 0},
    {}
 
};
 
/* 设备树匹配表 */
static const struct of_device_id ap3216c_of_match[] = {
    {
        .compatible = "alientek,ap3216c",
    },
    {}
 
};
 
/* i2c_driver */
static struct i2c_driver ap3216c_driver = {
    .probe = ap3216c_probe,
    .remove = ap3216c_remove,
    .driver = {
        .name = "ap3216c",
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(ap3216c_of_match),
    },
    .id_table = ap3216c_id,
};
 
/*驱动入口函数*/
static int __init ap3216c_init(void)
{
    int ret;
    ret = i2c_add_driver(&ap3216c_driver);
    if (ret != 0)
    {
        pr_err("AP3216C I2C registration failed %d\n", ret);
        return ret;
    }
    return 0;
}
 
/*驱动出口函数*/
static void __exit ap3216c_exit(void)
{
    i2c_del_driver(&ap3216c_driver);
}
 
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("supersmart");


示例代码主要分三大块

  1. i2c设备驱动注册与注销
  2. 文件操作函数集
  3. I2C 设备数据收发处理


原文链接:https://blog.csdn.net/shengnan89/article/details/124270291

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值