Linux驱动开发12 IIC驱动

IIC总线驱动+IIC设备驱动(驱动分割分离分层思想)     

我们不需要写适配器,只需要写设备驱动  

        I2C 是很常用的一个串行通信接口,用于连接各种外设、传感器等器件,在裸机篇已经对
I.MX6U I2C 接口做了详细的讲解。本章我们来学习一下如何在 Linux 下开发 I2C 接口器件驱动,重点是学习 Linux 下的 I2C 驱动框架,按照指定的框架去编写 I2C 设备驱动。

        I2C 总线驱动重点是 I2C 适配器(也就是 SOC I2C 接口控制器)驱动,这里要用到 两个重要的数据结构:i2c_adapter i2c_algorithmLinux 内核将 SOC I2C 适配器(控制器) 抽象成i2c_adapteri2c_adapter 结构体定义在 include/linux/i2c.h 文件中。

        

 =========================================================================

I2C 设备驱动重点关注两个数据结构: i2c_client 和 i2c_driver ,根据总线、设备和驱动模型,
I2C 总线上一小节已经讲了。还剩下设备和驱动, i2c_client 就是描述设备信息的, i2c_driver 描述驱动内容,类似于 platform_driver

1、对于我们 I2C 设备驱动编写人来说,重点工作就是构建 i2c_driver,构建完成以后需要向 Linux 内核注册这个 i2c_driveri2c_driver 注册函数为 int i2c_register_driver

另外 i2c_add_driver 也常常用于注册 i2c_driver i2c_add_driver 是一个宏,定义如下:
示例代码 61.1.2.3 i2c_add_driver 宏
587 #define i2c_add_driver ( driver ) \
588 i2c_register_driver ( THIS_MODULE , driver )
i2c_add_driver 就是对 i2c_register_driver 做了一个简单的封装,只有一个参数,就是要注册
i2c_driver

2、注销 I2C 设备驱动的时候需要将前面注册的 i2c_driver 从 Linux 内核中注销掉,需要用到i2c_del_driver 函数,此函数原型如下:
void i2c_del_driver(struct i2c_driver *driver)
函数参数和返回值含义如下:
driver :要注销的 i2c_driver
返回值:
=========================================================================

在IIC1 上 接了 AP3216C
修改设备树,我们不能在imx6ull.dtsi中修改,只能在imx6ull-alientek-emmc.dts中修改,
从下列代码中可以看出i2c1下添加了ap3216。i2c2下添加了codec等一系列设备

 

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

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

&i2c2 {
	clock_frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c2>;
	status = "okay";

	codec: wm8960@1a {
		compatible = "wlf,wm8960";
		reg = <0x1a>;
		clocks = <&clks IMX6UL_CLK_SAI2>;
		clock-names = "mclk";
		wlf,shared-lrclk;
	};

	ov5640: ov5640@3c {
		compatible = "ovti,ov5640";
		reg = <0x3c>;
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_csi1>;
		clocks = <&clks IMX6UL_CLK_CSI>;
		clock-names = "csi_mclk";
		pwn-gpios = <&gpio_spi 6 1>;
		rst-gpios = <&gpio_spi 5 0>;
		csi_id = <0>;
		mclk = <24000000>;
		mclk_source = <0>;
		status = "disabled";
		port {
			ov5640_ep: endpoint {
				remote-endpoint = <&csi1_ep>;
			};
		};
	};

	/* zuozhongkai FT5406/FT5426 */
	ft5426: ft5426@38 {
		compatible = "edt,edt-ft5426","edt,edt-ft5406";
		reg = <0x38>;
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_tsc
					&pinctrl_tsc_reset >; 
		interrupt-parent = <&gpio1>; 
		interrupts = <9 0>; 
		reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;  
		interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; 
		status = "okay";
	};

	gt9147:gt9147@14 {
		compatible = "goodix,gt9147", "goodix,gt9xx";
		reg = <0x14>;
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_tsc
					&pinctrl_tsc_reset >; 
		interrupt-parent = <&gpio1>; 
		interrupts = <9 0>; 
		reset-gpios  = <&gpio5 9 GPIO_ACTIVE_LOW>;
		interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; 
		status = "disable";  /* 如果需要改为okay */
	};

	/* zuozhongkai sill902x,如果需要HDMI就将status改为okay即可  */
	/*
	sii902x: sii902x@39 {
        compatible = "SiI,sii902x";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_sii902x>;
        interrupt-parent = <&gpio1>;
        interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
		irq-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
        mode_str = "1280x720M@60";
        bits-per-pixel = <16>;
        resets = <&sii902x_reset>;
        reg = <0x39>;
        status = "disable"; 
    };*/
};

 

