I2C子系统
linux驱动 | I2C子系统_★_仰望星空_★的博客-CSDN博客
https://blog.csdn.net/qq_36413982/article/details/123767630
AP3216C
AP3216C是敦南科技退出的一款三合一环境传感器:
- 数字环境光传感器(ALS)
- 接近传感器(PS)
- 一个红外LED(IR)
通信接口:
- 通过IIC接口和MCU连接,并支持中断(INT)输出
应用:
- AP3216C被广泛应用于智能手机上面,用来检测光强度(自动背光控制),和接近开关控制(听筒靠近耳朵,手机自动灭屏功)
寄存器表:
寄存器描述:
I2C写寄存器时序:
- S,I2C启动信号
- Slave address, I2C从设备地址
- W,该位和Slave address组成一个字节,地址字节最低位,其值为0表示操作方向为写,1则表示读
- A,应答信息,从设备给主机的应答
- Register Address,寄存器地址,主机要往哪个寄存器地址写数据
- A,应答信息,从设备给主机的应答
I2C读寄存器时序:
- S,I2C启动信号
- Slave address, I2C从设备地址,W为0,表示写数据
- A,应答信息,从设备给主机的应答
- Register Address,寄存器地址,主机要往哪个寄存器地址写数据
- A,应答信息,从设备给主机的应答
- Sr,重新发起I2C启动信号
- Slave address, I2C从设备地址,W为1,表示读数据
- A,应答信息,从设备给主机的应答
- Register Command,从句返回寄存器的值
- N,主机 主动发送非应答
- P,主机发起停止信号
AP3216C驱动实现
设备树编写
imx6ull-mmc-npi.dts: 向i2c1
节点追加节点ap3216c
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
/* 设备树修改查看是否修改成功,查看内核目录/sys/bus/i2c/devices/看是否存在0x1E地址的设备 */
ap3216c@1E {
compatible = "xgj-ap3216c";
// compatible = "fire,i2c_ap3216c";
reg = <0x1E>;
interrupt-parent = <&gpio5>;
interrupts = <2 IRQ_TYPE_EDGE_BOTH>; //IRQ_TYPE_EDGE_RISING
status = "okay";
};
};
- compatible ,设备树匹配属性
- reg ,i2c设备地址
ap3216c接在i2c1下,在i2c1节点下追加设备的子节点ap3216c
设备树编译:
ares@ubuntu:~/work/ebf_linux_kernel-ebf_4.19.35_imx6ul$ cat make_dtb.sh
#!/bin/sh
make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs
将设备树拷贝系统目录:
debian@npi:~/nfs_root/driver$ cat cp_dtb_to_linux.sh
#!/bin/sh
sudo cp imx6ull-mmc-npi.dtb /usr/lib/linux-image-4.19.35-carp-imx6/
- /usr/lib/linux-image-4.19.35-carp-imx6/ ,系统存放设备树的目录
重启系统设备树生效:
sudo reboot
通过查看系统目录 /sys/firmware/devicetree/base/ 看是否存在ap3216c的设备树节点判定设备树是否生效
驱动实现
ap3216c.h:
寄存器相关的定义
#ifndef _AP3216C_H_
#define _AP3216C_H_
#include <linux/i2c.h>
/* System Registers define */
#define AP3216C_REG_SYS_CONFIG 0x00 /* Control of basic functions */
#define AP3216C_REG_INT_STATUS 0x01 /* ALS and PS interrupt status output */
#define AP3216C_REG_INT_CLEAR_MANNER 0x02 /* Auto/semi clear INT pin selector */
#define AP3216C_REG_IR_DATA_LOW 0x0A /* Lower byte for IR ADC channel output */
#define AP3216C_REG_IR_DATA_HIGH 0x0B /* Higher byte for IR ADC channel output */
#define AP3216C_REG_ALS_DATA_LOW 0x0C /* Lower byte for ALS ADC channel output */
#define AP3216C_REG_ALS_DATA_HIGH 0x0D /* Higher byte for ALS ADC channel output */
#define AP3216C_REG_PS_DATA_LOW 0x0E /* Lower byte for PS ADC channel output */
#define AP3216C_REG_PS_DATA_HIGH 0x0F /* Higher byte for PS ADC channel output */
/* ALS rAP3216C_egisters define */
#define AP3216C_REG_ALS_CONFIG 0x10 /* Control of gain, conversion time of persist for ALS */
#define AP3216C_REG_ALS_CALIBRATION 0X19 /* ALS window loss calibration */
#define AP3216C_REG_ALS_LOW_THRESHOLD_LOW 0x1A /* Lower byte of ALS low threshold */
#define AP3216C_REG_ALS_LOW_THRESHOLD_HIGH 0x1B /* Higher byte of ALS low threshold */
#define AP3216C_REG_ALS_HIGH_THRESHOLD_LOW 0x1C /* Lower byte of ALS high threshold */
#define AP3216C_REG_ALS_HIGH_THRESHOLD_HIGH 0x1D /* Higher byte of ALS high threshold */
/* PS reAP3216C_gisters define */
#define AP3216C_REG_PS_CONFIG 0x20 /* Control of gain, integrated time and persist for PS */
#define AP3216C_REG_PS_LED_DRIVER 0x21 /* Control of LED pulses number and driver current */
#define AP3216C_REG_PS_INT_FORM 0x22 /* Interrupt algorithms style select of PS */
#define AP3216C_REG_PS_MEAN_TIME 0x23 /* PS average time selector */
#define AP3216C_REG_PS_LED_WAITING_TIME 0x24 /* Control PS LED waiting time */
#define AP3216C_REG_PS_CALIBRATION_L 0x28 /* Offset value to eliminate cross talk */
#define AP3216C_REG_PS_CALIBRATION_H 0x29 /* Offset value to eliminate cross talk */
#define AP3216C_REG_PS_LOW_THRESHOLD_LOW 0x2A /* Lower byte of PS low threshold */
#define AP3216C_REG_PS_LOW_THRESHOLD_HIGH 0x2B /* Higher byte of PS low threshold */
#define AP3216C_REG_PS_HIGH_THRESHOLD_LOW 0x2C /* Lower byte of PS high threshold */
#define AP3216C_REG_PS_HIGH_THRESHOLD_HIGH 0x2D /* Higher byte of PS high threshold */
enum sys_config_value {
AP3216C_SYS_CONF_POWER_DOWN = 0x000,
AP3216C_SYS_CONF_ALS_ACTIVE = 0x001,
AP3216C_SYS_CONF_PS_IR_ACTIVE = 0x002,
AP3216C_SYS_CONF_ALS_PS_IR_ACTIVE = 0x03,
AP3216C_SYS_CONF_SW_RESET = 0x04, /* software reset */
AP3216C_SYS_CONF_ALS_ONCE = 0x05,
AP3216C_SYS_CONF_PS_IR_ONCE = 0x06,
AP3216C_SYS_CONF_ALS_PS_IRONCE = 0x07,
};
#endif /* _AP3216C_H_ */
使用内核i2c子系统去驱动ap3216c:
static int ap3216c_i2c_write_reg(struct i2c_client *client, uint8_t reg_addr, uint8_t data)
{
int ret = 0;
#if 0
struct i2c_msg msgs[2];
msgs[0].addr = client->addr;
msgs[0].buf = ®_addr; /* 发送寄存器地址 */
msgs[0].flags = 0; /* I2C方向 :写数据 */
msgs[0].len = sizeof(reg_addr);
msgs[1].addr = client->addr;
msgs[1].buf = &data; /* 写数据 */
msgs[1].flags = 0; /* I2C方向 :写数据 */
msgs[1].len = sizeof(data);
#else
uint8_t buf[2];
struct i2c_msg msg;
buf[0] = reg_addr;
buf[1] = data;
msg.addr = client->addr;
msg.buf = buf; /* 发送寄存器地址 */
msg.flags = 0; /* I2C方向 :写数据 */
msg.len = 2;
#endif
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret < 0)
return ret;
else if (ret != 1)
return -EIO;
return 0;
}
static int ap3216c_i2c_read_reg(struct i2c_client *client, uint8_t reg_addr, uint8_t *data)
{
int ret = 0;
struct i2c_msg msgs[2];
msgs[0].addr = client->addr;
msgs[0].buf = ®_addr; /* 发送寄存器地址 */
msgs[0].flags = 0; /* I2C方向 :写数据 */
msgs[0].len = sizeof(reg_addr);
msgs[1].addr = client->addr;
msgs[1].buf = data; /* 读取寄存器数据 */
msgs[1].flags = I2C_M_RD; /* I2C方向 :读数据 */
msgs[1].len = 1;
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret < 0)
return ret;
else if (ret != 2)
return -EIO;
return 0;
}
static int ap3216c_init(void)
{
int ret;
/* 软复位 */
ret = ap3216c_i2c_write_reg(ap3216c_dev->client, AP3216C_REG_SYS_CONFIG, AP3216C_SYS_CONF_SW_RESET);
msleep(50);
/* 开启ALS、PS、IR*/
ret = ap3216c_i2c_write_reg(ap3216c_dev->client, AP3216C_REG_SYS_CONFIG, AP3216C_SYS_CONF_ALS_PS_IR_ACTIVE);
return ret;
}
static int ap3216c_read_als(short int *als)
{
int ret;
uint8_t low, high;
ret = ap3216c_i2c_read_reg(ap3216c_dev->client, AP3216C_REG_ALS_DATA_LOW, &low);
ret = ap3216c_i2c_read_reg(ap3216c_dev->client, AP3216C_REG_ALS_DATA_HIGH, &high);
*als = (high << 8) + low;
return ret;
}
static int ap3216c_read_ps(short int *ps)
{
int ret;
uint8_t low, high;
ret = ap3216c_i2c_read_reg(ap3216c_dev->client, AP3216C_REG_PS_DATA_LOW, &low);
ret = ap3216c_i2c_read_reg(ap3216c_dev->client, AP3216C_REG_PS_DATA_HIGH, &high);
*ps = (high << 8) + low;
return ret;
}
static int ap3216c_read_ir(short int *ir)
{
int ret;
uint8_t low, high;
ret = ap3216c_i2c_read_reg(ap3216c_dev->client, AP3216C_REG_IR_DATA_LOW, &low);
ret = ap3216c_i2c_read_reg(ap3216c_dev->client, AP3216C_REG_IR_DATA_HIGH, &high);
*ir = (high << 8) + low;
return ret;
}
熟透于心的linux驱动的框架(套路):
#define AP3216C_DTS_NODE_PATH "xgj_ap3216c"
#define AP3216C_DTS_COMPATIBLE "xgj-ap3216c"
#define AP3216C_READY_GPIO_NAME "ready-gpios"
#define DEV_NAME "ap3216c"
struct ap3216c_data {
short int als; //环境光亮度传感器数据
short int ps; //接近传感器数据
short int ir; //红外LED
};
struct ap3216c_device {
int irq; /* 中断号 */
int gpio;
dev_t dev_no; /* 设备号 */
struct cdev chrdev;
struct class *class;
struct mutex m_lock;
wait_queue_head_t wq; /* 等待队列 */
struct ap3216c_data data;
struct i2c_client *client;
};
static struct ap3216c_device *ap3216c_dev;
static int _drv_open (struct inode *node, struct file *file)
{
int ret = 0;
file->private_data = ap3216c_dev;
if ((ret = ap3216c_init()) != 0)
{
printk("ap3216c init failed %d\r\n", ret);
}
printk("%s open\r\n", DEV_NAME);
return ret;
}
static ssize_t _drv_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
struct ap3216c_device *tmp_ap3216c = filp->private_data;
/* 中断屏蔽 */
if (size != sizeof(struct ap3216c_data)) return -EINVAL;
ret = ap3216c_read_als(&ap3216c_dev->data.als);
ret = ap3216c_read_ps(&ap3216c_dev->data.ps);
ret = ap3216c_read_ir(&ap3216c_dev->data.ir);
if (copy_to_user(buf, &tmp_ap3216c->data, sizeof(struct ap3216c_data)))
{
ret = -EFAULT;
}
else
{
ret = sizeof(struct ap3216c_data);
}
return ret;
}
/* 使驱动支持多路复用IO */
static __poll_t _drv_poll(struct file *filp, struct poll_table_struct *wait)
{
__poll_t mask = 0;
// mutex_lock(&ap3216c_dev->m_lock);
// poll_wait(filp, &ap3216c_dev->wq, wait);
// mutex_unlock(&sr501.m_lock);
return mask;
}
static long _drv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
return ret;
}
static int _drv_release(struct inode *node, struct file *filp)
{
struct ap3216c_device *tmp_ap3216c = filp->private_data;
printk("%s close\n", DEV_NAME);
return 0;
}
static struct file_operations ap3216c_drv_ops = {
.owner = THIS_MODULE,
.open = _drv_open,
.read = _drv_read,
// .poll = _drv_poll,
.release = _drv_release,
};
static irqreturn_t ap3216c_isr(int irq, void *dev)
{
printk("%s %d %s\n", __FILE__, __LINE__, __FUNCTION__);
return IRQ_RETVAL(IRQ_HANDLED);
}
static int _driver_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int err = 0;
int ret;
struct device *_dev;
struct device_node *_dts_node;
ap3216c_dev = (struct ap3216c_device *) kzalloc(sizeof(struct ap3216c_device), GFP_KERNEL);
if (!ap3216c_dev)
{
printk("can't kzalloc ap3216c\n");
return -ENOMEM;
}
_dts_node = client->dev.of_node;
if (!_dts_node) {
printk("AP3216C dts node can not found!\r\n");
err = -EINVAL;
goto out_free_dev;
}
ap3216c_dev->client = client;
printk("AP3216C irq %d !\r\n", client->irq);
#if 1
ap3216c_dev->irq = client->irq;
err = request_irq(ap3216c_dev->irq, ap3216c_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, DEV_NAME, NULL); /* 申请中断 */
if (err) {
printk(KERN_INFO"failed to request irq %d\r\n", ap3216c_dev->irq);
goto out_free_dev;
}
/* 内核自动分配设备号 */
err = alloc_chrdev_region(&ap3216c_dev->dev_no, 0, 1, DEV_NAME);
if (err < 0) {
pr_err("Error: failed to register mbochs_dev, err: %d\n", err);
goto out_free_irq;
}
cdev_init(&ap3216c_dev->chrdev, &ap3216c_drv_ops);
err = cdev_add(&ap3216c_dev->chrdev, ap3216c_dev->dev_no, 1);
if (err) {
goto out_unregister;
}
ap3216c_dev->class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(ap3216c_dev->class)) {
err = PTR_ERR(ap3216c_dev->class);
goto out_cdev_del;
}
/* 创建设备节点 */
_dev = device_create(ap3216c_dev->class , NULL, ap3216c_dev->dev_no, NULL, DEV_NAME);
if (IS_ERR(_dev)) { /* 判断指针是否合法 */
err = PTR_ERR(_dev);
goto out_class_del;
}
// mutex_init(&ap3216c_dev->m_lock); /* 初始化互斥锁 */
printk("ap3216c probe success\r\n");
goto out;
out_class_del:
class_destroy(ap3216c_dev->class);
out_cdev_del:
cdev_del(&ap3216c_dev->chrdev);
out_unregister:
unregister_chrdev_region(ap3216c_dev->dev_no, 1); /* 注销设备 */
out_free_irq:
free_irq(ap3216c_dev->irq, NULL);
out_free_dev:
kfree(ap3216c_dev);
ap3216c_dev = NULL;
out:
#endif
return err;
}
static int _driver_remove(struct i2c_client *client)
{
device_destroy(ap3216c_dev->class, ap3216c_dev->dev_no);
class_destroy(ap3216c_dev->class);
unregister_chrdev_region(ap3216c_dev->dev_no, 1); /* 注销设备 */
cdev_del(&ap3216c_dev->chrdev);
free_irq(ap3216c_dev->irq, NULL); /* 释放中断*/
kfree(ap3216c_dev);
printk(KERN_INFO"%s success\n", DEV_NAME);
return 0;
}
/* 设备树的匹配列表 */
static struct of_device_id dts_match_table[] = {
{.compatible = AP3216C_DTS_COMPATIBLE, }, /* 通过设备树来匹配 */
};
/* 传统匹配方式 ID 列表 ,即使不使用也要添加,不然probe匹配不成功 */
static const struct i2c_device_id ap3216c_id[] = {
{AP3216C_DTS_COMPATIBLE, 0},
};
static struct i2c_driver ap3216c_driver = {
.probe = _driver_probe,
.remove = _driver_remove,
.driver = {
.name = AP3216C_DTS_COMPATIBLE,
.owner = THIS_MODULE,
.of_match_table = dts_match_table, /* 通过设备树匹配 */
},
.id_table = ap3216c_id,
};
/* 入口函数 */
static int __init _driver_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ap3216c_driver); /* 注册I2C驱动 */
printk("%s %s\n", DEV_NAME, __FUNCTION__);
return ret;
}
/* 出口函数 */
static void __exit _driver_exit(void)
{
printk("%s driver %s\n", DEV_NAME, __FUNCTION__);
i2c_del_driver(&ap3216c_driver); /* 注销I2C驱动 */
}
module_init(_driver_init);
module_exit(_driver_exit);
MODULE_AUTHOR("Ares");
MODULE_LICENSE("GPL");
必须注意的地方:
I2C总线驱动内核缺陷,即使不用i2c_device_id也要加上,不然设备树匹配方式of_device_id 无法匹配成功
测试程序:
#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <stdint.h>
#define DEV_NAME "/dev/ap3216c"
struct ap3216c_data {
short int als; /* 环境光亮度传感器数据 */
short int ps; /* 接近传感器数据 */
short int ir; /* 红外LED */
};
void sleep_ms(unsigned int ms)
{
struct timeval delay;
delay.tv_sec = 0;
delay.tv_usec = ms * 1000;
select(0, NULL, NULL, NULL, &delay);
}
int main(int argc, char **argv)
{
int fd;
int ret;
struct pollfd fds[1];
/* 2. 打开文件 */
fd = open(DEV_NAME, O_RDWR ); // | O_NONBLOCK
if (fd < 0)
{
printf("can not open file %s, %d\n", DEV_NAME, fd);
return -1;
}
struct ap3216c_data data;
while (1)
{
if (read(fd, &data, sizeof(data)) == sizeof(data))
{
printf("als %d ps %d ir %d\r\n", data.als, data.ps, data.ir);
}
sleep_ms(1000);
}
close(fd);
printf("%s ok!\n", DEV_NAME);
return 0;
}
安装、卸载and执行测试程序:
sudo insmod ap3216c_drv.ko
sudo ./ap3216c_test
sudo rmmod ap3216c_drv.ko