Linux I2C 驱动实验编写与测试步骤-基于正点原子IMX6ULL开发板

1 原理图分析

IMX6ULL开发板使用了一个AP3216C的三合一环境光传感器,此设备连接在SOC的I2C1 接口,原理图如下:
Linux I2C 驱动实验编写与测试步骤-基于正点原子IMX6ULL开发板_第1张图片

2修改设备树

2.1 IO 修改或添加

AP3216C 用到了 I2C1 接口,I.MX6U-ALPHA 开发板上的 I2C1 接口使用到了 UART4_TXD 和UART4_RXD,因此肯定要在设备树里面设置这两个 IO。如果要用到 AP3216C 的中断功能的话还需初始化 AP_INT 对应的 GIO1_IO01 这个 IO,本次实验我们不使用中断功能。因此只需要设置 UART4_TXD 和 UART4_RXD 这两个 IO,NXP 其实已经将他这两个 IO 设置好了,打开 imx6ull-alientek-emmc.dts,然后找到如下内容:

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。

2.2在 i2c1 节点追加 ap3216c 子节点

AP3216C 是连接到 I2C1 上的,因此需要在 i2c1 节点下添加 ap3216c 的设备子节点,在imx6ull-alientek-emmc.dts 文件中找到 i2c1 节点,将NXP 官方的 EVK 开发板上的mag3110和fxls8471子节点删除,添加ap3216c设备子节点。

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

		ap3216c@1e {
		compatible = "alientek,ap3216c";
		reg = <0x1e>;
	};
};

设备树修改完成以后使用“make dtbs”重新编译一下,然后使用新的设备树启动 Linux 内核。进入/sys/bus/i2c/devices。
在这里插入图片描述
图中的“0-001e”就是 ap3216c 的设备目录,“1e”就是 ap3216c 器件地址。进入0-001e 目录,可以看到“name”文件,name 问价就保存着此设备名字,在这里就是“ap3216c”。
在这里插入图片描述

3 驱动程序编写

3.1 新建工程和文件

新建iic文件夹,在文件夹下新建ap3216c.c 和 ap3216creg.h两个文件,ap3216c.c 为 AP3216C 的
驱动代码,ap3216creg.h 是 AP3216C 寄存器头文件。

3.2 ap3216creg.h

在ap3216creg.h 文件中输入如下内容

#ifndef AP3216C_H
#define AP3216C_H

/* 寄存器地址 */
#define AP3216C_SYSTEMCONG  0X00
#define AP3216C_INTSTATUS   0X01
#define AP3216C_INTCLEAR    0X02
#define AP3216C_IRDATALOW   0X0A
#define AP3216C_IRDATAHIGH  0X0B
#define AP3216C_ALSDATALOW  0X0C
#define AP3216C_ALSDATAHIGH 0X0D
#define AP3216C_PSDATALOW   0X0E
#define AP3216C_PSDATAHIGH  0X0F


#endif 

3.3 ap3216c驱动程序编写

ap3216c.c 驱动程序编写步骤如下

第一步:添加头文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "ap3216creg.h"

第二步:编写驱动入口函数和驱动出口函数


/*驱动入口函数*/
static int __init ap3216c_init(void)
{
   return 0;
}

/*驱动出口函数*/
static void __exit ap3216c_exit(void)
{
   
}

module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("supersmart");

第三步:添加和删除ap3216c_driver

static struct i2c_driver ap3216c_driver = {

};

/*驱动入口函数*/
static int __init ap3216c_init(void)
{
    int ret = 0;
    i2c_add_driver(&ap3216c_driver);

    return ret;
}

/*驱动出口函数*/
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");

i2c_driver 注册函数原型:

int i2c_register_driver(struct module *owner, 
 					struct i2c_driver *driver)

#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)					

i2c_driver注销函数原型:

void i2c_del_driver(struct i2c_driver *driver)

第四步:完成ap3216c_driver

static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{

    return 0;
}

static int ap3216c_remove(struct i2c_client *client)
{
    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",
    },
    {}

};

static struct i2c_driver ap3216c_driver = {
    .probe = ap3216c_probe,//当 I2C 设备和 I2C 驱动匹配成功以后 probe 函数就会执行,这些和 platform 驱动一样,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 = 0;
    i2c_add_driver(&ap3216c_driver);

    return ret;
}

/*驱动出口函数*/
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");

第五步:完成“probe”和“remove”函数

