正点原子linux阿尔法开发板使用——IIC驱动

IIC总线驱动

总线驱动中两个重要的数据结构:i2c_adapter、i2c_algorithm。i2c_adapter存放在include/linux/i2c.h中。红色箭头所指向的地方就是总线访问算法。algo对外提供API函数实现读写操作。这个i2c_algorithm就是实现IIC主机和IIC设备之间通信方法。
在这里插入图片描述i2c_algorithm深究:
在这里插入图片描述因此,得到IIC主机驱动主要完成i2c_adapter结构体变量的初始化,然后设置,i2c_algorithm中的master_xfer函数,完成之后,完成以后通过 i2c_add_numbered_adapter或 i2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapter,这两个函数的原型如下:

int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)

这两个函数的区别在于 i2c_add_adapter 使用动态的总线号,而 i2c_add_numbered_adapter使用静态总线号。

删除 I2C 适配器,函数原型如下:

void i2c_del_adapter(struct i2c_adapter * adap)

注意:soc主机驱动半导体厂商已经配置完成,如果不配置主机驱动的半导体厂商用左工的话就是离倒闭也不远了。

IIC 设备驱动(具体设备,如ap3216c)

IIC 设备驱动重点关注两个数据结构:i2c_client 和 i2c_driver。i2c_client 描述设备信息,i2c_driver 描述驱动内容,类似于 platform_driver。

1、i2c_client 结构体

在这里插入图片描述一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client。

2、i2c_driver 结构体(重点要实现的内容!!!!)

i2c_driver 类似 platform_driver,编写IIC设备驱动重点要处理的内容。
在这里插入图片描述* 当 I2C 设备和驱动匹配成功以后 probe 函数就会执行,和 platform 驱动一样。

  • device_driver驱动结构体,如果使用设备树的话,需要设置 device_driver 的of_match_table 成员变量,也就是驱动的兼容(compatible)属性。
  • id_table 是传统的、未使用设备树的设备匹配 ID 表。
  • 对于 IIC 设备驱动编写人来说,重点工作就是构建 i2c_driver,构建完成以后需要向Linux 内核注册这个 i2c_driver。i2c_driver 注册函数为 int i2c_register_driver,此函数原型如下:
int i2c_register_driver(struct module *owner, 
 struct i2c_driver *driver)

owner:一般为 THIS_MODULE。
driver:要注册的 i2c_driver。

注销IIC设备。

void i2c_del_driver(struct i2c_driver *driver)
3、IIC设备和驱动匹配过程

设备和驱动匹配过程是通过i2c_bus_type

在这里插入图片描述
.match 就是 I2C 总线的设备和驱动匹配函数,在这里就是 i2c_device_match 这个函数,此
函数内容如下:
在这里插入图片描述设备树下匹配过程:

  • of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节点的 compatible 属性和of_device_id 中的 compatible 属性是否相等,如果相等的话就表示 I2C设备和驱动匹配。
4、I.MX6U 的 I2C 适配器驱动分析(可以跳过!!!)

在imx6ull.dtsi下找到文件中找到 I.MX6U 的 I2C1 控制器节点,内容如下:
在这里插入图片描述

通过搜索设备树节点信息,找到如下内容:
在这里插入图片描述
设备树匹配函数:
在这里插入图片描述
再看匹配成功之后的probe执行的代码,匹配成功之后,probe函数执行相关的操作:
在这里插入图片描述
在这里插入图片描述
i2c_imx_algo 包含 I2C1 适配器与 I2C 设备的通信函数 master_xfer,i2c_imx_algo 结构体定义如下:
在这里插入图片描述
这个IIC适配器支持的通信协议:
在这里插入图片描述

i2c_imx_xfer函数(不全):


static int i2c_imx_xfer(struct i2c_adapter *adapter,
						struct i2c_msg *msgs, int num)
{
	unsigned int i, temp;
	int result;
	bool is_lastmsg = false;
	struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);

	dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);

	/* Start I2C transfer */
	result = i2c_imx_start(i2c_imx);
	if (result)
		goto fail0;

	/* read/write data */
	for (i = 0; i < num; i++) {
		if (i == num - 1)
			is_lastmsg = true;

		if (i) {
			dev_dbg(&i2c_imx->adapter.dev,
				"<%s> repeated start\n", __func__);
			temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
			temp |= I2CR_RSTA;
			imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
			result =  i2c_imx_bus_busy(i2c_imx, 1);
			if (result)
				goto fail0;
		}
		dev_dbg(&i2c_imx->adapter.dev,
			"<%s> transfer message: %d\n", __func__, i);
		/* write/read data */

		if (msgs[i].flags & I2C_M_RD)
			result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);
		else {
			if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
				result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
			else
				result = i2c_imx_write(i2c_imx, &msgs[i]);
		}
		if (result)
			goto fail0;
	}

