此章节以AP3216C驱动为例子演示如何在内核空间使用I2C总线
在内核中编写AP3216C驱动
AP3216C集成了光强(Ambilent Light Sensor,ALS)、距离(Proximity Sensor,PS)和红外传感器(Infrared Radiation LED,IR),该芯片通过IIC接口与主控芯片交互。
电路原理图
从原理图中可以看出AP3216C接在控制器的I2C5 I2C接口上,所复用的GPIO分别是PA11和PA12
编写设备树
按如下步骤在设备树中添加I2C5适配器的描述:
- 在顶层设备树文件中引用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>;
};
};
- 在 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驱动主要包括以下部分:
- 定义并初始化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,
};
- 注册/注销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)
- 传输数据,在内核中可以使用函数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 = ®
//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");
编写驱动测试程序
驱动测试程序非常简单,它包括以下几个步骤:
- 打开AP3216C设备,此时会调用到驱动层的open函数,在open函数中会对AP3216C进行初始化
- 循环读取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;
}
上机测试
- 修改设备树(根据硬件原理图修改),然后用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树,并用新的设备树启动目标板。
- 从这里下载代码并进行编译,然后拷贝到目标板根文件系统的root目录中。
- 执行命令insmod ap3216c.ko加载ap3216c驱动,加载成功后会在/dev/目录中生成ap3216c_0的设备文件。
- 执行命令./app.out /dev/ap3216c_0启动测试程序。测试程序会将读取到的寄存器值输出