9.4在内核空间使用I2C总线

此章节以AP3216C驱动为例子演示如何在内核空间使用I2C总线

在内核中编写AP3216C驱动

AP3216C集成了光强(Ambilent Light Sensor,ALS)、距离(Proximity Sensor,PS)和红外传感器(Infrared Radiation LED,IR),该芯片通过IIC接口与主控芯片交互。

电路原理图

从原理图中可以看出AP3216C接在控制器的I2C5 I2C接口上,所复用的GPIO分别是PA11和PA12
在这里插入图片描述

在这里插入图片描述

编写设备树

按如下步骤在设备树中添加I2C5适配器的描述:

  1. 在顶层设备树文件中引用i2c5 节点,并进行如下修改
&i2c5 {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&i2c5_pins_a>;
	pinctrl-1 = <&i2c5_pins_sleep_a>;
	status = "okay";

	ap3216c@1e {
		compatible = "alientek,ap3216c";
		labe = "ap3216c_0";
		reg = <0x1e>;
	};
};
  1. 在 stm32mp15-pinctrl.dtsi 的 &pinctrl 节点中增加 I2C5 的引脚配置,内容如下:
	i2c5_pins_a: i2c5-0 {
		pins {
			pinmux = <STM32_PINMUX('A', 11, AF4)>, /* I2C5_SCL */
				 <STM32_PINMUX('A', 12, AF4)>; /* I2C5_SDA */
			bias-disable;
			drive-open-drain;
			slew-rate = <0>;
		};
	};

	i2c5_pins_sleep_a: i2c5-1 {
		pins {
			pinmux = <STM32_PINMUX('A', 11, ANALOG)>, /* I2C5_SCL */
				 <STM32_PINMUX('A', 12, ANALOG)>; /* I2C5_SDA */

		};
	};

编写驱动代码

内核空间I2C驱动主要包括以下部分:

  1. 定义并初始化i2c_driver对象,重点在于probe函数、remove函数、of_match_table的实现,probe函数在i2c_client和i2c_driver匹配成功时执行,remove在i2c_client或i2c_driver卸载时执行,of_match_table定义从设备树创建i2c_client与i2c_driver之间的匹配字符串。
/* 匹配列表,用于设备树和I2C驱动匹配 */
static const struct of_device_id ap3216c_of_match[] = {
	{.compatible = "alientek,ap3216c"},
	{ /* Sentinel */ }
};
//I2C驱动
static struct i2c_driver ap3216c_driver = {
	.driver = {
		.name = "alientek,ap3216c",
		.owner = THIS_MODULE,
		.pm = NULL,
		.of_match_table = ap3216c_of_match,
	},
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
};
  1. 注册/注销i2c_driver,定义好i2c_driver 后需要通过函数i2c_add_driver将其注册到I2C总线上,当I2C驱动模块不使用是也可通过i2c_del_driver注销i2c_driver。
//注册 I2C 设备驱动
int i2c_add_driver(struct i2c_driver *driver)
//注销 I2C 设备驱动
void i2c_del_driver(struct i2c_driver *driver)
  1. 传输数据,在内核中可以使用函数i2c_transfer在I2C总线上发起数据传输,以达到读写I2C设备的目的。
/**
 * adap I2C 适配器
 * msgs msg 列表
 * num msg 数量,或者说msgs数组的元素个数
 * 返回负值失败,返回其他非负值,发送的 msgs 数量
 * 此函数在连续发送多个数据包时如果设置了 I2C_M_NOSTART ,在发送数据包的最后 1byte 时不会等待 ACK 信号,
 * 可能会导致发送失败
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

完整的AP3216C内核驱动代码如下:

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/i2c.h>

#define AP3216C_NUMBER			5

/* 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 数据高字节 */

struct ap3216c_handle {
	//ap3216c链表节点
	struct list_head node;
	//ap3216c的ID号,这里作为ap3216c的次设备号
	uint32_t id;
	//ap3216c标签,这里用于生成设备文件名
	const char *labe;
	//记录此设备对应的i2c_client
	struct i2c_client *client;
};

//设备号
static dev_t ap3216c_num;
//cdev对象
static struct cdev ap3216c_cdev;
//class对象
static struct class *ap3216c_class;