1、ap3216c_probe// i2c 驱动的 probe 函数,当驱动与设备匹配以后此函数就会执行
2、ap3216c_remove // i2c 驱动的 remove 函数,移除 i2c 驱动此函数会执行


#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;

static const struct file_operations ap3216c_fops = {

};

/*
 * @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",
    },
    {}

};

static struct i2c_driver ap3216c_driver = {
    .probe = ap3216c_probe, //当 I2C 设备和 I2C 驱动匹配成功以后 probe 函数就会执行,这些和 platform 驱动一样,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 = 0;
    i2c_add_driver(&ap3216c_driver);

    return ret;
}

/*驱动出口函数*/
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");

第六步:完成file_operations 文件操作集 ap3216c_fops结构体成员变量

1、ap3216c_open //打开设备
2、ap3216c_read //从设备读取数据
3、ap3216c_release //关闭/释放设备

#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;

/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int ap3216c_open(struct inode *inode, struct file *filp)
{
    unsigned char value = 0;

    filp->private_data = &ap3216cdev;

    return 0;
}

/*
* @description : 从设备读取数据
* @param – filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param – offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
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;

    return 0;
}

/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int ap3216c_release(struct inode *inode, struct file *filp)
{

    printk("ap3216c_release\r\n");
    return 0;
}

/* AP3216C 操作函数 */
static const struct file_operations ap3216c_fops = {
    .owner = THIS_MODULE,
    .open = ap3216c_open,
    .read = ap3216c_read,
    .release = ap3216c_release,
};

/*
 * @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",
    },
    {}

};

static struct i2c_driver ap3216c_driver = {
    .probe = ap3216c_probe, //当 I2C 设备和 I2C 驱动匹配成功以后 probe 函数就会执行,这些和 platform 驱动一样,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 = 0;
    i2c_add_driver(&ap3216c_driver);

    return ret;
}

/*驱动出口函数*/
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");

第七步:添加AP3216C的读写寄存器函数

1、ap3216c_read_regs //从 ap3216c 读取多个寄存器数据
2、ap3216c_write_regs // 向 ap3216c 多个寄存器写入数据
3、ap3216c_read_reg //读取 ap3216c 指定寄存器值,读取一个寄存器
4、ap3216c_write_reg //向 ap3216c 指定寄存器写入指定的值,写一个寄存器

重点使用的函数i2c_transfer 函数原型如下:

int i2c_transfer(struct i2c_adapter *adap, 
					struct i2c_msg *msgs, 
								int num)

函数参数和返回值含义如下:
adap:所使用的 I2C 适配器,i2c_client 会保存其对应的 i2c_adapter。
msgs:I2C 要发送的一个或多个消息。
num:消息数量,也就是 msgs 的数量。
返回值:负值,失败,其他非负值,发送的 msgs 数量。

#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;


/*
* @description : 从 ap3216c 读取多个寄存器数据
* @param – dev : ap3216c 设备
* @param – reg : 要读取的寄存器首地址
* @param – val : 读取到的数据
* @param – len : 要读取的数据长度
* @return : 操作结果
*/
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);
}

/*
* @description : 向 ap3216c 多个寄存器写入数据
* @param – dev : ap3216c 设备
* @param – reg : 要写入的寄存器首地址
* @param – val : 要写入的数据缓冲区
* @param – len : 要写入的数据长度
* @return : 操作结果
*/

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);
}

/*
* @description : 读取 ap3216c 指定寄存器值,读取一个寄存器
* @param – dev : ap3216c 设备
* @param – reg : 要读取的寄存器
* @return : 读取到的寄存器值
*/
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
    u8 data = 0;

    ap3216c_read_regs(dev, reg, &data, 1);

    return data;
}

/*
* @description : 向 ap3216c 指定寄存器写入指定的值,写一个寄存器
* @param – dev : ap3216c 设备
* @param – reg : 要写的寄存器
* @param – data : 要写入的值
* @return : 无
*/
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);
}


/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int ap3216c_open(struct inode *inode, struct file *filp)
{
    unsigned char value = 0;

    filp->private_data = &ap3216cdev;

    return 0;
}

/*
* @description : 从设备读取数据
* @param – filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param – offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
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;

    return 0;
}

/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int ap3216c_release(struct inode *inode, struct file *filp)
{

    printk("ap3216c_release\r\n");
    return 0;
}

/* AP3216C 操作函数 */
static const struct file_operations ap3216c_fops = {
    .owner = THIS_MODULE,
    .open = ap3216c_open,
    .read = ap3216c_read,
    .release = ap3216c_release,
};