fail0:
	/* Stop I2C transfer */
	i2c_imx_stop(i2c_imx);

	dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__,
		(result < 0) ? "error" : "success msg",
			(result < 0) ? result : num);
	return (result < 0) ? result : num;
}
  • 调用 i2c_imx_start 函数开启 I2C 通信。
  • 如果是从 I2C 设备读数据的话就调用 i2c_imx_read 函数。
  • 向 I2C 设备写数据,如果要用 DMA 的话就使用 i2c_imx_dma_write 函数来完成写数据。如果不使用 DMA 的话就使用 i2c_imx_write 函数完成写数据。
  • I2C 通信完成以后调用 i2c_imx_stop 函数停止 I2C 通信。
5、IIC 设备驱动编写流程(设备树)

NXP 官方的 EVK 开发板在 I2C1 上接了 mag3110 这个磁力计芯片,因此必须在 i2c1 节点下创建 mag3110 子节点,然
后在这个子节点内描述 mag3110 这个芯片的相关信息。

使用设备树节点的时候:
在这里插入图片描述

  • 节点名字:mag3110@0e,“0e”就是 mag3110 的 I2C 器件地址
  • compatible 属性值为“fsl,mag3110”
  • reg 属性设置 mag3110 的器件地址,值为 0x0e。
  • 重点:I2C 设备节点的创建重点是 compatible 属性和 reg 属性的设置,一个用于匹配驱动,一个用于设置器件地址。
6、IIC 设备数据收发处理流程

初始化 I2C 设备就必须能够对 I2C 设备寄存器进行读写操作,这里要用到 i2c_transfer 函数。i2c_transfer 函数
最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数,对于 I.MX6U 而言就是i2c_imx_xfer 这个函数。

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 参数结构体描述:
在这里插入图片描述

I2C设备驱动

i2c_client:表示I2C设备,不需要我们自己创建i2c_client,一般在设备树里面添加具体的I2C芯片,比如fxls8471,系统在解析设备树的时候就会知道有这个I2C设备,然后会创建对应的i2c_client。
在这里插入图片描述

重点

i2c设备驱动框架,i2c_driver初始化与注册,需要II2C设备驱动编写人员编写的,IIC驱动程序就是初始化i2c_driver,然后向系统注册。注册使用i2c_register_driveri2c_add_driver,如果注销i2c_driver使用i2c_del_driver

在这里插入图片描述

驱动编写

在这里插入图片描述

设备树修改

在IIC1上接了一个AP3216C
1、修改设备树,IO,添加AP3216C的设备节点。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
P77左忠凯手撕IIC驱动代码

模仿实现:
在这里插入图片描述
2、编写I2C驱动框架,I2C设备驱动框架,字符设备驱动框架。

3、初始化AP3216C,实现ap3216c_read()。

重点是通过IIC控制器来向AP3216C里面发送或者读取数据。使用I2C_transfer这个API来完成数据传输。

adapt:IIC设备对应的适配器,也就是IIC接口,当IIC设备和驱动匹配之后,probe函数会执行,probe函数传递进来的第一个参数就是 i2c_client,在i2c_client里面保存了此I2C设备所对应的i2c_adapt。

msgs:就是构成的I2C传输的数据。

IIC驱动AP3216C驱动代码流程:

头文件

#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.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/atomic.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/semaphore.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include "ap3216c.h"

IIC的设备结构体以及初始化

#define AP3216C_CNT 	1
#define AP3216C_NAME	"ap3216c"


struct ap3216c_dev {
	
	struct cdev cdev;
	struct class *class;/*类:为了自动创建节点*/
	struct device *device;/*设备:为了自动创建节点*/
	dev_t devid; //设备号
	int major;   //主设备号
	int minor;   //次设备号

	void *private_data;

	unsigned short ir,als,ps;
};

struct ap3216c_dev ap3216c;

读取AP3216C的N个寄存器的数值


/*读取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;	/*从机地址*/
	msg[0].flags = 0;				/*要发送的数据*/
	msg[0].buf = &reg; 				/*要发送的数据,也就是寄存器地址*/
	msg[0].len = 1;					/*要发送的寄存器地址为1*/


	/*msg[1]读取数据*/
	msg[1].addr =  client->addr;	/*从机地址*/
	msg[1].flags = I2C_M_RD;		/*表示读数据*/
	msg[1].buf = val; 				/*接收到的从机的数据*/
	msg[1].len = len;				/*要读取的寄存器长度*/
	return i2c_transfer(client->adapter,msg,2);
}


