blob: 5bc32764341070672945a995db7fc05802803d3a [file] [log] [blame]
/*
* ap3426.c - Linux kernel modules for DynaImage ambient light + proximity
*sensor ap3426
*
* Copyright (C) 2015 Jian Zhou
* Copyright (C) 2015 Marvell Technologies
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/input.h>
#include <linux/regmap.h>
#include <linux/ioctl.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/regulator/consumer.h>
#include <linux/of_gpio.h>
#include <linux/pm.h>
struct ap3426_platform_data {
unsigned int irq;
char avdd_name[20];
};
//#undef pr_debug
//#define pr_debug pr_info
#define AP3426_I2C_NAME "ap3426"
#define AP3426_LIGHT_INPUT_NAME "ap3426-light"
#define AP3426_PROXIMITY_INPUT_NAME "ap3426-proximity"
/* AP3426 registers */
#define AP3426_REG_CONFIG 0x00
#define AP3426_REG_INT_FLAG 0x01
#define AP3426_REG_INT_CTL 0x02
#define AP3426_REG_WAIT_TIME 0x06
#define AP3426_REG_IR_DATA_LOW 0x0A
#define AP3426_REG_IR_DATA_HIGH 0x0B
#define AP3426_REG_ALS_DATA_LOW 0x0C
#define AP3426_REG_ALS_DATA_HIGH 0x0D
#define AP3426_REG_PS_DATA_LOW 0x0E
#define AP3426_REG_PS_DATA_HIGH 0x0F
#define AP3426_REG_ALS_GAIN 0x10
#define AP3426_REG_ALS_PERSIST 0x14
#define AP3426_REG_ALS_LOW_THRES_0 0x1A
#define AP3426_REG_ALS_LOW_THRES_1 0x1B
#define AP3426_REG_ALS_HIGH_THRES_0 0x1C
#define AP3426_REG_ALS_HIGH_THRES_1 0x1D
#define AP3426_REG_PS_GAIN 0x20
#define AP3426_REG_PS_LED_DRIVER 0x21
#define AP3426_REG_PS_INT_FORM 0x22
#define AP3426_REG_PS_MEAN_TIME 0x23
#define AP3426_REG_PS_SMART_INT 0x24
#define AP3426_REG_PS_INT_TIME 0x25
#define AP3426_REG_PS_PERSIST 0x26
#define AP3426_REG_PS_CAL_L 0x28
#define AP3426_REG_PS_CAL_H 0x29
#define AP3426_REG_PS_LOW_THRES_0 0x2A
#define AP3426_REG_PS_LOW_THRES_1 0x2B
#define AP3426_REG_PS_HIGH_THRES_0 0x2C
#define AP3426_REG_PS_HIGH_THRES_1 0x2D
#define AP3426_ALS_SENSITIVITY 0x10
#define AP3426_PS_SENSITIVITY 0x20
static struct regmap_config ap3426_regmap_config = {
.reg_bits = 8, .val_bits = 8,
};
static int gain_table[] = {32768, 8192, 2048, 512};
/* PS distance table */
static int ps_distance_table[] = {1023, 740, 340, 200, 180, 176};
#define AP3426_DRV_NAME "dyna,ap3426"
#define DRIVER_VERSION "1.0.0"
#define ABS_LIGHT 0x29 /* added to support LIGHT - light sensor */
#define AP3426_PS_DETECTION_THRESHOLD 150
#define AP3426_PS_HSYTERESIS_THRESHOLD 130
#define AP3426_ALS_THRESHOLD_HSYTERESIS 20 /* 20 = 20% */
#define DEVICE_ATTR2(_name, _mode, _show, _store) \
struct device_attribute dev_attr2_##_name = \
__ATTR(_name, _mode, _show, _store)
#define AP_IOCTL_PS_ENABLE 1
#define AP_IOCTL_PS_GET_ENABLE 2
#define AP_IOCTL_PS_POLL_DELAY 3
#define AP_IOCTL_ALS_ENABLE 4
#define AP_IOCTL_ALS_GET_ENABLE 5
#define AP_IOCTL_ALS_POLL_DELAY 6
#define AP_IOCTL_PS_GET_PDATA 7 /* pdata */
#define AP_IOCTL_ALS_GET_CH0DATA 8 /* ch0data */
#define AP_IOCTL_ALS_GET_CH1DATA 9 /* ch1data */
#define AP_DISABLE_PS 0
#define AP_ENABLE_PS 1
#define AP_DISABLE_ALS 0
#define AP_ENABLE_ALS_WITH_INT 1
#define AP_ENABLE_ALS_NO_INT 2
#define AP_ALS_POLL_SLOW 0 /* 1 Hz (1s) */
#define AP_ALS_POLL_MEDIUM 1 /* 10 Hz (100ms) */
#define AP_ALS_POLL_FAST 2 /* 20 Hz (50ms) */
enum {
AP3426_ALS_RES_27MS = 0, /* 27.2ms integration time */
AP3426_ALS_RES_51MS = 1, /* 51.68ms integration time */
AP3426_ALS_RES_100MS = 2, /* 100.64ms integration time */
} ap3426_als_res_e;
/*
* Structs
*/
struct ap3426_data {
struct i2c_client *client;
struct regmap *regmap;
struct mutex update_lock;
struct delayed_work dwork; /* for PS interrupt */
struct delayed_work als_dwork; /* for ALS polling */
struct input_dev *input_dev_als;
struct input_dev *input_dev_ps;
struct regulator *avdd;
int irq;
int suspended;
unsigned int enable_suspended_value; /* suspend_resume usage */
unsigned int enable;
unsigned int atime;
unsigned int ptime;
unsigned int wtime;
unsigned int ailt;
unsigned int aiht;
unsigned int pilt;
unsigned int piht;
unsigned int pers;
unsigned int config;
unsigned int ppcount;
unsigned int control;
int als_cal;
int ps_cal;
int als_gain;
int als_persist;
int ps_gain;
int ps_persist;
int ps_led_driver;
int ps_mean_time;
int ps_integrated_time;
int wait_time;
/* control flag from HAL */
unsigned int enable_ps_sensor;
unsigned int enable_als_sensor;
/* PS parameters */
unsigned int ps_threshold;
unsigned int ps_hysteresis_threshold; /* always lower than ps_threshold */
unsigned int ps_detection; /* 0 = near-to-far; 1 = far-to-near */
unsigned int ps_data; /* to store PS data */
/* ALS parameters */
unsigned int als_threshold_l; /* low threshold */
unsigned int als_threshold_h; /* high threshold */
unsigned int als_data; /* to store ALS data */
int als_prev_lux; /* to store previous lux value */
unsigned int
als_poll_delay; /* needed for light sensor polling : micro-second (us) */
struct ap3426_platform_data *pdata; /* platform data */
};
/*
* Global data
*/
static struct i2c_client *
ap3426_i2c_client; /* global i2c_client to support ioctl */
static struct workqueue_struct *ap3426_workqueue;
static void ap3426_change_ps_threshold(struct i2c_client *client) {
struct ap3426_data *data = i2c_get_clientdata(client);
// Todo: get ps_data from ap3426; data->ps_data = 0;
if ((data->ps_data > data->pilt) && (data->ps_data >= data->piht)) {
/* far-to-near detected */
data->ps_detection = 1;
data->ps_data = 2;
/* FAR-to-NEAR detection */
input_report_abs(data->input_dev_ps, ABS_DISTANCE, data->ps_data);
input_sync(data->input_dev_ps);
// Todo: write threshold to ap3426
data->pilt = data->ps_hysteresis_threshold;
data->piht = 1023;
pr_debug("far-to-near detected\n");
} else if ((data->ps_data <= data->pilt) && (data->ps_data < data->piht)) {
/* near-to-far detected */
data->ps_detection = 0;
/* NEAR-to-FAR detection */
input_report_abs(data->input_dev_ps, ABS_DISTANCE, 10);
input_sync(data->input_dev_ps);
// Todo: write threshold to ap3426
data->pilt = 0;
data->piht = data->ps_threshold;
pr_debug("near-to-far detected\n");
}
pr_debug("high threshhold change to %d, the low threshhold change to %d",
data->pilt, data->piht);
}
static void ap3426_reschedule_work(struct ap3426_data *data,
unsigned long delay) {
/*
* If work is already scheduled then subsequent schedules will not
* change the scheduled time that's why we have to cancel it first.
*/
cancel_delayed_work(&data->dwork);
queue_delayed_work(ap3426_workqueue, &data->dwork, delay);
}
/* ALS polling routine */
static void ap3426_als_polling_work_handler(struct work_struct *work) {
struct ap3426_data *data =
container_of(work, struct ap3426_data, als_dwork.work);
struct i2c_client *client = data->client;
u8 als_data[4];
u8 ps_data[4];
int luxValue = 0;
int rc;
unsigned int gain;
/* Read data and clear interrupt */
rc = regmap_bulk_read(data->regmap, AP3426_REG_ALS_DATA_LOW, als_data, 2);
if (rc) {
dev_err(&client->dev, "read %d failed.(%d)\n", AP3426_REG_ALS_DATA_LOW, rc);
goto exit;
}
rc = regmap_bulk_read(data->regmap, AP3426_REG_PS_DATA_LOW, ps_data, 2);
if (rc) {
dev_err(&client->dev, "read %d failed.(%d)\n", AP3426_REG_PS_DATA_LOW, rc);
goto exit;
}
/* report value */
gain = gain_table[data->als_gain & 0x3];
luxValue =
(((als_data[0] | (als_data[1] << 8)) * gain) >> 16) * 100 / data->als_cal;
pr_debug("lux:%d als_data:0x%x-0x%x\n", luxValue, als_data[0], als_data[1]);
luxValue = luxValue > 0 ? luxValue : 0;
luxValue = luxValue < 10000 ? luxValue : 10000;
data->als_data = luxValue;
input_report_abs(data->input_dev_als, ABS_PRESSURE,
luxValue); /*report the lux level */
input_sync(data->input_dev_als);
exit:
/* restart timer */
schedule_delayed_work(&data->als_dwork,
msecs_to_jiffies(data->als_poll_delay));
}
/* PS interrupt routine */
static void ap3426_work_handler(struct work_struct *work) {
struct ap3426_data *data = container_of(work, struct ap3426_data, dwork.work);
struct i2c_client *client = data->client;
int status;
if ((status & 0x02) == 0x02) {
/* PS is interrupted */
ap3426_change_ps_threshold(client);
}
}
/* assume this is ISR */
static irqreturn_t ap3426_interrupt(int vec, void *info) {
struct i2c_client *client = (struct i2c_client *)info;
struct ap3426_data *data = i2c_get_clientdata(client);
ap3426_reschedule_work(data, 0);
return IRQ_HANDLED;
}
/*
* IOCTL support
*/
static int ap3426_enable_als_sensor(struct i2c_client *client, int val) {
struct ap3426_data *data = i2c_get_clientdata(client);
unsigned int config;
int rc;
pr_debug("%s: enable als sensor ( %d)\n", __func__, val);
if ((val != AP_DISABLE_ALS) && (val != AP_ENABLE_ALS_WITH_INT) &&
(val != AP_ENABLE_ALS_NO_INT)) {
pr_debug("%s: enable als sensor=%d\n", __func__, val);
return -1;
}
/* Read the system config register */
rc = regmap_read(data->regmap, AP3426_REG_CONFIG, &config);
if (rc) {
dev_err(&client->dev, "read %d failed.(%d)\n", AP3426_REG_CONFIG, rc);
goto out;
}
if ((val == AP_ENABLE_ALS_WITH_INT) || (val == AP_ENABLE_ALS_NO_INT)) {
u8 als_data[4];
int rc = 0;
if (regulator_enable(data->avdd)) {
dev_err(&client->dev, "dyna sensor avdd power supply enable failed\n");
goto out;
}
/* turn on light sensor */
data->enable_als_sensor = val;
/* lower threshold */
als_data[0] = 0x0;
als_data[1] = 0x0;
/* upper threshold */
als_data[2] = 0xff;
als_data[3] = 0xff;
rc = regmap_bulk_write(data->regmap, AP3426_REG_ALS_LOW_THRES_0, als_data,
4);
if (rc) {
dev_err(&client->dev, "write %d failed.(%d)\n",
AP3426_REG_ALS_LOW_THRES_0, rc);
goto out;
}
/* enable als_sensor */
rc = regmap_write(data->regmap, AP3426_REG_CONFIG, config | 0x01);
if (rc) {
dev_err(&client->dev, "write %d failed.(%d)\n", AP3426_REG_CONFIG, rc);
goto out;
}
/*
* If work is already scheduled then subsequent schedules will not
* change the scheduled time that's why we have to cancel it first.
*/
cancel_delayed_work(&data->als_dwork);
flush_delayed_work(&data->als_dwork);
queue_delayed_work(ap3426_workqueue, &data->als_dwork,
msecs_to_jiffies(data->als_poll_delay));
} else {
/* turn off light sensor
* what if the p sensor is active?
*/
data->enable_als_sensor = AP_DISABLE_ALS;
/* disable als_sensor */
regmap_write(data->regmap, AP3426_REG_CONFIG, (config & (~0x01)));
/*
* If work is already scheduled then subsequent schedules will not
* change the scheduled time that's why we have to cancel it first.
*/
cancel_delayed_work(&data->als_dwork);
flush_delayed_work(&data->als_dwork);
regulator_disable(data->avdd);
}
out:
return 0;
}
static int ap3426_set_als_poll_delay(struct i2c_client *client,
unsigned int val) {
struct ap3426_data *data = i2c_get_clientdata(client);
int atime_index = 0;
pr_debug("%s : %d\n", __func__, val);
if ((val != AP_ALS_POLL_SLOW) && (val != AP_ALS_POLL_MEDIUM) &&
(val != AP_ALS_POLL_FAST)) {
pr_debug("%s:invalid value=%d\n", __func__, val);
return -1;
}
if (val == AP_ALS_POLL_FAST) {
data->als_poll_delay = 50; /* 50ms */
atime_index = AP3426_ALS_RES_27MS;
} else if (val == AP_ALS_POLL_MEDIUM) {
data->als_poll_delay = 100; /* 100ms */
atime_index = AP3426_ALS_RES_51MS;
} else { /* AP_ALS_POLL_SLOW */
data->als_poll_delay = 1000; /* 1000ms */
atime_index = AP3426_ALS_RES_100MS;
}
// Todo: write atime to ap3426
/*
* If work is already scheduled then subsequent schedules will not
* change the scheduled time that's why we have to cancel it first.
*/
cancel_delayed_work(&data->als_dwork);
flush_delayed_work(&data->als_dwork);
queue_delayed_work(ap3426_workqueue, &data->als_dwork,
msecs_to_jiffies(data->als_poll_delay));
return 0;
}
static int ap3426_enable_ps_sensor(struct i2c_client *client, int val) {
struct ap3426_data *data = i2c_get_clientdata(client);
int rc;
unsigned int config;
pr_debug("enable ps senosr ( %d)\n", val);
if ((val != AP_DISABLE_PS) && (val != AP_ENABLE_PS)) {
pr_debug("%s:invalid value=%d\n", __func__, val);
return -1;
}
rc = regmap_read(data->regmap, AP3426_REG_CONFIG, &config);
if (rc) {
dev_err(&client->dev, "read %d failed.(%d)\n", AP3426_REG_CONFIG, rc);
goto out;
}
if (val == AP_ENABLE_PS) {
u8 buffer[6];
unsigned int tmp;
if (regulator_enable(data->avdd)) {
dev_err(&client->dev, "dyna sensor avdd power supply enable failed\n");
goto out;
}
/* turn on p sensor */
if (data->enable_ps_sensor == AP_DISABLE_PS) {
data->enable_ps_sensor = AP_ENABLE_PS;
regmap_write(data->regmap, AP3426_REG_CONFIG, config | 0x02);
regmap_bulk_read(data->regmap, AP3426_REG_ALS_DATA_LOW, buffer, 4);
tmp = buffer[2] | (buffer[3] << 8);
pr_debug("ps senosr data( 0x%x)\n", tmp);
}
} else {
regmap_write(data->regmap, AP3426_REG_CONFIG,
(config & (~0x02))); /* Power Off */
pr_debug("disable ps senosr ( 0x%x)\n", (config & (~0x02)));
data->enable_ps_sensor = AP_DISABLE_PS;
regulator_disable(data->avdd);
}
out:
return 0;
}
static int ap3426_ps_open(struct inode *inode, struct file *file) {
pr_debug("ap3426_ps_open\n");
return 0;
}
static int ap3426_ps_release(struct inode *inode, struct file *file) {
pr_debug("ap3426_ps_release\n");
return 0;
}
static long ap3426_ps_ioctl(struct file *file, unsigned int cmd,
unsigned long arg) {
struct ap3426_data *data;
struct i2c_client *client;
int enable;
u8 ps_data[4];
int ret = -1;
if (arg == 0) return -1;
if (ap3426_i2c_client == NULL) {
pr_debug("ap3426_ps_ioctl error: i2c driver not installed\n");
return -EFAULT;
}
client = ap3426_i2c_client;
data = i2c_get_clientdata(ap3426_i2c_client);
switch (cmd) {
case AP_IOCTL_PS_ENABLE:
if (copy_from_user(&enable, (void __user *)arg, sizeof(enable))) {
pr_debug("ap3426_ps_ioctl: copy_from_user failed\n");
return -EFAULT;
}
ret = ap3426_enable_ps_sensor(client, enable);
if (ret < 0) return ret;
break;
case AP_IOCTL_PS_GET_ENABLE:
if (copy_to_user((void __user *)arg, &data->enable_ps_sensor,
sizeof(data->enable_ps_sensor))) {
pr_debug("ap3426_ps_ioctl: copy_to_user failed\n");
return -EFAULT;
}
break;
case AP_IOCTL_PS_GET_PDATA:
regmap_bulk_read(data->regmap, AP3426_REG_PS_DATA_LOW, ps_data, 2);
data->ps_data = ps_data[0] | (ps_data[1] << 8);
if (copy_to_user((void __user *)arg, &data->ps_data,
sizeof(data->ps_data))) {
pr_debug("ap3426_ps_ioctl: copy_to_user failed\n");
return -EFAULT;
}
break;
default:
break;
}
return 0;
}
static int ap3426_als_open(struct inode *inode, struct file *file) {
pr_debug("ap3426_als_open\n");
return 0;
}
static int ap3426_als_release(struct inode *inode, struct file *file) {
pr_debug("ap3426_als_release\n");
return 0;
}
static long ap3426_als_ioctl(struct file *file, unsigned int cmd,
unsigned long arg) {
struct ap3426_data *data;
struct i2c_client *client;
int enable;
int ret = -1;
unsigned int delay;
if (arg == 0) return -1;
if (ap3426_i2c_client == NULL) {
pr_debug("ap3426_als_ioctl error: i2c driver not installed\n");
return -EFAULT;
}
client = ap3426_i2c_client;
data = i2c_get_clientdata(ap3426_i2c_client);
switch (cmd) {
case AP_IOCTL_ALS_ENABLE:
if (copy_from_user(&enable, (void __user *)arg, sizeof(enable))) {
pr_debug("ap3426_als_ioctl: copy_from_user failed\n");
return -EFAULT;
}
ret = ap3426_enable_als_sensor(client, enable);
if (ret < 0) return ret;
break;
case AP_IOCTL_ALS_POLL_DELAY:
if (data->enable_als_sensor == AP_ENABLE_ALS_NO_INT) {
if (copy_from_user(&delay, (void __user *)arg, sizeof(delay))) {
pr_debug("ap3426_als_ioctl: copy_to_user failed\n");
return -EFAULT;
}
ret = ap3426_set_als_poll_delay(client, delay);
if (ret < 0) return ret;
} else {
pr_debug("ap3426_als_ioctl: als is not in polling mode!\n");
return -EFAULT;
}
break;
case AP_IOCTL_ALS_GET_ENABLE:
if (copy_to_user((void __user *)arg, &data->enable_als_sensor,
sizeof(data->enable_als_sensor))) {
pr_debug("ap3426_als_ioctl: copy_to_user failed\n");
return -EFAULT;
}
break;
case AP_IOCTL_ALS_GET_CH0DATA:
regmap_read(data->regmap, AP3426_REG_ALS_DATA_LOW, &data->als_data);
if (copy_to_user((void __user *)arg, &data->als_data,
sizeof(data->als_data))) {
pr_debug("ap3426_ps_ioctl: copy_to_user failed\n");
return -EFAULT;
}
break;
case AP_IOCTL_ALS_GET_CH1DATA:
regmap_read(data->regmap, AP3426_REG_ALS_DATA_HIGH, &data->als_data);
if (copy_to_user((void __user *)arg, &data->als_data,
sizeof(data->als_data))) {
pr_debug("ap3426_ps_ioctl: copy_to_user failed\n");
return -EFAULT;
}
break;
default:
break;
}
return 0;
}
/*
* SysFS support
*/
static ssize_t ap3426_show_ch0data(struct device *dev,
struct device_attribute *attr, char *buf) {
struct input_dev *input = to_input_dev(dev);
struct ap3426_data *data = input_get_drvdata(input);
int ch0data;
regmap_read(data->regmap, AP3426_REG_ALS_DATA_LOW, &ch0data);
return sprintf(buf, "%d\n", ch0data);
}
static DEVICE_ATTR(ch0data, S_IRUGO, ap3426_show_ch0data, NULL);
static ssize_t ap3426_show_ch1data(struct device *dev,
struct device_attribute *attr, char *buf) {
struct input_dev *input = to_input_dev(dev);
struct ap3426_data *data = input_get_drvdata(input);
int ch1data;
regmap_read(data->regmap, AP3426_REG_ALS_DATA_HIGH, &ch1data);
return sprintf(buf, "%d\n", ch1data);
}
static DEVICE_ATTR(ch1data, S_IRUGO, ap3426_show_ch1data, NULL);
static ssize_t ap3426_show_pdata(struct device *dev,
struct device_attribute *attr, char *buf) {
struct input_dev *input = to_input_dev(dev);
struct ap3426_data *data = input_get_drvdata(input);
int pdata;
u8 ps_data[4];
regmap_bulk_read(data->regmap, AP3426_REG_PS_DATA_LOW, ps_data, 2);
pdata = ps_data[0] | (ps_data[1] << 8);
return sprintf(buf, "%d\n", pdata);
}
static DEVICE_ATTR(pdata, S_IRUGO, ap3426_show_pdata, NULL);
static ssize_t ap3426_show_proximity_enable(struct device *dev,
struct device_attribute *attr,
char *buf) {
struct input_dev *input = to_input_dev(dev);
struct ap3426_data *data = input_get_drvdata(input);
return sprintf(buf, "%d\n", data->enable_ps_sensor);
}
static ssize_t ap3426_store_proximity_enable(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count) {
struct input_dev *input = to_input_dev(dev);
struct ap3426_data *data = input_get_drvdata(input);
struct i2c_client *client = data->client;
unsigned long val;
int success = kstrtoul(buf, 10, &val);
if (success == 0) {
pr_debug("%s: enable ps senosr ( %ld)\n", __func__, val);
if ((val != AP_DISABLE_PS) && (val != AP_ENABLE_PS)) {
pr_debug("**%s:store invalid value=%ld\n", __func__, val);
return count;
}
ap3426_enable_ps_sensor(client, val);
}
return count;
}
static DEVICE_ATTR(active, S_IRUGO | S_IWUSR | S_IWGRP,
ap3426_show_proximity_enable, ap3426_store_proximity_enable);
static ssize_t ap3426_show_light_enable(struct device *dev,
struct device_attribute *attr,
char *buf) {
struct input_dev *input = to_input_dev(dev);
struct ap3426_data *data = input_get_drvdata(input);
return sprintf(buf, "%d\n", data->enable_als_sensor);
}
static ssize_t ap3426_store_light_enable(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count) {
struct input_dev *input = to_input_dev(dev);
struct ap3426_data *data = input_get_drvdata(input);
struct i2c_client *client = data->client;
unsigned long val;
int success = kstrtoul(buf, 10, &val);
if (success == 0) {
pr_debug("%s: enable als sensor ( %ld)\n", __func__, val);
if ((val != AP_DISABLE_ALS) && (val != AP_ENABLE_ALS_WITH_INT) &&
(val != AP_ENABLE_ALS_NO_INT)) {
pr_debug("**%s: store invalid valeu=%ld\n", __func__, val);
return count;
}
ap3426_enable_als_sensor(client, val);
}
return count;
}
static DEVICE_ATTR2(active, S_IRUGO | S_IWUSR | S_IWGRP,
ap3426_show_light_enable, ap3426_store_light_enable);
static struct attribute *ap3426_als_attributes[] = {
&dev_attr_ch0data.attr, &dev_attr_ch1data.attr, &dev_attr2_active.attr,
NULL};
static const struct attribute_group ap3426_als_attr_group = {
.attrs = ap3426_als_attributes,
};
static struct attribute *ap3426_ps_attributes[] = {&dev_attr_pdata.attr,
&dev_attr_active.attr, NULL};
static const struct attribute_group ap3426_ps_attr_group = {
.attrs = ap3426_ps_attributes,
};
static const struct file_operations ap3426_ps_fops = {
.owner = THIS_MODULE,
.open = ap3426_ps_open,
.release = ap3426_ps_release,
.unlocked_ioctl = ap3426_ps_ioctl,
};
static struct miscdevice ap3426_ps_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ap3426_ps_dev",
.fops = &ap3426_ps_fops,
};
static const struct file_operations ap3426_als_fops = {
.owner = THIS_MODULE,
.open = ap3426_als_open,
.release = ap3426_als_release,
.unlocked_ioctl = ap3426_als_ioctl,
};
static struct miscdevice ap3426_als_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ap3426_als_dev",
.fops = &ap3426_als_fops,
};
/*
* Initialization function
*/
static int ap3426_init_client(struct i2c_client *client) {
struct ap3426_data *di = i2c_get_clientdata(client);
int rc;
/* Enable ps interrupt and auto clear interrupt */
rc = regmap_write(di->regmap, AP3426_REG_INT_CTL, 0x80);
if (rc) {
dev_err(&client->dev, "write %d register failed\n", AP3426_REG_INT_CTL);
return rc;
}
/* Set als gain */
rc = regmap_write(di->regmap, AP3426_REG_ALS_GAIN, di->als_gain << 4);
if (rc) {
dev_err(&client->dev, "write %d register failed\n", AP3426_REG_ALS_GAIN);
return rc;
}
/* Set als persistense */
rc = regmap_write(di->regmap, AP3426_REG_ALS_PERSIST, di->als_persist);
if (rc) {
dev_err(&client->dev, "write %d register failed\n", AP3426_REG_ALS_PERSIST);
return rc;
}
/* Set ps interrupt form */
rc = regmap_write(di->regmap, AP3426_REG_PS_INT_FORM, 0);
if (rc) {
dev_err(&client->dev, "write %d register failed\n", AP3426_REG_PS_INT_FORM);
return rc;
}
/* Set ps gain */
rc = regmap_write(di->regmap, AP3426_REG_PS_GAIN, di->ps_gain << 2);
if (rc) {
dev_err(&client->dev, "write %d register failed\n", AP3426_REG_PS_GAIN);
return rc;
}
/* Set ps persist */
rc = regmap_write(di->regmap, AP3426_REG_PS_PERSIST, di->ps_persist);
if (rc) {
dev_err(&client->dev, "write %d register failed\n", AP3426_REG_PS_PERSIST);
return rc;
}
/* Set PS LED driver strength */
rc = regmap_write(di->regmap, AP3426_REG_PS_LED_DRIVER, di->ps_led_driver);
if (rc) {
dev_err(&client->dev, "write %d register failed\n",
AP3426_REG_PS_LED_DRIVER);
return rc;
}
/* Set waiting time */
rc = regmap_write(di->regmap, AP3426_REG_WAIT_TIME, di->wait_time);
if (rc) {
dev_err(&client->dev, "write %d register failed\n", AP3426_REG_WAIT_TIME);
return rc;
}
/* Set PS mean time */
rc = regmap_write(di->regmap, AP3426_REG_PS_MEAN_TIME, di->ps_mean_time);
if (rc) {
dev_err(&client->dev, "write %d register failed\n",
AP3426_REG_PS_MEAN_TIME);
return rc;
}
/* Set PS integrated time */
rc = regmap_write(di->regmap, AP3426_REG_PS_INT_TIME, di->ps_integrated_time);
if (rc) {
dev_err(&client->dev, "write %d register failed\n", AP3426_REG_PS_INT_TIME);
return rc;
}
dev_dbg(&client->dev, "ap3426 initialize sucessful\n");
return 0;
}
#ifdef CONFIG_OF
static int ap3426_probe_dt(struct i2c_client *client) {
struct ap3426_platform_data *platform_data;
struct device_node *np = client->dev.of_node;
platform_data = kzalloc(sizeof(*platform_data), GFP_KERNEL);
if (platform_data == NULL) {
dev_err(&client->dev, "Alloc GFP_KERNEL memory failed.");
return -ENOMEM;
}
client->dev.platform_data = platform_data;
platform_data->irq = of_get_named_gpio(np, "irq-gpios", 0);
if (platform_data->irq < 0) {
dev_err(&client->dev, "of_get_named_gpio irq faild\n");
return -EINVAL;
}
return 0;
}
static struct of_device_id inv_match_table[] = {{
.compatible = "dyna,ap3426",
},
{}};
#endif
/*
* I2C init/probing/exit functions
*/
static struct i2c_driver ap3426_driver;
static int ap3426_probe(struct i2c_client *client,
const struct i2c_device_id *id) {
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
struct ap3426_data *data;
struct ap3426_platform_data *pdata;
struct regulator *avdd;
int err = 0;
#ifdef CONFIG_OF
err = ap3426_probe_dt(client);
if (err == -ENOMEM) {
dev_err(&client->dev, "%s: Failed to alloc mem for ap3426_platform_data\n",
__func__);
return err;
} else if (err == -EINVAL) {
kfree(client->dev.platform_data);
dev_err(&client->dev, "%s: Probe device tree data failed\n", __func__);
return err;
}
pdata = client->dev.platform_data;
#else
pdata = client->dev.platform_data;
if (!pdata) {
dev_err(&client->dev, "%s: No platform data found\n", __func__);
return -EINVAL;
}
#endif
avdd = regulator_get(&client->dev, "avdd");
if (IS_ERR(avdd)) {
dev_err(&client->dev, "sensor avdd power supply get failed\n");
goto out;
}
regulator_set_voltage(avdd, 3100000, 3100000);
if (regulator_enable(avdd)) {
dev_err(&client->dev, "dyna sensors regulator enable failed\n");
goto out;
}
/* add delay to make sure ldo enabled */
usleep_range(2000, 2200);
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) {
err = -EIO;
goto exit;
}
if (i2c_smbus_read_byte(client) < 0) {
dev_err(&client->dev, "i2c_smbus_read_byte error!\n");
err = -EIO;
goto exit;
}
data = kzalloc(sizeof(struct ap3426_data), GFP_KERNEL);
if (!data) {
err = -ENOMEM;
goto exit;
}
data->client = client;
ap3426_i2c_client = client;
data->avdd = avdd;
i2c_set_clientdata(client, data);
data->regmap = devm_regmap_init_i2c(client, &ap3426_regmap_config);
if (IS_ERR(data->regmap)) {
dev_err(&client->dev, "init regmap failed.(%ld)\n", PTR_ERR(data->regmap));
goto exit_kfree;
}
data->enable = 0; /* default mode is standard */
data->ps_threshold = AP3426_PS_DETECTION_THRESHOLD;
data->ps_hysteresis_threshold = AP3426_PS_HSYTERESIS_THRESHOLD;
data->ps_detection = 0; /* default to no detection */
data->enable_als_sensor = 0; /* default to 0 */
data->enable_ps_sensor = 0; /* default to 0 */
data->als_poll_delay = 100; /* default to 100ms */
data->als_prev_lux = 0;
data->suspended = 0;
data->enable_suspended_value = 0; /* suspend_resume usage */
//######################################################
data->als_gain = 0;
data->als_persist = 1;
data->ps_gain = 1;
data->ps_persist = 1;
data->ps_led_driver = 3;
data->wait_time = 0;
data->ps_mean_time = 0;
data->ps_integrated_time = 0;
data->als_cal = 94;
//######################################################
mutex_init(&data->update_lock);
INIT_DELAYED_WORK(&data->dwork, ap3426_work_handler);
INIT_DELAYED_WORK(&data->als_dwork, ap3426_als_polling_work_handler);
if (request_irq((client->irq), ap3426_interrupt,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, AP3426_DRV_NAME,
(void *)client)) {
pr_debug("%s Could not allocate irq resource !\n", __func__);
goto exit_kfree;
}
pr_info("%s interrupt is hooked\n", __func__);
/* Initialize the AP3426 chip */
err = ap3426_init_client(client);
if (err) goto exit_kfree;
/* Register to Input Device */
data->input_dev_als = input_allocate_device();
if (!data->input_dev_als) {
err = -ENOMEM;
pr_debug("Failed to allocate input device als\n");
goto exit_free_irq;
}
data->input_dev_ps = input_allocate_device();
if (!data->input_dev_ps) {
err = -ENOMEM;
pr_debug("Failed to allocate input device ps\n");
goto exit_free_dev_als;
}
data->input_dev_als->name = "AP3426_light_sensor";
data->input_dev_als->id.bustype = BUS_I2C;
input_set_capability(data->input_dev_als, EV_ABS, ABS_MISC);
__set_bit(EV_ABS, data->input_dev_als->evbit);
__set_bit(ABS_PRESSURE, data->input_dev_als->absbit);
input_set_abs_params(data->input_dev_als, ABS_LIGHT, 0, 30000, 0, 0);
input_set_drvdata(data->input_dev_als, data);
data->input_dev_ps->name = "AP3426_proximity_sensor";
data->input_dev_ps->id.bustype = BUS_I2C;
input_set_capability(data->input_dev_ps, EV_ABS, ABS_MISC);
__set_bit(EV_ABS, data->input_dev_ps->evbit);
__set_bit(ABS_DISTANCE, data->input_dev_ps->absbit);
input_set_abs_params(data->input_dev_ps, ABS_DISTANCE, 0, 10, 0, 0);
input_set_drvdata(data->input_dev_ps, data);
err = input_register_device(data->input_dev_als);
if (err) {
err = -ENOMEM;
pr_debug("Unable to register input device als: %s\n",
data->input_dev_als->name);
goto exit_free_dev_ps;
}
err = input_register_device(data->input_dev_ps);
if (err) {
err = -ENOMEM;
pr_debug("Unable to register input device ps: %s\n",
data->input_dev_ps->name);
goto exit_unregister_dev_als;
}
/* Register sysfs hooks */
err = sysfs_create_group(&data->input_dev_als->dev.kobj,
&ap3426_als_attr_group);
if (err) goto exit_unregister_dev_als;
err =
sysfs_create_group(&data->input_dev_ps->dev.kobj, &ap3426_ps_attr_group);
if (err) goto exit_unregister_dev_ps;
/* Register for sensor ioctl */
err = misc_register(&ap3426_ps_device);
if (err) {
pr_debug("Unalbe to register ps ioctl: %d", err);
goto exit_remove_sysfs_group;
}
err = misc_register(&ap3426_als_device);
if (err) {
pr_debug("Unalbe to register als ioctl: %d", err);
goto exit_unregister_ps_ioctl;
}
pr_debug("%s support ver. %s enabled\n", __func__, DRIVER_VERSION);
regulator_disable(avdd);
return 0;
exit_unregister_ps_ioctl:
misc_deregister(&ap3426_ps_device);
exit_remove_sysfs_group:
sysfs_remove_group(&data->input_dev_als->dev.kobj, &ap3426_als_attr_group);
sysfs_remove_group(&data->input_dev_ps->dev.kobj, &ap3426_ps_attr_group);
exit_unregister_dev_ps:
input_unregister_device(data->input_dev_ps);
exit_unregister_dev_als:
input_unregister_device(data->input_dev_als);
exit_free_dev_ps:
exit_free_dev_als:
exit_free_irq:
free_irq((client->irq), client);
exit_kfree:
kfree(data);
exit:
regulator_disable(avdd);
out:
regulator_put(avdd);
return err;
}
static int ap3426_remove(struct i2c_client *client) {
struct ap3426_data *data = i2c_get_clientdata(client);
/* Power down the device */
// Todo, disable ap3426
misc_deregister(&ap3426_als_device);
misc_deregister(&ap3426_ps_device);
sysfs_remove_group(&data->input_dev_als->dev.kobj, &ap3426_als_attr_group);
sysfs_remove_group(&data->input_dev_ps->dev.kobj, &ap3426_ps_attr_group);
input_unregister_device(data->input_dev_ps);
input_unregister_device(data->input_dev_als);
free_irq((client->irq), client);
kfree(data);
return 0;
}
#ifdef CONFIG_PM
static int ap3426_suspend(struct device *dev) {
struct i2c_client *client = to_i2c_client(dev);
struct ap3426_data *data = i2c_get_clientdata(client);
pr_debug("ap3426_suspend\n");
/* Do nothing as p-sensor is in active */
if (!data->enable) return 0;
data->suspended = 1;
data->enable_suspended_value = data->enable;
// Todo disable ap3426
cancel_delayed_work(&data->als_dwork);
flush_delayed_work(&data->als_dwork);
cancel_delayed_work(&data->dwork);
flush_delayed_work(&data->dwork);
flush_workqueue(ap3426_workqueue);
disable_irq(client->irq);
if (NULL != ap3426_workqueue) {
destroy_workqueue(ap3426_workqueue);
pr_debug(KERN_INFO "%s, Destroy workqueue\n", __func__);
ap3426_workqueue = NULL;
}
regulator_disable(data->avdd);
return 0;
}
static int ap3426_resume(struct device *dev) {
struct i2c_client *client = to_i2c_client(dev);
struct ap3426_data *data = i2c_get_clientdata(client);
/* Do nothing as it was not suspended */
pr_debug("ap3426_resume (enable=%d)\n", data->enable_suspended_value);
if (!data->enable_suspended_value) return 0;
if (ap3426_workqueue == NULL) {
ap3426_workqueue = create_workqueue("proximity_als");
if (NULL == ap3426_workqueue) return -ENOMEM;
}
if (!data->suspended) return 0; /* if previously not suspended, leave it */
if (regulator_enable(data->avdd)) {
dev_err(&client->dev, "dyna sensor avdd power supply enable failed\n");
goto out;
}
enable_irq(client->irq);
// Todo: resume config to data->enable_suspended_value
data->suspended = 0;
// Todo: clear pending interrupt
out:
return 0;
}
#else
#define ap3426_suspend NULL
#define ap3426_resume NULL
#endif /* CONFIG_PM */
static const struct i2c_device_id ap3426_id[] = {{"ap3426", 0}, {}};
MODULE_DEVICE_TABLE(i2c, ap3426_id);
static SIMPLE_DEV_PM_OPS(ap3426_pm_ops, ap3426_suspend, ap3426_resume);
static struct i2c_driver ap3426_driver = {
.driver =
{
.name = AP3426_DRV_NAME,
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = of_match_ptr(inv_match_table),
#endif
.pm = &ap3426_pm_ops,
},
.probe = ap3426_probe,
.remove = ap3426_remove,
.id_table = ap3426_id,
};
static int __init ap3426_init(void) {
ap3426_workqueue = create_workqueue("proximity_als");
if (!ap3426_workqueue) return -ENOMEM;
return i2c_add_driver(&ap3426_driver);
}
static void __exit ap3426_exit(void) {
if (ap3426_workqueue) destroy_workqueue(ap3426_workqueue);
ap3426_workqueue = NULL;
i2c_del_driver(&ap3426_driver);
}
MODULE_AUTHOR("Jian Zhou");
MODULE_DESCRIPTION("AP3426 ambient light + proximity sensor driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);
module_init(ap3426_init);
module_exit(ap3426_exit);