/*
 * @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",
    },
    {}

};

static struct i2c_driver ap3216c_driver = {
    .probe = ap3216c_probe, //当 I2C 设备和 I2C 驱动匹配成功以后 probe 函数就会执行,这些和 platform 驱动一样,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 = 0;
    i2c_add_driver(&ap3216c_driver);

    return ret;
}

/*驱动出口函数*/
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");

第八步:完善ap3216c驱动

1、增加读取 ap3216c 的数据函数
2、在ap3216c_open函数中增加初始化ap3216c部分代码
3、在 ap3216c_read函数中增加向应用层返回ap3216c原始数据代码


#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;

/*
* @description : 从 ap3216c 读取多个寄存器数据
* @param – dev : ap3216c 设备
* @param – reg : 要读取的寄存器首地址
* @param – val : 读取到的数据
* @param – len : 要读取的数据长度
* @return : 操作结果
*/
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);
}

/*
* @description : 向 ap3216c 多个寄存器写入数据
* @param – dev : ap3216c 设备
* @param – reg : 要写入的寄存器首地址
* @param – val : 要写入的数据缓冲区
* @param – len : 要写入的数据长度
* @return : 操作结果
*/

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);
}

/*
* @description : 读取 ap3216c 指定寄存器值,读取一个寄存器
* @param – dev : ap3216c 设备
* @param – reg : 要读取的寄存器
* @return : 读取到的寄存器值
*/
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
    u8 data = 0;

    ap3216c_read_regs(dev, reg, &data, 1);

    return data;
}

/*
* @description : 向 ap3216c 指定寄存器写入指定的值,写一个寄存器
* @param – dev : ap3216c 设备
* @param – reg : 要写的寄存器
* @param – data : 要写入的值
* @return : 无
*/
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);
}

/*
* @description : 读取 AP3216C 的数据,读取原始数据,包括 ALS,PS 和 IR, 
* :同时打开 ALS,IR+PS 的话两次数据读取的间隔要大于 112.5ms
* @param - ir : ir 数据
* @param - ps : ps 数据
* @param - ps : als 数据
* @return : 无。
*/
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];
}

/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
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;
}

/*
* @description : 从设备读取数据
* @param – filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param – offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
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;
}

/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int ap3216c_release(struct inode *inode, struct file *filp)
{

    printk("ap3216c_release\r\n");
    return 0;
}

/* AP3216C 操作函数 */
static const struct file_operations ap3216c_fops = {
    .owner = THIS_MODULE,
    .open = ap3216c_open,
    .read = ap3216c_read,
    .release = ap3216c_release,
};

/*
 * @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",
    },
    {}

};

static struct i2c_driver ap3216c_driver = {
    .probe = ap3216c_probe, //当 I2C 设备和 I2C 驱动匹配成功以后 probe 函数就会执行,这些和 platform 驱动一样,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 = 0;
    i2c_add_driver(&ap3216c_driver);

    return ret;
}

/*驱动出口函数*/
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");

4应用测试程序编写

ap3216cAPP.c 应用测试程序内容如下

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*
 *argc:应用程序参数个数
 * argv[]:具体的参数内容,字符串形式
 * ./ap3216cAPP  
 * ./ap3216cAPP /dev/ap3216c
 */

int main(int argc, char *argv[])
{
    int fd, err;
    char *filename;
    unsigned short data[3];
    unsigned short ir, ps, als;

    if (argc != 2)
    {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("file %s open failed!\r\n", filename);
        return -1;
    }

    while (1)
    {
        err = read(fd, data, sizeof(data));
        if (err == 0)
        {
            ir = data[0];
            als = data[1];
            ps = data[2];
            printf("AP3216C ir=%d,als=%d,ps=%d\r\n",ir,als,ps);
        }
        usleep(500000);/* 延时200ms */
    }
    close(fd);

    return 0;
}

5运行测试

5.1编译驱动程序和测试APP

5.2运行测试

将上一小节编译出来 ap3216c.ko 和 ap3216cAPP 这两个文件拷贝到 rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录 lib/modules/4.1.15 中。加载 ap3216c.ko 这个驱动模块。
在这里插入图片描述
输入如下命令读取数据:
Linux I2C 驱动实验编写与测试步骤-基于正点原子IMX6ULL开发板_第2张图片
用手电筒照射数据变化如下:
Linux I2C 驱动实验编写与测试步骤-基于正点原子IMX6ULL开发板_第3张图片

你可能感兴趣的:(arm,linux,c++,vscode,功能测试)