MX6ULL学习笔记(十一)I2C设备驱动
前言
????????I2C 是很常用的一个串行通信接口,用于连接各种外设、传感器等器件,本章我们来学习一下如何在 Linux 下开发 I2C 接口器件驱动。本章以 I.MX6U-ALPHA 开发板上的 AP3216C 这个三合一环境光传感器为例,通过 AP3216C 讲解 一下如何编写 Linux 下的 I2C 设备驱动程序。
目录
一、Linux I2C 驱动框架简介
前面我们说过,linux内核驱动遵循分离与分层的思想,也就是Linux中的总线-驱动-设备模型,也就是常说的驱动分离,如图所示,那么i2c也不例外。
当向系统注册一个驱动时,总线会在右侧的设备中查找,看看有没有与之匹配的设备,有的话就将两者联系起来;当向系统中注册一个设备时,总线会在左侧的驱动中查找,看有没有与之匹配的驱动,有的话也联系起来。
下面三层是通过总线-设备-驱动模型融合到一起的I2C驱动。
这里可以看到,Linux 内核将 I2C 驱动分为两部分:
①、I2C 总线驱动,I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做I2C适配器驱动。
②、I2C 设备驱动,I2C 设备驱动就是针对具体的 I2C 设备而编写的驱动。
二、Linux I2C 驱动框架分析。
I2C 总线驱动
针对I2C总线,也有一个I2C总线的结构体即i2c_bus_type结构,结构体定义在 i2c-core.c 文件中,
此结构里面有一个匹配函数,i2c_device_match函数。
>>如果不使用设备树的话,i2c_device_match函数通过id_table(i2c_driver结构的成员,它表示能支持哪些设备)来比较dev与drv是否匹配,
????????具体方法是用id_table的name去比较,name分别是i2c_client结构和i2c_driver结构的成员。如果名字相同,就表示此驱动i2c_driver能支持这个设备i2c_client。id_table 是传统的、未使用设备树的设备匹配 ID 表。
>>如果使用设备树的话,需要设置 device_driver 的 of_match_table 成员变量,也就是驱动的兼容(compatible)属性。这里的匹配方法和id_table类似。
2. i2c_client? -- 设备信息
已经知道,i2c_client 结构体定义在 include/linux/i2c.h 文件中,内容如下:
一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个
i2c_client。这个结构体是注册i2c_client时加入的,不但要加入这些结构体,还会在总线的驱动链表中一个一个地比较drv即i2c_driver来判断是否有匹配的,如果匹配将调用drv里面的probe函数,匹配函数由总线提供;
?????? 一般这部分是不需要我们去初始化的,因为我们引入了设备树的时候,关于i2
C设备的信息就在设备树描述好了,在内核启动的时候会读取设备树,这时i2c_client就初始化好了,当我们去注册i2c_driver,就可以得到这个i2c_client结构体的信息了。
3. i2c_driver? -- 驱动内容
i2c_driver 类似 platform_driver,是我们编写 I2C 设备驱动重点要处理的内容,i2c_driver 结构体定义在 include/linux/i2c.h 文件中,内容如下:
这个结构体是注册i2c_driver时加入的,不但要加入这些结构体,还会在总线的设备链表中一个一个地比较dev即比较i2c_client来判断是否有匹配的,如果匹配将调用drv里面的probe函数。
- 第170行,当 I2C设备和驱动匹配成功以后 probe函数就会执行,和platform驱动一样。
- 第188行,device_driver 驱动结构体,如果使用设备树的话,需要设置 device_driver 的 of_match_table 成员变量,也就是驱动的兼容(compatible)属性。
- 第 189 行,id_table 是传统的、未使用设备树的设备匹配 ID 表。
????????对于我们 I2C 设备驱动编写人来说,重点工作就是构建 i2c_driver,构建完成以后需要向 Linux 内核注册这个 i2c_driver。当设备和驱动匹配以后 i2c_driver 里面的 probe 函数就会执行,probe 函数里面所做的就 是字符设备驱动那一套了。
????????一般需要在 probe 函数里面初始化 I2C 设备,通过读写操作,对I2C 设备寄存器进行配置,就像在stm32的时候初始化oled屏幕的时候,进行的初始化一样。
三、I2c相关API
1、I2C驱动注册函数。
①i2c_driver 的注册函数为 int i2c_register_driver,此函数原型如下:
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
函数参数和返回值含义如下:
- owner:一般为 THIS_MODULE。
- driver:要注册的 i2c_driver。
- 返回值:0,成功;负值,失败。
②封装过的i2c_driver注册函数
????????i2c_add_driver 就是对 i2c_register_driver 做了一个简单的封装,只有一个参数,就是要注册 的 i2c_driver。
#define i2c_add_driver(driver) \
?i2c_register_driver(THIS_MODULE, driver)
参数:
- driver:要注册的 i2c_driver。
- 返回值:0,成功;负值,失败。
2、I2C 设备驱动注销API。
①注销 I2C 设备驱动
将前面注册的 i2c_driver 从 Linux 内核中注销掉,需要用到 i2c_del_driver 函数,此函数原型如下:
void i2c_del_driver(struct i2c_driver *driver)
函数参数和返回值含义如下:
driver:要注销的 i2c_driver。
3. I2C 设备寄存器读写函数
????????涉及到I2C 设备就必须要知道I2C的两个操作,读和写,必须能够对 I2C 设备寄存器进行读写操作,这样我们才能使用这个I2C设备,这里就要用到 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 数量。
4.i2c_driver 的注册示例代码
示例代码 61.1.2.4 i2c_driver 注册流程
/* i2c 驱动的 probe 函数 */
?static int xxx_probe(struct i2c_client *client,
const struct i2c_device_id *id)
?{
? /* 函数具体程序 */
? return 0;
?}
?/* i2c 驱动的 remove 函数 */
static int xxx_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 = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
? .of_match_table = xxx_of_match,
},
.id_table = xxx_id,
?};
?/* 驱动入口函数 */
?static int __init xxx_init(void)
?{
??? int ret = 0;
??? ret = i2c_add_driver(&xxx_driver);
return ret;
?}
?/* 驱动出口函数 */
?static void __exit xxx_exit(void)
?{
??? i2c_del_driver(&xxx_driver);
?}
?module_init(xxx_init);
?module_exit(xxx_exit);
四、MX6U 的 I2C 适配器驱动分析
上面我们讲解了 Linux 下的 I2C 驱动框架,重点分为 I2C 适配器驱动和 I2C 设备驱动, 其中 I2C 适配器驱动就是 SOC 的 I2C 控制器驱动。I2C 设备驱动是需要用户根据不同的 I2C 设 备去编写,而 I2C 适配器驱动一般都是 SOC 厂商去编写的,比如NXP 就编写好了 I.MX6U 的 I2C 适配器驱动。在 imx6ull.dtsi 文件中找到 I.MX6U 的 I2C1 控制器节点,节点内容如下所示:
重点关注 i2c1 节点的 compatible 属性值,因为通过 compatible 属性值可以在 Linux 源码里 面找到对应的驱动文件。这里i2c1节点的compatible属性值有两个:“fsl,imx6ul-i2c”和“fsl,imx21- i2c”,在 Linux 源码中搜索这两个字符串即可找到对应的驱动文件。I.MX6U 的 I2C 适配器驱动 驱动文件为 drivers/i2c/busses/i2c-imx.c,在此文件中有如下内容:
I2C 适配器驱动 SOC 厂商已经替我们编写好了,所以这部分我们了解就好。
五、设备驱动编写流程
接下来来学习一下 I2C 设备驱动的详细编写流程,这部分才是我们要学习的重点。
按照分层的思想,在总线-驱动-设备模型中,总线部分,也就是I2C 适配器驱动这部分,SOC 厂商已经替我们编写好了,我们只需要完善设备信息描述和驱动内容的编写就好了,
1.I2C 设备信息描述
前面说过,在引入设备树之后,我们的设备信息描述,就统一使用在设备树下创建相应的节点就行了。比如 NXP 官方的 EVK 开发 板在 I2C1 上接了 mag3110 这个磁力计芯片,因此必须在 i2c1 节点下创建 mag3110 子节点,然 后在这个子节点内描述 mag3110 这个芯片的相关信息。打开 imx6ull-14x14-evk.dts 这个设备树 文件,然后找到如下内容:
&i2c1 {
clock-frequency = <100000>;?
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;? status = "okay";
? mag3110@0e {
?? compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
??};
......
};
第 7~11 行,向 i2c1 添加 mag3110 子节点,
第 7 行“mag3110@0e”是子节点名字,“@” 后面的“0e”就是 mag3110 的 I2C 器件地址。第 8 行设置 compatible 属性值为“fsl,mag3110”。
第 9 行的 reg 属性也是设置 mag3110 的器件地址的,因此值为 0x0e。I2C 设备节点的创建重点 是 compatible 属性和 reg 属性的设置,一个用于匹配驱动,一个用于设置器件地址。
2.驱动内容的编写
????????I2C 设备驱动首先要做的就是初始化 i2c_driver 并向 Linux 内核 注册。当设备和驱动匹配以后 i2c_driver 里面的 probe 函数就会执行,probe 函数里面所做的就是字符设备驱动那一套了。
一般需要在 probe 函数里面初始化 I2C 设备,要初始化 I2C 设备就使用函数,对I2C 设备寄存器进行读写操作。
接下来就是简单讲解一下驱动内容的编写
①首先还是得先定义一个驱动结构体,也就是上面说过的i2c_driver.
②建立完结构体就先来编写设备树匹配列表和传统匹配方式ID列表
③然后就是完善编写probe函数:函数原型:
static int ap3216c_probe(struct i2c_client *i2c,
????????????????????? ?const struct i2c_device_id *id)
probe 函数里面所做的就 是字符设备驱动那一套了。创建设备号, 初始化cdev, 添加一个cdev, 创建设备类, 创建设备.
④、然后就是完善编写remove函数,函数原型:
static int ap3216c_remove(struct i2c_client *i2c)
里面要做的也还是和字符设备那一套一样,删除设备,注销掉类和设备。
六、具体设备树的修改
1、IO 修改或添加
? ? ? ? 根据原理图,我们可以得知ap3216c的i2c两个引脚接在了UART4_TXD 和 UART4_RXD 这两个 IO,因此只需要设置 UART4_TXD 和 UART4_RXD 这两个 IO,NXP 其实已经将 他这两个 IO 设置好了,打开 imx6ull-alientek-emmc.dts,然后找到如下内容: ????????
????????pinctrl_i2c1 就是 I2C1 的 IO 节点,这里将 UART4_TXD 和 UART4_RXD 这两个 IO 分别 复用为 I2C1_SCL 和 I2C1_SDA,电气属性都设置为 0x4001b8b0。
2、在 i2c1 节点追加 ap3216c 子节点
????????AP3216C 是连接到 I2C1 上的,因此需要在 i2c1 节点下添加 ap3216c 的设备子节点,在
imx6ull-alientek-emmc.dts 文件中找到 i2c1 节点,此节点添加后内容如下:
? ? ? ? ?本来?i2c1 节点里面有mag3110 和 fxls8471 这两个 I2C 子节点,但是那是NXP官方的开发板自的,我们不需要就删除了,然后添加我们自己的节点就好了。
七、驱动代码的编写
①定义一个I2c驱动结构体
/*-------------------------------------------------------------------------*/
static struct i2c_driver ap3216c_driver = {
.driver = {
.name = "ap3216c",
.owner = THIS_MODULE,
.of_match_table = ap3216c_of_match,
},
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.id_table =ap3216c_id ,
};
②建立完结构体就先来编写设备树匹配列表和传统匹配方式ID列表。
static const struct i2c_device_id ap3216c_id[]={
{ "alientek,ap3216c", 0 },
{ }
};
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "alientek,ap3216c" },
{/* Sentinel */ }
};
③然后就是完善编写probe函数。
static int ap3216c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
/*1 创建设备号*/
if(ap3216cdev.major){
ap3216cdev.devid = MKDEV(ap3216cdev.major,0);
register_chrdev_region(ap3216cdev.devid,DEVICE_CNT,DEVICE_NAME);
}
else{
alloc_chrdev_region(&ap3216cdev.devid,0,DEVICE_CNT,DEVICE_NAME);
ap3216cdev.major = MAJOR(ap3216cdev.devid);
ap3216cdev.minor = MINOR(ap3216cdev.devid);
}
/* 2 初始化cdev*/
ap3216cdev.cdev.owner= THIS_MODULE;
cdev_init(&ap3216cdev.cdev,&ap3216cdev_fops);
/* 3、添加一个cdev */
cdev_add(&ap3216cdev.cdev,ap3216cdev.devid,DEVICE_CNT);
/*4 创建设备类*/
ap3216cdev.class = class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(ap3216cdev.class))
{
printk("ap3216cdev fail!\r\n");
return PTR_ERR(ap3216cdev.class);
}
/*5 创建设备*/
ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, DEVICE_NAME);
if(IS_ERR(ap3216cdev.device))
{
printk("ap3216cdev fail!\r\n");
return PTR_ERR(ap3216cdev.device);
}
ap3216cdev.private_data = i2c;
printk("ap3216cdev init success!\r\n");
return 0;
}
④然后就是完善编写remove函数:
static int ap3216c_remove(struct i2c_client *i2c)
{
printk("ap3216c_dev driver and device was matched!\r\n");
cdev_del(&ap3216cdev.cdev);
unregister_chrdev_region(ap3216cdev.devid,DEVICE_CNT);
device_destroy(ap3216cdev.class,ap3216cdev.devid);
class_destroy(ap3216cdev.class);
return 0;
}
⑥编写自己的寄存器读写函数,方便使用。
? ? ? ? 虽然我们上面知道了,通过i2c_transfer函数可以实现对于I2C设备寄存器的读写,但是如果每次我们都一条一条的去进行参数的赋值,一条一条的去输入,会使得带阿米重复性太高,这里我们自己封装一下:
/*
* @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)
{
int ret;
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 = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* ap3216c地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
/*
* @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); /* 将要写入的数据拷贝到数组b里面 */
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);
}
static void ap3216c_readdata(struct ap3216c_dev *dev)
{
int i = 0;
unsigned char buf[6];
for(i=0;i<6;i++)
{
buf[i] = ap3216c_read_reg(dev,AP3216C_IRDATALOW + i);
}
if(buf[0] & 0x80)/* IR_OF位为1,则数据无效 */{
dev->ir = 0;
}
else{
dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
}
dev->als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器的数据 */
if(buf[4] & 0x40) /* IR_OF位为1,则数据无效 */
{
dev->ps = 0;
}
else /* 读取PS传感器的数据 */
{
dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}
}
?⑦编写设备操作函数,open函数
static int ap3216cdev_open(struct inode *inode , struct file *file)
{
unsigned char value = 0;
file->private_data = &ap3216cdev;
/* 初始化AP3216C */
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04); /* 复位AP3216C */
mdelay(50); /* AP3216C复位最少10ms */
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR */
value = ap3216c_read_reg(&ap3216cdev,AP3216C_SYSTEMCONG);
printk("AP3216C_SYSTEMCONG = %#x\r\n",value);
return 0;
}
?⑦编写设备操作函数,read函数
static ssize_t ap3216cdev_read(struct file *file, char __user *buf, size_t size, loff_t *ptr)
{
long err=0;
short data[3];
struct ap3216c_dev *dev = (struct ap3216c_dev *)file->private_data;
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;
}
⑧完整代码如下:
#include <linux/ide.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/i2c.h>
#include "ap3216creg.h"
#include <linux/delay.h>
/************************函数定义-begin***********************************************/
static int ap3216cdev_release(struct inode *inode, struct file *file);
static ssize_t ap3216cdev_read(struct file *file, char __user *buf, size_t size, loff_t *ptr);
static ssize_t ap3216cdev_write(struct file *file, const char __user *buf, size_t size, loff_t *ptr);
static int ap3216cdev_open(struct inode *inode , struct file *file);
static int ap3216c_probe(struct i2c_client *i2c,const struct i2c_device_id *id);
static int ap3216c_remove(struct i2c_client *i2c);
/************************函数定义-end********************************************/
/************************宏定义-begin***********************************************/
#define DEVICE_NAME "ap3216c"
#define DEVICE_CNT 1
/************************宏定义-end********************************************/
/************************结构体定义-begin***********************************************/
/* ap3216c设备信息结构体 */
struct ap3216c_dev
{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
void *private_data; /* 私有数据 */
unsigned short ir, als, ps; /* 三个光传感器数据 */
};
struct ap3216c_dev ap3216cdev; /* led设备 */
/* 设备操作函数结构体 */
static const struct file_operations ap3216cdev_fops = {
.owner = THIS_MODULE,
.open = ap3216cdev_open,
.read = ap3216cdev_read,
.write = ap3216cdev_write,
.release = ap3216cdev_release
};
/*
* @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)
{
int ret;
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 = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* ap3216c地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
/*
* @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); /* 将要写入的数据拷贝到数组b里面 */
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);
}
static void ap3216c_readdata(struct ap3216c_dev *dev)
{
int i = 0;
unsigned char buf[6];
for(i=0;i<6;i++)
{
buf[i] = ap3216c_read_reg(dev,AP3216C_IRDATALOW + i);
}
if(buf[0] & 0x80)/* IR_OF位为1,则数据无效 */{
dev->ir = 0;
}
else{
dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
}
dev->als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器的数据 */
if(buf[4] & 0x40) /* IR_OF位为1,则数据无效 */
{
dev->ps = 0;
}
else /* 读取PS传感器的数据 */
{
dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}
}
static int ap3216cdev_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t ap3216cdev_read(struct file *file, char __user *buf, size_t size, loff_t *ptr)
{
long err=0;
short data[3];
struct ap3216c_dev *dev = (struct ap3216c_dev *)file->private_data;
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 ssize_t ap3216cdev_write(struct file *file, const char __user *buf, size_t size, loff_t *ptr)
{
return 0;
}
static int ap3216cdev_open(struct inode *inode , struct file *file)
{
unsigned char value = 0;
file->private_data = &ap3216cdev;
/* 初始化AP3216C */
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04); /* 复位AP3216C */
mdelay(50); /* AP3216C复位最少10ms */
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR */
value = ap3216c_read_reg(&ap3216cdev,AP3216C_SYSTEMCONG);
printk("AP3216C_SYSTEMCONG = %#x\r\n",value);
return 0;
}
static int ap3216c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
/*1 创建设备号*/
if(ap3216cdev.major){
ap3216cdev.devid = MKDEV(ap3216cdev.major,0);
register_chrdev_region(ap3216cdev.devid,DEVICE_CNT,DEVICE_NAME);
}
else{
alloc_chrdev_region(&ap3216cdev.devid,0,DEVICE_CNT,DEVICE_NAME);
ap3216cdev.major = MAJOR(ap3216cdev.devid);
ap3216cdev.minor = MINOR(ap3216cdev.devid);
}
/* 2 初始化cdev*/
ap3216cdev.cdev.owner= THIS_MODULE;
cdev_init(&ap3216cdev.cdev,&ap3216cdev_fops);
/* 3、添加一个cdev */
cdev_add(&ap3216cdev.cdev,ap3216cdev.devid,DEVICE_CNT);
/*4 创建设备类*/
ap3216cdev.class = class_create(THIS_MODULE,DEVICE_NAME);
if(IS_ERR(ap3216cdev.class))
{
printk("ap3216cdev fail!\r\n");
return PTR_ERR(ap3216cdev.class);
}
/*5 创建设备*/
ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, DEVICE_NAME);
if(IS_ERR(ap3216cdev.device))
{
printk("ap3216cdev fail!\r\n");
return PTR_ERR(ap3216cdev.device);
}
ap3216cdev.private_data = i2c;
printk("ap3216cdev init success!\r\n");
return 0;
}
static int ap3216c_remove(struct i2c_client *i2c)
{
printk("ap3216c_dev driver and device was matched!\r\n");
cdev_del(&ap3216cdev.cdev);
unregister_chrdev_region(ap3216cdev.devid,DEVICE_CNT);
device_destroy(ap3216cdev.class,ap3216cdev.devid);
class_destroy(ap3216cdev.class);
return 0;
}
static const struct i2c_device_id ap3216c_id[]={
{ "alientek,ap3216c", 0 },
{ }
};
static const struct of_device_id ap3216c_of_match[] = {
{ .compatible = "alientek,ap3216c" },
{/* Sentinel */ }
};
/*-------------------------------------------------------------------------*/
static struct i2c_driver ap3216c_driver = {
.driver = {
.name = "ap3216c",
.owner = THIS_MODULE,
.of_match_table = ap3216c_of_match,
},
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.id_table =ap3216c_id ,
};
static int __init ap3216c_init(void)
{
int ret = 0;
ret = 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("oudafa");
八、测试代码的编写
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd;
char *filename;
unsigned short databuf[3];
unsigned short ir, als, ps;
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) { /* 数据读取成功 */
ir = databuf[0]; /* ir传感器数据 */
als = databuf[1]; /* als传感器数据 */
ps = databuf[2]; /* ps传感器数据 */
printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
}
usleep(200000); /*100ms */
}
close(fd); /* 关闭文件 */
return 0;
}
九、使用测试
????????这里就不对讲前面那些模块加载的了,之前讲过挺多次的了、流程也就是使用make命令,然后编译出.ko文件以及APP文件,然后复制到nfs挂载的目录下,使用insmod命令去加载模块。
????????当驱动模块加载成功以后使用 ap3216cApp 来测试,输入如下命令:
./ap3216cApp /dev/ap3216c
测试 APP 会不断的从 AP3216C 中读取数据,然后输出到终端上,如图所示:
十、总结
通过上述分析,可以总结出一个设计驱动程序的方法,这个方法适用于Linux内核的各个版本:
- 1. 如果要设计I2C设备驱动,看 Documentation/i2c目录下的相关内核文档;I2C协议;处理器的I2C接口;掌握bus-dev-drv模型;掌握驱动程序设计相关知识。
- 2. 实现bus-dev-drv模型驱动。比如要先注册i2c_client,而内核文档中instantiating-devices(构造设备)文件就说明了方法。然后根据方法,搜索内核中对应的例子,模仿就可以 设计出需要的驱动;然后要注册i2c_driver,也是模仿其他如何设计即可。总之,Linux是非常庞大的系统,里面有很多例子可以参考的。
- 3. 理清思路,搞清楚框架中哪些工作是需要做的,哪些是不需要做的。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!