//ap3216c句柄列表
static struct list_head ap3216c_list = LIST_HEAD_INIT(ap3216c_list);

//根据ID查找设备句柄
static struct ap3216c_handle *find_ap3216c_handle(uint32_t id)
{
	struct ap3216c_handle *pos;
	struct ap3216c_handle *n;
	struct ap3216c_handle *ap3216c_handle;

	ap3216c_handle = NULL;
	list_for_each_entry_safe(pos, n, &ap3216c_list, node)
	{
		if(pos->id == id)
		{
			ap3216c_handle = pos;
			break;
		}
	}

	return ap3216c_handle;
}

//分配一个ID
static int32_t alloc_id(void)
{
	int32_t id;

	//按从小到大顺序生成ID
	for(id = 0; find_ap3216c_handle(id) && (id < AP3216C_NUMBER); id++)
	{
		;
	}

	//ID必须小于注册的最大ID号
	if(id >= AP3216C_NUMBER)
		return -EINVAL;

	return id;
}

//将设备添加到链表
static void add_ap3216c(struct ap3216c_handle *ap3216c_handle)
{
	list_add(&ap3216c_handle->node, &ap3216c_list);
}

//将设备从链表中移除
static void remove_ap3216c(struct ap3216c_handle *ap3216c_handle)
{
	list_del(&ap3216c_handle->node);
}

static int ap3216c_read_regs(struct i2c_client *client, uint8_t reg, uint8_t *data, uint8_t lenght)
{
	struct i2c_msg msg[2];

	//从机地址
	msg[0].addr = client->addr;
	//表示写
	msg[0].flags = 0;
	//buf是一个指针,指向了要发送的数据
	msg[0].buf = &reg;
	//msg[0].buf的数据长度
	msg[0].len = 1;

	msg[1].addr = client->addr;
	//表示读
	msg[1].flags = I2C_M_RD;
	msg[1].buf = data;
	msg[1].len = lenght;
	if(i2c_transfer(client->adapter, msg, 2)  == 2)
		return 0;
	else 
		return -EIO;
}

static int ap3216c_write_regs(struct i2c_client *client, uint8_t reg, uint8_t *data, uint8_t lenght)
{
	uint8_t buffer[256];
	struct i2c_msg msg[1];
	
	//只能用一个msg发送,分多个msg时msg衔接的时候不会等待设备的ACK信号,可能会导致失败
	buffer[0] = reg;
	memcpy(&buffer[1], data, lenght);
	//从机地址
	msg[0].addr = client->addr;
	//表示写
	msg[0].flags = 0;
	//buf是一个指针,指向了要发送的数据
	msg[0].buf = buffer;
	//msg[0].buf的数据长度
	msg[0].len = 1 + lenght;
	if(i2c_transfer(client->adapter, msg, 1)  == 1)
		return 0;
	else 
		return -EIO;
}

static int ap3216c_read_reg(struct i2c_client *client, uint8_t reg, uint8_t *data)
{
	return ap3216c_read_regs(client, reg, data, 1);
}

static int ap3216c_write_reg(struct i2c_client *client, uint8_t reg, uint8_t data)
{
	return ap3216c_write_regs(client, reg, &data, 1);
}

//打开LED设备
static int ap3216c_open(struct inode *inode, struct file *file)
{
	int result;
	uint32_t id;
	struct ap3216c_handle *ap3216c_handle;

	//提取设备ID
	id = MINOR(inode->i_rdev);

	//查找设备句柄
	ap3216c_handle = find_ap3216c_handle(id);
	if(!ap3216c_handle)
	{
		printk("find ap3216c handle failed\r\n");
		return -EINVAL;
	}

	//初始化AP3216C,然后开启ALS、PS+IR
	result = ap3216c_write_reg(ap3216c_handle->client, AP3216C_SYSTEMCONG, 0x04);
	if(result != 0)
		return result;
	mdelay(50);
	result = ap3216c_write_reg(ap3216c_handle->client, AP3216C_SYSTEMCONG, 0X03);
	if(result != 0)
		return result;
	mdelay(250);

	//设置文件私有数据
	file->private_data = (void*)ap3216c_handle;
//	printk("%s\n", ap3216c_handle->labe);
	
	return 0;
}