查问题,栈回溯,可以看到进入了ap3216c_open,然后在i2c_transfer中出问题了

#ifndef AP3216C_H

#define AP3216C_H

/***************************************************************

Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.

文件名		: ap3216creg.h

作者	  	: 左忠凯

版本	   	: V1.0

描述	   	: AP3216C寄存器地址描述头文件

其他	   	: 无

论坛 	   	: www.openedv.com

日志	   	: 初版V1.0 2019/9/2 左忠凯创建

***************************************************************/



// 这个头文件用来存放ap3216c的寄存器信息

#define AP3216C_ADDR    	0X1E	/* AP3216C器件地址  */



/* AP3316C寄存器 */

#define AP3216C_SYSTEMCONG	0x00	/* 配置寄存器       */

#define AP3216C_INTSTATUS	0X01	/* 中断状态寄存器   */

#define AP3216C_INTCLEAR	0X02	/* 中断清除寄存器   */

#define AP3216C_IRDATALOW	0x0A	/* IR数据低字节     */

#define AP3216C_IRDATAHIGH	0x0B	/* IR数据高字节     */

#define AP3216C_ALSDATALOW	0x0C	/* ALS数据低字节    */

#define AP3216C_ALSDATAHIGH	0X0D	/* ALS数据高字节    */

#define AP3216C_PSDATALOW	0X0E	/* PS数据低字节     */

#define AP3216C_PSDATAHIGH	0X0F	/* PS数据高字节     */



#endif



#include <linux/types.h>

#include <linux/kernel.h>

#include <linux/delay.h>

#include <linux/ide.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/errno.h>

#include <linux/gpio.h>

#include <linux/cdev.h>

#include <linux/device.h>

#include <linux/of_gpio.h>

#include <linux/semaphore.h>

#include <linux/timer.h>

#include <linux/i2c.h>

#include <asm/mach/map.h>

#include <asm/uaccess.h>

#include <asm/io.h>

#include "ap3216creg.h"

/***************************************************************

Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.

文件名		: ap3216c.c

作者	  	: 左忠凯

版本	   	: V1.0

描述	   	: AP3216C驱动程序

其他	   	: 无

论坛 	   	: www.openedv.com

日志	   	: 初版V1.0 2019/9/2 左忠凯创建

***************************************************************/

#define AP3216C_CNT	1

#define AP3216C_NAME	"ap3216c"



struct ap3216c_dev {

	dev_t devid;			/* 设备号 	 */

	struct cdev cdev;		/* cdev 	*/

	struct class *class;	/* 类 		*/

	struct device *device;	/* 设备 	 */

	struct device_node	*nd; /* 设备节点 */

	int major;			/* 主设备号 */

	void *private_data;	/* 私有数据 */

	unsigned short ir, als, ps;		/* 三个光传感器数据 注意这里数据类型short*/

};



static struct ap3216c_dev ap3216cdev;



/*

 * @description	: 从ap3216c读取多个寄存器数据

 * @param - dev:  ap3216c设备

 * @param - reg:  要读取的寄存器首地址

 * @param - val:  读取到的数据

 * @param - len:  要读取的数据长度

 * @return 		: 操作结果

 * 

 * 读取AP3216C的N个寄存器值

 */

//读 read 写 write函数是IIC的难点和重点//

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;									/* 要发送的寄存器地址长度为1*/



	/* msg[1]读取数据 */

	msg[1].addr = client->addr;			/* 从机地址 也就是ap3216c地址 */

	msg[1].flags = I2C_M_RD;				/* 标记为读数据*/

	msg[1].buf = val;								/* 读取数据缓冲区,接收到的从机发送的数据 */

	msg[1].len = len;								/* 要读取的寄存器长度*/



  // i2c_transfer 即能向寄存器里面写数据,也能向寄存器里面读 

	//  从哪读要告诉他

	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 	  :   操作结果

 * 

 * 向AP3216C写N个寄存器的数据

 */

static s32 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 	  :   读取到的寄存器值

 * 

 * 读取AP3216C的 1 个寄存器值

 */

static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)

{

	u8 data = 0;



	// 直接调用写的函数,长度为 1

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

	return data;



#if 0

	struct i2c_client *client = (struct i2c_client *)dev->private_data;

	return i2c_smbus_read_byte_data(client, reg);

#endif

}



/*

 * @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 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;

		//从buf[0] 数组里面取 位 

	else 				/* 读取IR传感器的数据   		*/

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

}



//

///之下是IIC驱动框架搭建,之上时AP3216C寄存器数据读写函数编写/

