MX6ULL学习笔记(十一)I2C设备驱动

2023-12-14 20:36:17

前言

????????I2C 是很常用的一个串行通信接口,用于连接各种外设、传感器等器件,本章我们来学习一下如何在 Linux 下开发 I2C 接口器件驱动。本章以 I.MX6U-ALPHA 开发板上的 AP3216C 这个三合一环境光传感器为例,通过 AP3216C 讲解 一下如何编写 Linux 下的 I2C 设备驱动程序。

目录

前言

一、Linux I2C 驱动框架简介。

二、Linux I2C 驱动框架分析。

I2C 总线驱动

2. i2c_client? -- 设备信息

3. i2c_driver? -- 驱动内容

三、I2c相关API

1、I2C驱动注册函数。

2、I2C 设备驱动注销API。

3. I2C 设备寄存器读写函数

4.i2c_driver 的注册示例代码

四、MX6U 的 I2C 适配器驱动分析

五、设备驱动编写流程

1.I2C 设备信息描述

2.驱动内容的编写

六、具体设备树的修改

七、驱动代码的编写

八、测试代码的编写

九、使用测试

十、总结


一、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
  • msgsI2C 要发送的一个或多个消息。
  • 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 的器件地址的,因此值为 0x0eI2C 设备节点的创建重点 是 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 = &reg;					/* 读取的首地址 */
	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 = &reg;					/* 读取的首地址 */
	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. 1. 如果要设计I2C设备驱动,看 Documentation/i2c目录下的相关内核文档;I2C协议;处理器的I2C接口;掌握bus-dev-drv模型;掌握驱动程序设计相关知识。
  2. 2. 实现bus-dev-drv模型驱动。比如要先注册i2c_client,而内核文档中instantiating-devices(构造设备)文件就说明了方法。然后根据方法,搜索内核中对应的例子,模仿就可以 设计出需要的驱动;然后要注册i2c_driver,也是模仿其他如何设计即可。总之,Linux是非常庞大的系统,里面有很多例子可以参考的。
  3. 3. 理清思路,搞清楚框架中哪些工作是需要做的,哪些是不需要做的。

文章来源:https://blog.csdn.net/DOF526570/article/details/135002199
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。