linux sensor驱动,i2c,input,sysfs
目录简介:设备树设置:驱动代码编写:注册i2c总线:读取寄存器值input使用注册inputinput上报delay work使用sysfs使用简介:传感器,ap3216c,红外,光感,距离三合一传感器平台,正点原子linux imx6ull开发版,linux 4.1.15内核实现目标:1. 使能sensor后,定时通过input上报传感器值2. 上报周期可以通过sysfs 设置,使能和失能也可以
目录
简介:
- 传感器,ap3216c,红外,光感,距离三合一传感器
- 平台,正点原子linux imx6ull开发版,linux 4.1.15内核
实现目标:
1. 使能sensor后,定时通过input上报传感器值
2. 上报周期可以通过sysfs 设置,使能和失能也可以通过sysfs设置
具体代码,已经上传gitee,https://gitee.com/king_ocean/linux/tree/master/i2c
设备树设置:
传感器是i2c器件,挂载在i2c1上,器件地址为0x1e。
主要是设置compatiable 属性和 i2c地址,compatiable属性用于和驱动匹配,器件地址用于i2c寻址
查看设备树是否更新成功。
root@ATK-IMX6U:/proc/device-tree/soc/aips-bus@02100000/i2c@021a0000/
查看该目录下是否有生成自己新加的节点,进去目录,可以查看compatible属性,目录里的地址是我的板子的 i2c0 主机地址,不同的板子不一样
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
ap3216c@1e {
compatible = "alientek,ap3216c";
reg = <0x1e>;
};
};
驱动代码编写:
驱动代码编写分为5个部分,注册i2c总线,通过i2c读取传感器值,使用input上报数据,使用delay work进行定时上报,添加sysfs属性用来控制上报周期和使能失能传感器
先来看下总的数据结构定义。
struct ap3216c_data {
struct i2c_client *client;
struct input_dev *input;
struct delayed_work ap_work; /* delay work用来定时采集传感器数据 */
int delay; /* delay的时间,ms为单位 */
struct ap3216c_value value; /* 传感器值 */
int enable; /* 传感器工作状态,1 表示使能,0表示power down */
};
传感器值数据结构定义。
/* ap3216c 数据 */
struct ap3216c_value {
unsigned short ir;
unsigned short als;
unsigned short ps;
};
注册i2c总线:
一般linux驱动,我们都会将其挂载在一种总线下,如i2c总线,spi总线,platfrom总线。
首先需要定义一个 i2c driver,如下代码里几个成员变量是必须要实现的
probe函数,在加载驱动时,如果设备和驱动能匹配上就会执行probe函数,传统方式是通过id_table进行匹配,现在基本都是使用设备树,通过of_match_table匹配,即compatible 属性值和设备树中的必须完全一致,否则probe函数不会执行。
remove函数,卸载驱动时,执行remove函数
driver中的 owner name of_match_table 都需要初始化
id_table也需要初始化,实测如果id_table不初始化,会导致 probe 不执行,具体原因不清楚
以上初始化完成后,在module_init函数中注册i2c驱动,使用API i2c_add_driver
按照如下配置之后,insmod或者modprobe加载驱动时,就会执行probe函数
/* 传统方式的id匹配 */
static const struct i2c_device_id ap3216c_id[] = {
{"alientek,ap3216c", 0},
{}
};
/* of match,设备树匹配 */
static const struct of_device_id ap3216c_of_match[] = {
{.compatible = "alientek,ap3216c"},
{},
};
/* i2c driver,需要同时定义 id匹配和设备树两种匹配方式 */
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.owner = THIS_MODULE,
.name = AP3216C_NAME,
.of_match_table = ap3216c_of_match,
},
.id_table = ap3216c_id,
};
// module_i2c_driver(ap3216c_driver);
static int __init ap3216c_init(void)
{
int ret = 0;
KERNEL_LOG("enter");
ret = i2c_add_driver(&ap3216c_driver);
return ret;
}
static void __exit ap3216c_exit(void)
{
KERNEL_LOG("enter");
i2c_del_driver(&ap3216c_driver);
}
module_init(ap3216c_init);
module_exit(ap3216c_exit);
读取寄存器值
i2c的读写时序如下,具体可以参考 正点原子 IMX6U 嵌入式开发指南。
写时序,先通过i2c发送从机器件地址,最后一位是读写标志位,然后发送要写入的寄存器地址,然后是要写入的数据,如下
读时序,先通过i2c总线发送从机器件地址,最后一位读写标志位,(注意此处是写,因为要先写入要读取数据的寄存器地址),然后发送要读取的寄存器的地址,然后发送从机器件地址,最后一位是读,接着从机返回数据给主机
代码中单字节的读取,i2c-core 提供了API接口,如下:
comamnd对应的是寄存器地址,读取到的数据直接通过函数返回值进行返回,写入的值放在参数value中
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)
s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value)
初始化ap3216c
复位,设置工作模式为同时采集ir,als,ps。然后进入power down。
#define AP3216C_CMD_POWER_DOWN 0
#define AP3216C_CMD_RESET 0x04 /* 复位 */
#define AP3216C_CMD_ENABLE_ALL 0x03 /* 打开ALS,IR,PS */
/**
* @description: ap3216c 硬件初始化,reset,设置功能模式,默认下电状态
* @param {struct ap3216c_data} *p_ap3216c
* @return {*}
*/
static int ap3216c_hw_init(struct ap3216c_data *p_ap3216c)
{
int ret, value;
ret = i2c_smbus_write_byte_data(p_ap3216c->client, AP3216C_SYSTEMCONG, AP3216C_CMD_RESET);
if (ret < 0) {
KERNEL_LOG("can't write reg 0x%x : %d",
AP3216C_SYSTEMCONG, AP3216C_CMD_RESET);
return ret;
}
mdelay(RESET_TIME);
ret = i2c_smbus_write_byte_data(p_ap3216c->client, AP3216C_SYSTEMCONG, AP3216C_CMD_ENABLE_ALL);
if (ret < 0) {
KERNEL_LOG("can't write reg 0x%x : %d",
AP3216C_SYSTEMCONG, AP3216C_CMD_ENABLE_ALL);
return ret;
}
value = i2c_smbus_read_byte_data(p_ap3216c->client, AP3216C_SYSTEMCONG);
if (value != AP3216C_CMD_ENABLE_ALL) {
KERNEL_LOG("config reg set failed, config %d", value);
return -EINVAL;
}
ret = i2c_smbus_write_byte_data(p_ap3216c->client, AP3216C_SYSTEMCONG, AP3216C_CMD_POWER_DOWN);
if (ret < 0) {
KERNEL_LOG("can't write reg 0x%x : %d",
AP3216C_SYSTEMCONG, AP3216C_CMD_POWER_DOWN);
return ret;
}
return ret;
}
读取传感器值
ir,als,ps对应的值都是不超过16位,但是超过8位,所以都是用short类型,如下的转换是根据数据手册来的。
/**
* @description: 读取ap3216c的 ir,als,ps 到 ap3216c_data
* @param {struct ap3216c_data} *p_ap3216c 这个驱动的数据
* @return {*} 0,success
*/
static int ap3216c_read_data(struct ap3216c_data *p_ap3216c)
{
int i;
unsigned char buf[6];
struct ap3216c_value *value;
KERNEL_LOG("enter");
for (i = 0; i < 6; i++) {
// ap3216c_i2c_read_one_reg(p_ap3216c->client, buf+i, AP3216C_IRDATALOW + i);
buf[i] = i2c_smbus_read_byte_data(p_ap3216c->client, AP3216C_IRDATALOW + i);
// KERNEL_LOG("%d", buf[i]);
}
value = &p_ap3216c->value;
if (buf[0] & 0x80)
value->ir = 0;
else
value->ir = (buf[1] << 2) | (buf[0] & 0x03);
value->als = (buf[3] << 8) | buf[2];
if (buf[4] & 0x40)
value->ps = 0;
else
value->ps = ((buf[5] & 0x3F) << 4) | (buf[4] & 0x0F);
KERNEL_LOG("ir %d, als %d, ps %d", value->ir, value->als, value->ps);
return 0;
}
input使用
注册input
注册到input子系统,通过input上报传感器数据,然后上层应用可以通过input子系统获取到传感器值,此处使用abs类型,注意需要将 驱动自定义的数据放入input dev,input_set_drvdata(idev, p_ap3216c); 便于后面通过input dev 获取到 驱动数据
struct input_dev *idev;
idev = input_allocate_device();
if (idev == NULL) {
KERNEL_LOG("ERROR: alloc input dev failed");
goto input_alloc_failed;
}
p_ap3216c->input = idev;
idev->name = AP3216C_NAME;
idev->id.bustype = BUS_I2C;
idev->dev.parent = &client->dev;
idev->open = ap3216c_open;
idev->close = ap3216c_close;
__set_bit(EV_ABS, idev->evbit);
input_set_abs_params(idev, ABS_X, AP3216C_IR_MAX, AP3216C_IR_MIN, 0, 0);
input_set_abs_params(idev, ABS_Y, AP3216C_ALS_MAX, AP3216C_ALS_MIN, 0, 0);
input_set_abs_params(idev, ABS_Z, AP3216C_PS_MAX, AP3216C_PS_MIN, 0, 0);
/* 将自定义的数据结构体放到input 设备里 */
input_set_drvdata(idev, p_ap3216c);
ret = input_register_device(idev);
if (ret) {
KERNEL_LOG("ERROR: register input dev failed");
goto input_reg_failed;
}
input注册成功后,会生成input节点,加载驱动时,会有如下打印,则说明input节点注册成功,可以到sys对应的目录下查看具体信息
[ 541.566949] input: ap3216c as /devices/platform/soc/2100000.aips-bus/21a0000.i2c/i2c-0/0-001e/input/input2
input上报
先读取传感器数据,然后使用 input_report_abs进行上报,注意上报数据后一定要调用input_sync j进行同步
ap3216c_read_data(p_ap3216c);
value = &p_ap3216c->value;
input_report_abs(p_ap3216c->input, ABS_X, value->ir);
input_report_abs(p_ap3216c->input, ABS_Y, value->als);
input_report_abs(p_ap3216c->input, ABS_Z, value->ps);
KERNEL_LOG("ir %d, als %d, ps %d", value->ir, value->als, value->ps);
input_sync(p_ap3216c->input);
delay work使用
使用delay work实现定时上报
初始化 dealy work,调用如下函数,第二个参数是回调函数,在此回调函数里做具体上报工作。
INIT_DELAYED_WORK(&p_ap3216c->ap_work, work_func);
调用 schedule_delayed_work(&p_ap3216c->ap_work, delay); 开始执行,delay时间到后,回调work_func函数
struct ap3216c_data {
struct i2c_client *client;
struct input_dev *input;
struct delayed_work ap_work; /* delay work用来定时采集传感器数据 */
int delay; /* delay的时间,ms为单位 */
struct ap3216c_value value; /* 传感器值 */
int enable; /* 传感器工作状态,1 表示使能,0表示power down */
};
INIT_DELAYED_WORK(&p_ap3216c->ap_work, work_func);
delay = msecs_to_jiffies(p_ap3216c->delay); //延时时间
ret = schedule_delayed_work(&p_ap3216c->ap_work, delay);
sysfs使用
创建sysfs ,可以直接通过echo,cat形式访问 驱动代码。我们创建两个属性,delay,实现通过sysfs节点读取和设置 上报周期效果。ebable,实现通过sysfs节点读取 传感器工作状态,设置传感器使能,关闭。
通过DEVICE_ATTR 设置属性的访问权限,对应的show,store函数。
定义属性 static struct attribute *ap3216c_attributes[] = {};
定义属性 组 static struct attribute_group ap3216c_attribute_group
static DEVICE_ATTR(delay, S_IRUGO | S_IWUSR | S_IWGRP,
ap3216c_delay_show, ap3216c_delay_store);
static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
ap3216c_enable_show, ap3216c_enable_store);
static struct attribute *ap3216c_attributes[] = {
&dev_attr_delay.attr,
&dev_attr_enable.attr,
NULL
};
static struct attribute_group ap3216c_attribute_group = {
.attrs = ap3216c_attributes
};
定义对应的show,store函数
ssize_t ap3216c_delay_store(struct device *dev, struct attribute *attr, const char *buf, size_t count)
{
struct ap3216c_data *p_ap3216c;
int tmp_delay;
struct i2c_client *client;
KERNEL_LOG("buf: %s", buf);
client = to_i2c_client(dev);
p_ap3216c = i2c_get_clientdata(client);
kstrtoint(buf, 10, &tmp_delay);
KERNEL_LOG("tmp_delay %d", tmp_delay);
if (tmp_delay > 0 && tmp_delay < AP3216C_DELAY_MAC) {
p_ap3216c->delay = tmp_delay;
} else {
KERNEL_LOG("ERROR: the delay value %d is unvalid", tmp_delay);
}
return count;
}
static ssize_t ap3216c_enable_show(struct device *dev, struct attribute *attr, char *buf)
{
struct ap3216c_data *p_ap3216c;
int count;
struct i2c_client *client;
KERNEL_LOG("enter");
client = to_i2c_client(dev);
p_ap3216c = i2c_get_clientdata(client);
snprintf(buf, 8, "%d\n", p_ap3216c->enable);
count = strlen(buf);
return count;
return 0;
}
ssize_t ap3216c_enable_store(struct device *dev, struct attribute *attr, const char *buf, size_t count)
{
struct ap3216c_data *p_ap3216c;
int tmp_enable;
struct i2c_client *client;
KERNEL_LOG("buf: %s", buf);
client = to_i2c_client(dev);
p_ap3216c = i2c_get_clientdata(client);
kstrtoint(buf, 10, &tmp_enable);
KERNEL_LOG("tmp_enable %d", tmp_enable);
if (tmp_enable == 0) {
p_ap3216c->enable = 0;
ap3216c_disable(p_ap3216c);
} else {
p_ap3216c->enable = 1;
ap3216c_enable(p_ap3216c);
}
return count;
}
static DEVICE_ATTR(delay, S_IRUGO | S_IWUSR | S_IWGRP,
ap3216c_delay_show, ap3216c_delay_store);
static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,
ap3216c_enable_show, ap3216c_enable_store);
static struct attribute *ap3216c_attributes[] = {
&dev_attr_delay.attr,
&dev_attr_enable.attr,
NULL
};
static struct attribute_group ap3216c_attribute_group = {
.attrs = ap3216c_attributes
};
在probe函数中注册sysfs group
ret = sysfs_create_group(&p_ap3216c->client->dev.kobj, &ap3216c_attribute_group);
通过如下方法操作sysfs节点
更多推荐
所有评论(0)