static int ap3216c_release(struct inode *inode, struct file *file)
{
	return 0;
}

static ssize_t ap3216c_read(struct file *file, char __user *user_buf, size_t len, loff_t *off)
{
	int i;
	int result;
	uint16_t data[3];
	uint8_t buffer[6];
	struct ap3216c_handle *ap3216c_handle = (struct ap3216c_handle *)file->private_data;
	
	if(len < 6)
		return -EINVAL;

	/* 循环读取所有传感器数据 */
	for(i=0; i<6; i++) 
	{
		result = ap3216c_read_reg(ap3216c_handle->client, AP3216C_IRDATALOW+i, &buffer[i]);
		if(result != 0)
			return result;
	}

	if(buffer[0] & 0X80)
	{
		/* IR_OF位为1,则数据无效 */
		data[0] = 0;
	}
	else
	{
		/* 读取IR传感器的数据*/
		data[0] = ((uint16_t)buffer[1] << 2) | (buffer[0] & 0X03);
	}

	/* 读取ALS传感器的数据 */
	data[1] = ((uint16_t)buffer[3] << 8) | buffer[2];

	if(buffer[4] & 0x40)
	{
		/* IR_OF位为1,则数据无效 */
		data[2] = 0;
	}
	else
	{
		/* 读取PS传感器的数据 */
		data[2] = ((uint16_t)(buffer[5] & 0X3F) << 4) | (buffer[4] & 0X0F);
	}

	result = copy_to_user(user_buf, data, sizeof(data));
	if(result != 0)
		return -EFAULT;

	return 6;
}

//设备和驱动匹配成功执行
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *device_id)
{
	int result;
	uint32_t id;
	struct device *device;
	struct ap3216c_handle *ap3216c_handle;

	printk("%s\r\n", __FUNCTION__);

	//分配一个ID
	id = alloc_id();
	if(id < 0)
		return id;

	//分配设备句柄,采用devm前缀的函数,在模块卸载时自动释放
	ap3216c_handle = devm_kzalloc(&client->dev, sizeof(struct ap3216c_handle), GFP_KERNEL);
	if(!ap3216c_handle)
	{
		printk("alloc memory faiap3216c\r\n");
		return -ENOMEM;
	}
	//复位LED设备句柄
	memset(ap3216c_handle, 0, sizeof(struct ap3216c_handle));

	//绑定ID
	ap3216c_handle->id = id;
	//绑定i2c_client
	ap3216c_handle->client = client;
	//获取labe
	result = of_property_read_string(client->dev.of_node, "labe", &ap3216c_handle->labe);
	if(result < 0)
	{
		printk("get labe failed\r\n");
		return result;
	}
	//添加AP3216C到链表
	add_ap3216c(ap3216c_handle);

	//设置驱动私有数据
	client->dev.driver_data = ap3216c_handle;

	printk("device major %d, device minor %d, device file name = %s\r\n",
		MAJOR(ap3216c_num+ap3216c_handle->id), MINOR(ap3216c_num+ap3216c_handle->id), ap3216c_handle->labe);
	//创建设备文件,将ID作为此设备的次设备号
	device = device_create(ap3216c_class, NULL, ap3216c_num+ap3216c_handle->id, NULL, ap3216c_handle->labe);
	if(IS_ERR(device))
	{
		remove_ap3216c(ap3216c_handle);
		printk("device create failed");
		return PTR_ERR(device);
	}

	return 0;
}

//设备或驱动卸载时执行
static int ap3216c_remove(struct i2c_client *client)
{
	struct ap3216c_handle *ap3216c_handle;

	printk("%s\r\n", __FUNCTION__);

	//提取平台设备的驱动私有数据
	ap3216c_handle = (struct ap3216c_handle*)client->dev.driver_data;

	//删除设备文件
	device_destroy(ap3216c_class, ap3216c_num+ap3216c_handle->id);

	//从设备句柄链表中删除
	remove_ap3216c(ap3216c_handle);

	return 0;
}

