Linux驱动 | AP3216C驱动(I2C)

I2C子系统

linux驱动 | I2C子系统_★_仰望星空_★的博客-CSDN博客
https://blog.csdn.net/qq_36413982/article/details/123767630

AP3216C

AP3216C是敦南科技退出的一款三合一环境传感器:

  • 数字环境光传感器(ALS)
  • 接近传感器(PS)
  • 一个红外LED(IR)

通信接口:

  • 通过IIC接口和MCU连接,并支持中断(INT)输出

应用:

  • AP3216C被广泛应用于智能手机上面,用来检测光强度(自动背光控制),和接近开关控制(听筒靠近耳朵,手机自动灭屏功)

寄存器表:
在这里插入图片描述
在这里插入图片描述
寄存器描述:
在这里插入图片描述
I2C写寄存器时序:
在这里插入图片描述

  1. S,I2C启动信号
  2. Slave address, I2C从设备地址
  3. W,该位和Slave address组成一个字节,地址字节最低位,其值为0表示操作方向为写,1则表示读
  4. A,应答信息,从设备给主机的应答
  5. Register Address,寄存器地址,主机要往哪个寄存器地址写数据
  6. A,应答信息,从设备给主机的应答

I2C读寄存器时序:
在这里插入图片描述

  1. S,I2C启动信号
  2. Slave address, I2C从设备地址,W为0,表示写数据
  3. A,应答信息,从设备给主机的应答
  4. Register Address,寄存器地址,主机要往哪个寄存器地址写数据
  5. A,应答信息,从设备给主机的应答
  6. Sr,重新发起I2C启动信号
  7. Slave address, I2C从设备地址,W为1,表示读数据
  8. A,应答信息,从设备给主机的应答
  9. Register Command,从句返回寄存器的值
  10. N,主机 主动发送非应答
  11. 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 = &reg_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 = &reg_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
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
以下是一个简单的示例代码,展示了如何在Linux系统上编写i2c驱动程序以控制AP3216C传感器: ```c #include <linux/i2c-dev.h> #include <linux/i2c.h> #define AP3216C_I2C_ADDR 0x1e static int ap3216c_read(struct i2c_client *client, u8 reg, u8 *data, int len) { struct i2c_msg msgs[2] = { { .addr = client->addr, .flags = 0, .len = 1, .buf = &reg, }, { .addr = client->addr, .flags = I2C_M_RD, .len = len, .buf = data, }, }; return i2c_transfer(client->adapter, msgs, 2); } static int ap3216c_write(struct i2c_client *client, u8 reg, u8 *data, int len) { u8 *buf = kzalloc(len + 1, GFP_KERNEL); int ret; if (!buf) return -ENOMEM; buf[0] = reg; memcpy(buf + 1, data, len); struct i2c_msg msg = { .addr = client->addr, .flags = 0, .len = len + 1, .buf = buf, }; ret = i2c_transfer(client->adapter, &msg, 1); kfree(buf); return ret; } static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id) { int ret; u8 data[2]; ret = ap3216c_read(client, 0x00, data, 2); if (ret < 0) { dev_err(&client->dev, "Failed to read AP3216C chip ID\n"); return ret; } dev_info(&client->dev, "AP3216C chip ID: %x\n", data[1]); /* TODO: Write initialization sequence here */ return 0; } static const struct i2c_device_id ap3216c_id[] = { { "ap3216c", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, ap3216c_id); static struct i2c_driver ap3216c_driver = { .driver = { .name = "ap3216c", }, .probe = ap3216c_probe, .id_table = ap3216c_id, }; module_i2c_driver(ap3216c_driver); ``` 这个示例代码定义了两个函数:`ap3216c_read`和`ap3216c_write`,分别用于读和写AP3216C寄存器。该驱动程序在初始化过程中会通过I2C总线读取AP3216C芯片的ID,并可以在该函数中添加其他初始化代码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欲盖弥彰1314

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值