/*读取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写入N个寄存器的数据

/*向AP3216C写入N个寄存器的数据*/
static int ap3216c_write_regs(struct ap3216c_dev *dev,u8 reg,u8 *buf,int len)
{
	struct i2c_msg msg;
	u8 b[256];

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

	b[0] = reg;

	//构建要发送的数据,也就是寄存器首地址+实际的地址。
	memcpy(&b[1],buf,len); 


	msg.addr =  client->addr;	/*从机地址*/
	msg.flags = 0;				/*要发送的数据*/
	msg.buf = b; 				/*要发送的数据,也就是寄存器地址+实际数据*/
	msg.len = len+1;			/*要发送的数据长度:寄存器地址+1*/

	return i2c_transfer(client->adapter,&msg,1);
}

/*向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);

}

IIC读取数据

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; 
	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_probe函数

static int ap3216c_open(struct inode *inode, struct file *filp)
{
	/*设置私有数据*/
	filp->private_data = &ap3216c;

	unsigned char val = 0;

	printk("ap3216c_open \r\n");

	/*初始化AP3216C*/
	ap3216c_write_reg(&ap3216c,AP3216C_SYSTEMCONG,0X4);//复位
	mdelay(50);
	ap3216c_write_reg(&ap3216c,AP3216C_SYSTEMCONG,0X3);//复位

	val = ap3216c_read_reg(&ap3216c,AP3216C_SYSTEMCONG);

	printk("val = %#x\r\n",val);

	return 0;

}

static ssize_t ap3216c_read(struct file *filp, __user char *buf, size_t count,loff_t *ppos)
{
	long err = 0;
	short data[3];
	struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->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 int ap3216c_release(struct inode *inode, struct file *filp)
{
	//struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
	
	return 0;
}



/**
 * 字符设备的操作集合
*/
const struct file_operations ap3216c_fops = {
	.owner = THIS_MODULE,
	.open = ap3216c_open,
	.read = ap3216c_read,
	.release = ap3216c_release,

};


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

	int ret = 0;
	/*搭建字符设备驱动框架*/
	/*2、注册字符设备*/
	printk("ap3216c_init ok\r\n");

	ap3216c.major = 0;//设置为0,表示由系统申请设备号
	if(ap3216c.major)   //给定主设备号
	{
		ap3216c.devid = MKDEV(ap3216c.major,0);	
		ret = register_chrdev_region(ap3216c.devid,AP3216C_CNT,AP3216C_NAME);
	}else{
		ret = alloc_chrdev_region(&ap3216c.devid,0,AP3216C_CNT,AP3216C_NAME);
		ap3216c.major = MAJOR(ap3216c.devid);
		ap3216c.minor = MINOR(ap3216c.devid);
	}
	if(ret < 0){
		printk("ap3216c chrdev_region err\r\n");
		return -1;
	}

	printk("ap3216c majorid = %d,minorid = %d\r\n",ap3216c.major,ap3216c.minor);

	/*3、初始化cdev*/
	ap3216c.cdev.owner = THIS_MODULE;

	cdev_init(&ap3216c.cdev, &ap3216c_fops);

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

	/* 4、创建类 */
	ap3216c.class = class_create(THIS_MODULE, AP3216C_NAME);
	if (IS_ERR(ap3216c.class)) {
		return PTR_ERR(ap3216c.class);
	}

	/* 5、创建设备 */
	ap3216c.device = device_create(ap3216c.class, NULL, ap3216c.devid, NULL, AP3216C_NAME);
	if (IS_ERR(ap3216c.device)) {
		return PTR_ERR(ap3216c.device);
	}

	ap3216c.private_data = client; 
	return 0;

}

i2c_driver函数添加

static int ap3216c_remove(struct i2c_client *client)
{

	/*删除字符设备*/
	cdev_del(&ap3216c.cdev);

	/*注销设备号*/
	unregister_chrdev_region(ap3216c.devid,AP3216C_CNT);

	/*先摧毁设备再摧毁类*/
	device_destroy(ap3216c.class,ap3216c.devid);

	/*摧毁类*/
	class_destroy(ap3216c.class);
	return 0;

}

/*传统的匹配表*/
static struct i2c_device_id app3216c_id[] ={
	{"alentek,ap3216c",0},
	{}

};

/*设备树匹配表*/
static struct of_device_id app3216c_of_match[] ={
	{.compatible = "alentek,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(app3216c_of_match),

	},
	.id_table = app3216c_id,

};

IIC驱动的加载与卸载


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("qhy");

IIC应用编程实现读取传感器的数据

#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>
#include <linux/input.h>
/*input_event 结构体变量*/

static struct input_event input_event;

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

    fd = open(argv[1], O_RDWR);
    if (fd<0)
    {
        perror("open error");
        exit(-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);
        }
    }
    
    /*close*/
    close(fd);

    exit(0);
}
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值