aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYu Jun <yujun@marvell.com>2016-02-01 00:34:05 -0800
committerMohammed Habibulla <moch@google.com>2016-02-25 22:36:05 +0000
commit804a28a2520307ebcad92e18241b963d07427b41 (patch)
treed7ed00d1fa19d47550c29ab5afa145c3e0a550ad
parent1dbeb7dae9912deb3b2690f95114e7131d2cc001 (diff)
downloadpxa-v3.14-804a28a2520307ebcad92e18241b963d07427b41.tar.gz
VENDOR: Marvell: To add the ap3426 sensor kernel driver
BUG=26894696 Change-Id: I39073164739ade0e0e83979f0baa8d9028b4c940
-rwxr-xr-xarch/arm/configs/abox_edge_defconfig1
-rw-r--r--arch/arm64/boot/dts/pxa1908-board-common.dtsi12
-rw-r--r--arch/arm64/boot/dts/pxa1908-dkb.dtsi3
-rw-r--r--drivers/hwmon/Kconfig6
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/ap3426.c1248
6 files changed, 1270 insertions, 1 deletions
diff --git a/arch/arm/configs/abox_edge_defconfig b/arch/arm/configs/abox_edge_defconfig
index 06f342b26df..403b4cf2b9d 100755
--- a/arch/arm/configs/abox_edge_defconfig
+++ b/arch/arm/configs/abox_edge_defconfig
@@ -1889,6 +1889,7 @@ CONFIG_SENSORS_MMX3524X_MXC400X=y
# CONFIG_SENSORS_LPS331AP is not set
# CONFIG_SENSORS_BMP18X is not set
CONFIG_SENSORS_APDS9930=y
+CONFIG_SENSORS_AP3426=y
# CONFIG_SENSORS_APDS990X_MRVL is not set
# CONFIG_SENSORS_GRIP is not set
# CONFIG_SENSORS_STK3X1X is not set
diff --git a/arch/arm64/boot/dts/pxa1908-board-common.dtsi b/arch/arm64/boot/dts/pxa1908-board-common.dtsi
index 588d5d7be52..e6a444ecdea 100644
--- a/arch/arm64/boot/dts/pxa1908-board-common.dtsi
+++ b/arch/arm64/boot/dts/pxa1908-board-common.dtsi
@@ -1023,7 +1023,7 @@
reg = <0x15>;
acdd-supply = <&ldo4>;
status = "disabled";
- };
+ };
sensor6: mmc3524x@30 {
compatible = "memsic,mmc3524x";
@@ -1031,6 +1031,16 @@
acdd-supply = <&ldo4>;
status = "disabled";
};
+
+ sensor7: ap3426@1E {
+ compatible = "dyna,ap3426";
+ reg = <0x1E>;
+ interrupt-parent = <&gpio>;
+ interrupts = <20 0x1>;
+ irq-gpios = <&gpio 20 0>;
+ avdd-supply = <&ldo4>;
+ status = "disabled";
+ };
};
/* SSPA port 0 */
diff --git a/arch/arm64/boot/dts/pxa1908-dkb.dtsi b/arch/arm64/boot/dts/pxa1908-dkb.dtsi
index 1f80fae5768..e5bb6a25566 100644
--- a/arch/arm64/boot/dts/pxa1908-dkb.dtsi
+++ b/arch/arm64/boot/dts/pxa1908-dkb.dtsi
@@ -98,6 +98,9 @@
sensor6: mmc3524x@30 {
status = "disable";
};
+ sensor7: ap3426@1E {
+ status = "okay";
+ };
};
};
};
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 369ed93b305..37bcbccf529 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1607,6 +1607,12 @@ config SENSORS_APDS990X_MRVL
This driver provides support the APDS990x deivce
sensor.Read pressures and temperatures output.
+config SENSORS_AP3426
+ tristate "AP3426 light and proximity sensor"
+ depends on I2C && INPUT
+ help
+ This driver provides support for the Dyna Image AP3426 device.
+
config SENSORS_GRIP
bool "grip(sar) sensor support"
default n
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 16c07b5c51e..8a746d5f508 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -147,6 +147,7 @@ obj-$(CONFIG_SENSORS_LPS331AP) += lps331ap.o
obj-$(CONFIG_SENSORS_BMP18X) += bmp18x-core.o bmp18x-i2c.o
obj-$(CONFIG_SENSORS_APDS9930) += apds9930.o
obj-$(CONFIG_SENSORS_APDS990X_MRVL) += apds990x.o
+obj-$(CONFIG_SENSORS_AP3426) += ap3426.o
obj-$(CONFIG_SENSORS_GRIP) += grip_sensor.o
obj-$(CONFIG_SENSORS_STK3X1X) += stk3x1x.o
obj-$(CONFIG_SENSORS_LIS3DH) += lis3dh.o
diff --git a/drivers/hwmon/ap3426.c b/drivers/hwmon/ap3426.c
new file mode 100644
index 00000000000..5bc32764341
--- /dev/null
+++ b/drivers/hwmon/ap3426.c
@@ -0,0 +1,1248 @@
+/*
+ * 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);