//

/*

 * @description		: 打开设备

 * @param - inode 	: 传递给驱动的inode

 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量

 * 					  一般在open的时候将private_data指向设备结构体。

 * @return 			: 0 成功;其他 失败

 */

static int ap3216c_open(struct inode *inode, struct file *filp)

{

	filp->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 		*/

	return 0;

}



/*

 * @description		: 从设备读取数据 

 * @param - filp 	: 要打开的设备文件(文件描述符)

 * @param - buf 	: 返回给用户空间的数据缓冲区

 * @param - cnt 	: 要读取的数据长度

 * @param - offt 	: 相对于文件首地址的偏移

 * @return 			: 读取的字节数,如果为负值,表示读取失败

 */

static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)

{

	short data[3];

	long err = 0;



	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)

{

	return 0;

}



/* AP3216C操作函数 */

static const struct file_operations ap3216c_ops = {

	.owner = THIS_MODULE,

	.open = ap3216c_open,

	.read = ap3216c_read,

	.release = ap3216c_release,

};



 /*

  * @description     : i2c驱动的probe函数,当驱动与

  *                    设备匹配以后此函数就会执行

  * @param - client  : i2c设备

  * @param - id      : i2c设备ID

  * @return          : 0,成功;其他负值,失败

	*  

								probe函数

  */

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

{

	/* 搭建一套完整的字符设备驱动  */

	/* 1、构建设备号 */

	if (ap3216cdev.major) {

		ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);

		register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);

	} else {

		alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);

		ap3216cdev.major = MAJOR(ap3216cdev.devid);

	}



	/* 2、注册设备 */

	cdev_init(&ap3216cdev.cdev, &ap3216c_ops);

	cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);



	/* 3、创建类 */

	ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);

	if (IS_ERR(ap3216cdev.class)) {

		return PTR_ERR(ap3216cdev.class);

	}



	/* 4、创建设备 */

	ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);

	if (IS_ERR(ap3216cdev.device)) {

		return PTR_ERR(ap3216cdev.device);

	}



	// 让私有数据ap3216cdev 获得client

	ap3216cdev.private_data = client;



	return 0;

}



/*

 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行

 * @param - client 	: i2c设备

 * @return          : 0,成功;其他负值,失败

 */

static int ap3216c_remove(struct i2c_client *client)

{

	/* 删除设备 */

	cdev_del(&ap3216cdev.cdev);

	unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);



	/* 注销掉类和设备 */

	device_destroy(ap3216cdev.class, ap3216cdev.devid);

	class_destroy(ap3216cdev.class);

	return 0;

}





/* 两种匹配表 probe函数调用时候判断两个都成立才行,后面的不为空就行 */

/* 传统匹配方式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 */ }

};



/* i2c驱动结构体

		定义一个probe函数,一个remove函数

 */	

/********************I2C驱动的重中之重*************************/

static struct i2c_driver ap3216c_driver = {

	.probe = ap3216c_probe,

	.remove = ap3216c_remove,

	.driver = {

			.owner = THIS_MODULE,

		   	.name = "ap3216c",

		   	.of_match_table = ap3216c_of_match, 

		   },

	.id_table = ap3216c_id,

};

		   

/*

 * @description	: 驱动入口函数

 * @param 		: 无

 * @return 		: 无

 */

static int __init ap3216c_init(void)

{

	int ret = 0;



	//注册i2c_driver

	ret = i2c_add_driver(&ap3216c_driver);

	return ret;

}



/*

 * @description	: 驱动出口函数

 * @param 		: 无

 * @return 		: 无

 */

static void __exit ap3216c_exit(void)

{

	//注销i2c_driver

	i2c_del_driver(&ap3216c_driver);

}



/* module_i2c_driver(ap3216c_driver) */



module_init(ap3216c_init);

module_exit(ap3216c_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("zuozhongkai");







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

/***************************************************************

Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.

文件名		: ap3216cApp.c

作者	  	: 左忠凯

版本	   	: V1.0

描述	   	: ap3216c设备测试APP。

其他	   	: 无

使用方法	 :./ap3216cApp /dev/ap3216c

论坛 	   	: www.openedv.com

日志	   	: 初版V1.0 2019/9/20 左忠凯创建

***************************************************************/



/*

 * @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;

	}



	

	// 老规矩,这上面的部分都是模板,下面才是这个app真正写的

	// 循环读取

	while (1) {

		// 读取数据

		ret = read(fd, databuf, sizeof(databuf));

		// 判断数据 光强度(ALS)、接近距离(PS)和红外线强度(IR)

		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;

}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值