/* 匹配列表,用于设备树和I2C驱动匹配 */
static const struct of_device_id ap3216c_of_match[] = {
	{.compatible = "alientek,ap3216c"},
	{ /* Sentinel */ }
};
//I2C驱动
static struct i2c_driver ap3216c_driver = {
	.driver = {
		.name = "alientek,ap3216c",
		.owner = THIS_MODULE,
		.pm = NULL,
		.of_match_table = ap3216c_of_match,
	},
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
};

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

static int __init ap3216c_drv_init(void)
{
	int result;

	printk("%s\r\n", __FUNCTION__);

	//根据次设备号起始值动态注册字符设备号
	result = alloc_chrdev_region(&ap3216c_num, 0, AP3216C_NUMBER, "atk,ap3216c");
	if(result < 0)
	{
		printk("alloc chrdev failed\r\n");
		return result;
	}
	printk("first device major %d, minor %d\r\n", MAJOR(ap3216c_num), MINOR(ap3216c_num));

	//初始化CDEV对象
	cdev_init(&ap3216c_cdev, &ap3216c_ops);
	ap3216c_cdev.owner = THIS_MODULE;
	//向系统添加CDEV对象
	result = cdev_add(&ap3216c_cdev, ap3216c_num, AP3216C_NUMBER);
	if(result < 0)
	{
		unregister_chrdev_region(ap3216c_num, AP3216C_NUMBER);
		printk("add cdev failed\r\n");
		return result;
	}

	//创建class对象
	ap3216c_class = class_create(THIS_MODULE, "ap3216c,class");
	if(IS_ERR(ap3216c_class))
	{
		cdev_del(&ap3216c_cdev);
		unregister_chrdev_region(ap3216c_num, AP3216C_NUMBER);
		printk("class create failed");
		return PTR_ERR(ap3216c_class);
	}

	//注册I2C驱动
	result = i2c_add_driver(&ap3216c_driver);
	if(result < 0)
	{
		class_destroy(ap3216c_class);
		cdev_del(&ap3216c_cdev);
		unregister_chrdev_region(ap3216c_num, AP3216C_NUMBER);
		printk("add cdev failed\r\n");
		return result;
	}

	return 0;
}

static void __exit ap3216c_drv_exit(void)
{
	printk("%s\r\n", __FUNCTION__);

	//注销I2C驱动
	i2c_del_driver(&ap3216c_driver);
	//销毁class对象
	class_destroy(ap3216c_class);
	//从系统删除CDEV对象
	cdev_del(&ap3216c_cdev);
	//注销字符设备号
	unregister_chrdev_region(ap3216c_num, AP3216C_NUMBER);
}

module_init(ap3216c_drv_init);
module_exit(ap3216c_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("ap3216c_driver");

编写驱动测试程序

驱动测试程序非常简单,它包括以下几个步骤:

  1. 打开AP3216C设备,此时会调用到驱动层的open函数,在open函数中会对AP3216C进行初始化
  2. 循环读取AP3216C设备,此时会调用到驱动层的read函数,在read函数中会读取IR、ALS、PS寄存器的值,然后拷贝到应用层
    完整的驱动测试程序如下:
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: ap3216cApp.c
作者	  	: 正点原子Linux团队
版本	   	: V1.0
描述	   	: ap3216c设备测试APP。
其他	   	: 无
使用方法	 :./ap3216cApp /dev/ap3216c
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2021/03/19 正点原子Linux团队创建
***************************************************************/
#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) 
	{
		usleep(200000);

		ret = read(fd, databuf, sizeof(databuf));
		if(ret == 6)
		{
			/* 数据读取成功 */
			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(fd);	/* 关闭文件 */	
	return 0;
}

上机测试

  1. 修改设备树(根据硬件原理图修改),然后用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树,并用新的设备树启动目标板。
  2. 这里下载代码并进行编译,然后拷贝到目标板根文件系统的root目录中。
  3. 执行命令insmod ap3216c.ko加载ap3216c驱动,加载成功后会在/dev/目录中生成ap3216c_0的设备文件。
    在这里插入图片描述
  4. 执行命令./app.out /dev/ap3216c_0启动测试程序。测试程序会将读取到的寄存器值输出
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值