summaryrefslogtreecommitdiff
path: root/drivers/input/misc/al3006_pls.c
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/input/misc/al3006_pls.c
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'drivers/input/misc/al3006_pls.c')
-rw-r--r--drivers/input/misc/al3006_pls.c838
1 files changed, 838 insertions, 0 deletions
diff --git a/drivers/input/misc/al3006_pls.c b/drivers/input/misc/al3006_pls.c
new file mode 100644
index 00000000..8d72a055
--- /dev/null
+++ b/drivers/input/misc/al3006_pls.c
@@ -0,0 +1,838 @@
+/*
+ * File: al3006_pls.c
+ * Based on:
+ * Author: Yunlong Wang <Yunlong.Wang @spreadtrum.com>
+ *
+ *
+ * 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/delay.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/input.h>
+#include <linux/gpio.h>
+#ifdef CONFIG_HAS_EARLYSUSPEND
+#include <linux/earlysuspend.h>
+#endif
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+#include <linux/i2c/al3006_pls.h>
+#include <linux/miscdevice.h>
+
+static struct i2c_client *this_client;
+static atomic_t p_flag;
+static atomic_t l_flag;
+static int al3006_pls_opened=0;
+
+/*0: sleep out; 1: sleep in*/
+static unsigned char suspend_flag=0;
+
+
+static ssize_t al3006_pls_show_suspend(struct device* cd,struct device_attribute *attr, char* buf);
+static ssize_t al3006_pls_store_suspend(struct device* cd, struct device_attribute *attr,const char* buf, size_t len);
+static ssize_t al3006_pls_show_version(struct device* cd,struct device_attribute *attr, char* buf);
+#ifdef CONFIG_HAS_EARLYSUSPEND
+static void al3006_pls_early_suspend(struct early_suspend *handler);
+static void al3006_pls_early_resume(struct early_suspend *handler);
+#endif
+static int al3006_pls_write_data(unsigned char addr, unsigned char data);
+static int al3006_pls_read_data(unsigned char addr, unsigned char *data);
+static int al3006_pls_enable(SENSOR_TYPE type);
+static int al3006_pls_disable(SENSOR_TYPE type);
+static void al3006_pls_report_init(void);
+
+static int al3006_pls_adc2lux[64]={
+ 1, 1, 1, 2, 2, 2, 3, 4,
+ 4, 5, 6, 7, 9, 11, 13, 16,
+ 19, 22, 27, 32, 39, 46, 56, 67,
+ 80, 96, 116, 139, 167, 200, 240, 289,
+ 346, 416, 499, 599, 720, 864, 1037, 1245,
+ 1495, 1795, 2154, 2586, 3105, 3728, 4475, 5372,
+ 6449, 7743, 9295, 11159, 13396, 16082, 19307, 23178,
+ 27862, 33405, 40103, 48144, 57797, 69386, 83298, 100000
+};
+
+
+static DEVICE_ATTR(suspend, S_IRUGO | S_IWUSR, al3006_pls_show_suspend, al3006_pls_store_suspend);
+static DEVICE_ATTR(version, S_IRUGO | S_IWUSR, al3006_pls_show_version, NULL);
+
+static ssize_t al3006_pls_show_suspend(struct device* cd,
+ struct device_attribute *attr, char* buf)
+{
+ ssize_t ret = 0;
+
+ if(suspend_flag==1)
+ sprintf(buf, "AL3006 Resume\n");
+ else
+ sprintf(buf, "AL3006 Suspend\n");
+
+ ret = strlen(buf) + 1;
+
+ return ret;
+}
+
+static ssize_t al3006_pls_store_suspend(struct device* cd, struct device_attribute *attr,
+ const char* buf, size_t len)
+{
+ unsigned long on_off = simple_strtoul(buf, NULL, 10);
+ suspend_flag = on_off;
+
+ if(on_off==1)
+ {
+ printk(KERN_INFO "AL3006 Entry Resume\n");
+ al3006_pls_enable(AL3006_PLS_BOTH);
+ }
+ else
+ {
+ printk(KERN_INFO "AL3006 Entry Suspend\n");
+ al3006_pls_disable(AL3006_PLS_BOTH);
+ }
+
+ return len;
+}
+
+static ssize_t al3006_pls_show_version(struct device* cd,
+ struct device_attribute *attr, char* buf)
+{
+ ssize_t ret = 0;
+
+ sprintf(buf, "AL3006");
+ ret = strlen(buf) + 1;
+
+ return ret;
+}
+
+static int al3006_pls_create_sysfs(struct i2c_client *client)
+{
+ int err;
+ struct device *dev = &(client->dev);
+
+ PLS_DBG("%s", __func__);
+
+ err = device_create_file(dev, &dev_attr_suspend);
+ err = device_create_file(dev, &dev_attr_version);
+
+ return err;
+}
+
+/*******************************************************************************
+* Function : al3006_pls_enable
+* Description : type: proximity / light
+*******************************************************************************/
+static int al3006_pls_enable(SENSOR_TYPE type)
+{
+ unsigned char config;
+
+ al3006_pls_read_data(AL3006_PLS_REG_CONFIG, &config);
+
+ PLS_DBG("%s: type=%d; config=0x%x", __func__, type, config);
+
+ switch(type) {
+ case AL3006_PLS_ALPS:
+ if(config == AL3006_PLS_PXY_ACTIVE)
+ type = AL3006_PLS_BOTH;
+ break;
+ case AL3006_PLS_PXY:
+ if(config == AL3006_PLS_ALPS_ACTIVE)
+ type = AL3006_PLS_BOTH;
+ break;
+ case AL3006_PLS_BOTH:
+ break;
+ }
+
+ PLS_DBG("%s: after type=%d\n",__func__, type);
+
+ switch(type) {
+ case AL3006_PLS_ALPS:
+ al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_ALPS_ACTIVE);
+ break;
+ case AL3006_PLS_PXY:
+ al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_PXY_ACTIVE);
+ al3006_pls_report_init();
+ break;
+ case AL3006_PLS_BOTH:
+ al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_BOTH_ACTIVE);
+ al3006_pls_report_init();
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*******************************************************************************
+* Function : al3006_pls_disable
+* Description : type: proximity / light
+*******************************************************************************/
+static int al3006_pls_disable(SENSOR_TYPE type)
+{
+ int pxy_flag, apls_flag;
+
+ PLS_DBG("%s: type=%d", __func__, type);
+
+ switch(type) {
+ case AL3006_PLS_ALPS:
+ pxy_flag = atomic_read(&p_flag);
+ /*check proximiy sensor status*/
+ if(pxy_flag == 0) {
+ PLS_DBG("%s: Disable both sensor for pxy_flag=%d", __func__, pxy_flag);
+ al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_BOTH_DEACTIVE);
+ } else {
+ PLS_DBG("%s: Only Proximity Sensor alive", __func__);
+ al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_PXY_ACTIVE);
+ }
+
+ break;
+ case AL3006_PLS_PXY:
+ apls_flag = atomic_read(&l_flag);
+ /*check light sensor status*/
+ if(apls_flag == 0) {
+ PLS_DBG("%s: Disable both sensor for apls_flag=%d", __func__, apls_flag);
+ al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_BOTH_DEACTIVE);
+ } else {
+ PLS_DBG("%s: Only Light Sensor alive", __func__);
+ al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_ALPS_ACTIVE);
+ }
+ break;
+ case AL3006_PLS_BOTH:
+ PLS_DBG("%s: Disable both sensors", __func__);
+ al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_BOTH_DEACTIVE);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int al3006_pls_open(struct inode *inode, struct file *file)
+{
+ PLS_DBG("%s\n", __func__);
+ if (al3006_pls_opened)
+ return -EBUSY;
+ al3006_pls_opened = 1;
+ return 0;
+}
+
+static int al3006_pls_release(struct inode *inode, struct file *file)
+{
+ PLS_DBG("%s", __func__);
+ al3006_pls_opened = 0;
+ return al3006_pls_disable(AL3006_PLS_BOTH);
+}
+
+
+/*******************************************************************************
+* Function : al3006_pls_ioctl
+* Description : ioctl interface to control the sensor
+*******************************************************************************/
+static long al3006_pls_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int flag;
+ unsigned char data;
+
+ PLS_DBG("%s: cmd %d", __func__, _IOC_NR(cmd));
+
+ /*get ioctl parameter*/
+ switch (cmd) {
+ case LTR_IOCTL_SET_PFLAG:
+ case LTR_IOCTL_SET_LFLAG:
+ if (copy_from_user(&flag, argp, sizeof(flag))) {
+ return -EFAULT;
+ }
+ if (flag < 0 || flag > 1) {
+ return -EINVAL;
+ }
+ PLS_DBG("%s: set flag=%d", __func__, flag);
+ break;
+ default:
+ break;
+ }
+
+ /*handle ioctl*/
+ switch (cmd) {
+ case LTR_IOCTL_GET_PFLAG:
+ flag = atomic_read(&p_flag);
+ break;
+
+ case LTR_IOCTL_GET_LFLAG:
+ flag = atomic_read(&l_flag);
+ break;
+
+ case LTR_IOCTL_GET_DATA:
+ break;
+
+ case LTR_IOCTL_SET_PFLAG:
+ atomic_set(&p_flag, flag);
+ if(flag==1){
+ al3006_pls_enable(AL3006_PLS_PXY);
+ }
+ else if(flag==0) {
+ al3006_pls_disable(AL3006_PLS_PXY);
+ }
+ break;
+
+ case LTR_IOCTL_SET_LFLAG:
+ atomic_set(&l_flag, flag);
+ if(flag==1){
+ al3006_pls_enable(AL3006_PLS_ALPS);
+ }
+ else if(flag==0) {
+ al3006_pls_disable(AL3006_PLS_ALPS);
+ }
+ break;
+
+ default:
+ pr_err("%s: invalid cmd %d\n", __func__, _IOC_NR(cmd));
+ return -EINVAL;
+ }
+
+ /*report ioctl*/
+ switch (cmd) {
+ case LTR_IOCTL_GET_PFLAG:
+ case LTR_IOCTL_GET_LFLAG:
+ if (copy_to_user(argp, &flag, sizeof(flag))) {
+ return -EFAULT;
+ }
+ PLS_DBG("%s: get flag=%d", __func__, flag);
+ break;
+
+ case LTR_IOCTL_GET_DATA:
+ al3006_pls_read_data(AL3006_PLS_REG_DATA,&data);
+ if (copy_to_user(argp, &data, sizeof(data))) {
+ return -EFAULT;
+ }
+ PLS_DBG("%s: get data=%d", __func__, flag);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+
+}
+
+
+static struct file_operations al3006_pls_fops = {
+ .owner = THIS_MODULE,
+ .open = al3006_pls_open,
+ .release = al3006_pls_release,
+ .unlocked_ioctl = al3006_pls_ioctl,
+};
+
+static struct miscdevice al3006_pls_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = AL3006_PLS_DEVICE,
+ .fops = &al3006_pls_fops,
+};
+
+/*******************************************************************************
+* Function : al3006_pls_rx_data
+* Description : read data from i2c
+* Parameters : buf: content, len: length
+*******************************************************************************/
+static int al3006_pls_rx_data(char *buf, int len)
+{
+ int ret;
+
+ struct i2c_msg msgs[] = {
+ {
+ .addr = this_client->addr,
+ .flags = 0,
+ .len = 1,
+ .buf = buf,
+ },
+ {
+ .addr = this_client->addr,
+ .flags = I2C_M_RD,
+ .len = len,
+ .buf = buf,
+ }
+ };
+
+ ret = i2c_transfer(this_client->adapter, msgs, 2);
+ if (ret < 0)
+ printk(KERN_ERR "%s i2c read error: %d\n", __func__, ret);
+
+
+ return ret;
+}
+
+/*******************************************************************************
+* Function : al3006_pls_tx_data
+* Description : send data to i2c
+* Parameters : buf: content, len: length
+*******************************************************************************/
+static int al3006_pls_tx_data(char *buf, int len)
+{
+ int ret;
+
+ struct i2c_msg msg[] = {
+ {
+ .addr = this_client->addr,
+ .flags = 0,
+ .len = len,
+ .buf = buf,
+ }
+ };
+
+ ret = i2c_transfer(this_client->adapter, msg, 1);
+ if (ret < 0)
+ printk(KERN_ERR "%s i2c write error: %d\n", __func__, ret);
+
+ return ret;
+}
+
+/*******************************************************************************
+* Function : al3006_pls_write_data
+* Description : write data to IC
+* Parameters : addr: register address, data: register data
+*******************************************************************************/
+static int al3006_pls_write_data(unsigned char addr, unsigned char data)
+{
+ unsigned char buf[2];
+ buf[0]=addr;
+ buf[1]=data;
+ return al3006_pls_tx_data(buf, 2);
+}
+
+/*******************************************************************************
+* Function : al3006_pls_read_data
+* Description : read data from IC
+* Parameters : addr: register address, data: read data
+*******************************************************************************/
+static int al3006_pls_read_data(unsigned char addr, unsigned char *data)
+{
+ int ret;
+ unsigned char buf;
+
+ buf=addr;
+ ret = al3006_pls_rx_data(&buf, 1);
+ *data = buf;
+
+ return ret;;
+}
+
+#if PLS_DEBUG
+static void al3006_pls_reg_dump(void)
+{
+ unsigned char config,time_ctrl,dls_ctrl,int_status,dps_ctrl,data,dls_win;
+ al3006_pls_read_data(AL3006_PLS_REG_CONFIG,&config);
+ al3006_pls_read_data(AL3006_PLS_REG_TIME_CTRL,&time_ctrl);
+ al3006_pls_read_data(AL3006_PLS_REG_DLS_CTRL,&dls_ctrl);
+ al3006_pls_read_data(AL3006_PLS_REG_INT_STATUS,&int_status);
+ al3006_pls_read_data(AL3006_PLS_REG_DPS_CTRL,&dps_ctrl);
+ al3006_pls_read_data(AL3006_PLS_REG_DATA,&data);
+ al3006_pls_read_data(AL3006_PLS_REG_DLS_WIN,&dls_win);
+
+ PLS_DBG("%s: config=0x%x,time_ctrl=0x%x,dls_ctrl=0x%x,int_status=0x%x,dps_ctrl=0x%x,data=0x%x,dls_win=0x%x", \
+ __func__, config,time_ctrl,dls_ctrl,int_status,dps_ctrl,data,dls_win);
+}
+#endif
+
+/*******************************************************************************
+* Function : al3006_pls_reg_init
+* Description : set al3006 registers
+* Parameters : none
+* Return : void
+*******************************************************************************/
+static int al3006_pls_reg_init(void)
+{
+ int ret=0;
+
+ /*set window loss*/
+ if(al3006_pls_write_data(AL3006_PLS_REG_DLS_WIN,0x0D)<0) {
+ printk(KERN_ERR "%s: I2C Write Reg %x Failed\n", __func__, AL3006_PLS_REG_DLS_WIN);
+ ret = -1;
+ }
+
+ /*set time control*/
+ if(al3006_pls_write_data(AL3006_PLS_REG_TIME_CTRL,0x1)<0) {
+ printk(KERN_ERR "%s: I2C Write Reg %x Failed\n", __func__, AL3006_PLS_REG_TIME_CTRL);
+ ret = -1;
+ }
+
+ /*set alps level*/
+#if defined(AL3006_PLS_ADC_LEVEL64)
+ al3006_pls_write_data(AL3006_PLS_REG_DLS_CTRL,0xA0);
+#elif defined(AL3006_PLS_ADC_LEVEL9)
+ al3006_pls_write_data(AL3006_PLS_REG_DLS_CTRL,0x40);
+#elif defined(AL3006_PLS_ADC_LEVEL5)
+ al3006_pls_write_data(AL3006_PLS_REG_DLS_CTRL,0x20);
+#endif
+
+ /*set dps distance*/
+ if(al3006_pls_write_data(AL3006_PLS_REG_DPS_CTRL,0x10)<0) {
+ printk(KERN_ERR "%s: I2C Write Reg %x Failed\n", __func__, AL3006_PLS_REG_DPS_CTRL);
+ ret = -1;
+ }
+
+ /*enter power down mode*/
+ if(al3006_pls_write_data(AL3006_PLS_REG_CONFIG,AL3006_PLS_BOTH_DEACTIVE)<0) {
+ printk(KERN_ERR "%s: I2C Write Reg %x Failed\n", __func__, AL3006_PLS_BOTH_DEACTIVE);
+ ret = -1;
+ }
+
+#if PLS_DEBUG
+ al3006_pls_reg_dump();
+#endif
+
+ return ret;
+}
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+/*******************************************************************************
+* Function : al3006_pls_early_suspend
+* Description : cancel the delayed work and put ts to shutdown mode
+* Parameters : handler
+* Return : none
+*******************************************************************************/
+static void al3006_pls_early_suspend(struct early_suspend *handler)
+{
+ PLS_DBG("%s\n", __func__);
+#if 0
+ al3006_pls_disable(AL3006_PLS_BOTH);
+#endif
+}
+
+/*******************************************************************************
+* Function : al3006_pls_early_resume
+* Description : ts re-entry the normal mode and schedule the work, there need to be a litte time
+ for ts ready
+* Parameters : handler
+* Return : none
+*******************************************************************************/
+static void al3006_pls_early_resume(struct early_suspend *handler)
+{
+ PLS_DBG("%s\n", __func__);
+#if 0
+ al3006_pls_enable(AL3006_PLS_BOTH);
+#endif
+}
+#endif
+
+static void al3006_pls_report_init(void)
+{
+ al3006_pls_struct *al3006_pls = (al3006_pls_struct *)i2c_get_clientdata(this_client);
+ PLS_DBG("%s\n",__func__);
+ /*report far*/
+ input_report_abs(al3006_pls->input, ABS_DISTANCE, 1);
+ input_sync(al3006_pls->input);
+}
+
+/*******************************************************************************
+* Function : al3006_pls_report_dps
+* Description : reg data,input dev
+* Parameters : report proximity sensor data to input system
+* Return : none
+*******************************************************************************/
+static void al3006_pls_report_dps(unsigned char data, struct input_dev *input)
+{
+ unsigned char dps_data = (data&0x80)>>7;
+
+ dps_data = (dps_data==1) ? 0: 1;
+ PLS_DBG("%s: proximity=%d", __func__, dps_data);
+
+ input_report_abs(input, ABS_DISTANCE, dps_data);
+ input_sync(input);
+}
+
+/*******************************************************************************
+* Function : al3006_pls_report_dls
+* Description : reg data,input dev
+* Parameters : report light sensor data to input system
+* Return : none
+*******************************************************************************/
+static void al3006_pls_report_dls(unsigned char data, struct input_dev *input)
+{
+ int lux;
+ unsigned char adc = data&0x3f;
+
+#if defined(AL3006_PLS_ADC_LEVEL9)
+ adc = adc<<3;
+#elif defined(AL3006_PLS_ADC_LEVEL5)
+ adc = adc<<4;
+#endif
+
+ lux = al3006_pls_adc2lux[adc];
+ PLS_DBG("%s: adc=%d, lux=%d", __func__, adc, lux);
+ input_report_abs(input, ABS_MISC, lux);
+ input_sync(input);
+}
+
+/*******************************************************************************
+* Function : al3006_pls_work
+* Description : handler current data and report coordinate
+* Parameters : work
+* Return : none
+*******************************************************************************/
+
+static void al3006_pls_work(struct work_struct *work)
+{
+ unsigned char int_status, data, enable;
+ al3006_pls_struct *pls = container_of(work, al3006_pls_struct, work);
+
+ al3006_pls_read_data(AL3006_PLS_REG_CONFIG,&enable);
+ al3006_pls_read_data(AL3006_PLS_REG_INT_STATUS,&int_status);
+ al3006_pls_read_data(AL3006_PLS_REG_DATA,&data);
+
+ PLS_DBG("\033[33;1m %s: enable=0x%x; int_status=0x%x; data=0x%x \033[m",\
+ __func__, enable, int_status, data);
+
+ switch(int_status & AL3006_PLS_INT_MASK) {
+ case AL3006_PLS_DPS_INT:
+ al3006_pls_report_dps(data, pls->input);
+ break;
+
+ case AL3006_PLS_DLS_INT:
+ al3006_pls_report_dls(data, pls->input);
+ break;
+
+ case AL3006_PLS_INT_MASK:
+ al3006_pls_report_dps(data, pls->input);
+ al3006_pls_report_dls(data, pls->input);
+ break;
+
+ default:
+ break;
+ }
+
+ enable_irq(pls->client->irq);
+
+}
+
+
+/*******************************************************************************
+* Function : al3006_pls_irq_handler
+* Description : handle ts irq
+* Parameters : handler
+* Return : none
+*******************************************************************************/
+static irqreturn_t al3006_pls_irq_handler(int irq, void *dev_id)
+{
+ al3006_pls_struct *pls = (al3006_pls_struct *)dev_id;
+
+ disable_irq_nosync(pls->client->irq);
+ queue_work(pls->ltr_work_queue,&pls->work);
+ return IRQ_HANDLED;
+}
+
+static void al3006_pls_pininit(int irq_pin)
+{
+ printk(KERN_INFO "%s [irq=%d]\n",__func__,irq_pin);
+ gpio_request(irq_pin, AL3006_PLS_IRQ_PIN);
+}
+
+
+static int al3006_pls_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+
+ int err = 0;
+ struct al3006_pls_platform_data *pdata = client->dev.platform_data;
+ struct input_dev *input_dev;
+ al3006_pls_struct *al3006_pls;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ printk("%s: functionality check failed\n", __func__);
+ err = -ENODEV;
+ goto exit_check_functionality_failed;
+ }
+
+ al3006_pls = kzalloc(sizeof(al3006_pls_struct), GFP_KERNEL);
+ if (!al3006_pls)
+ {
+ printk("%s: request memory failed\n", __func__);
+ err= -ENOMEM;
+ goto exit_request_memory_failed;
+ }
+
+ this_client = client;
+ al3006_pls->client = client;
+ i2c_set_clientdata(client, al3006_pls);
+
+ /*pin init*/
+ al3006_pls_pininit(pdata->irq_gpio_number);
+
+ /*get irq*/
+ client->irq = gpio_to_irq(pdata->irq_gpio_number);
+ al3006_pls->irq = client->irq;
+
+ PLS_DBG("I2C name=%s, addr=0x%x, gpio=%d, irq=%d",client->name,client->addr, \
+ pdata->irq_gpio_number, client->irq);
+
+ /*init AL3006_PLS*/
+ if(al3006_pls_reg_init()<0)
+ {
+ printk(KERN_ERR "%s: device init failed\n", __func__);
+ err=-1;
+ goto exit_device_init_failed;
+ }
+
+ /*register device*/
+ err = misc_register(&al3006_pls_device);
+ if (err) {
+ printk(KERN_ERR "%s: al3006_pls_device register failed\n", __func__);
+ goto exit_device_register_failed;
+ }
+
+
+ /* register input device*/
+ input_dev = input_allocate_device();
+ if (!input_dev)
+ {
+ printk(KERN_ERR "%s: input allocate device failed\n", __func__);
+ err = -ENOMEM;
+ goto exit_input_dev_allocate_failed;
+ }
+
+ al3006_pls->input = input_dev;
+
+ input_dev->name = AL3006_PLS_INPUT_DEV;
+ input_dev->phys = AL3006_PLS_INPUT_DEV;
+ input_dev->id.bustype = BUS_I2C;
+ input_dev->dev.parent = &client->dev;
+ input_dev->id.vendor = 0x0001;
+ input_dev->id.product = 0x0001;
+ input_dev->id.version = 0x0010;
+
+ __set_bit(EV_ABS, input_dev->evbit);
+ /*for proximity*/
+ input_set_abs_params(input_dev, ABS_DISTANCE, 0, 1, 0, 0);
+ /*for lightsensor*/
+ input_set_abs_params(input_dev, ABS_MISC, 0, 100001, 0, 0);
+
+ err = input_register_device(input_dev);
+ if (err < 0)
+ {
+ printk(KERN_ERR "%s: input device regist failed\n", __func__);
+ goto exit_input_register_failed;
+ }
+
+ /*create work queue*/
+ INIT_WORK(&al3006_pls->work, al3006_pls_work);
+ al3006_pls->ltr_work_queue= create_singlethread_workqueue(AL3006_PLS_DEVICE);
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ /*register early suspend*/
+ al3006_pls->ltr_early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
+ al3006_pls->ltr_early_suspend.suspend = al3006_pls_early_suspend;
+ al3006_pls->ltr_early_suspend.resume = al3006_pls_early_resume;
+ register_early_suspend(&al3006_pls->ltr_early_suspend);
+#endif
+
+ if(client->irq > 0)
+ {
+ err = request_irq(client->irq, al3006_pls_irq_handler, IRQ_TYPE_LEVEL_LOW, client->name,al3006_pls);
+ if (err <0)
+ {
+ printk(KERN_ERR "%s: IRQ setup failed %d\n", __func__, err);
+ goto irq_request_err;
+ }
+ }
+
+ /*create attribute files*/
+ al3006_pls_create_sysfs(client);
+
+
+ printk(KERN_INFO "%s: Probe Success!\n",__func__);
+
+ return 0;
+
+irq_request_err:
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ unregister_early_suspend(&al3006_pls->ltr_early_suspend);
+#endif
+ destroy_workqueue(al3006_pls->ltr_work_queue);
+exit_input_register_failed:
+ input_free_device(input_dev);
+ misc_deregister(&al3006_pls_device);
+exit_device_register_failed:
+exit_input_dev_allocate_failed:
+exit_device_init_failed:
+ kfree(al3006_pls);
+exit_request_memory_failed:
+exit_check_functionality_failed:
+ printk(KERN_ERR "%s: Probe Fail!\n",__func__);
+ return err;
+
+}
+
+static int al3006_pls_remove(struct i2c_client *client)
+{
+ al3006_pls_struct *al3006_pls = i2c_get_clientdata(client);
+
+ PLS_DBG("%s", __func__);
+
+ /*remove queue*/
+ flush_workqueue(al3006_pls->ltr_work_queue);
+ destroy_workqueue(al3006_pls->ltr_work_queue);
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ /*free suspend*/
+ unregister_early_suspend(&al3006_pls->ltr_early_suspend);
+#endif
+ misc_deregister(&al3006_pls_device);
+ /*free input*/
+ input_unregister_device(al3006_pls->input);
+ input_free_device(al3006_pls->input);
+ /*free irq*/
+ free_irq(al3006_pls->client->irq, al3006_pls);
+ /*free malloc*/
+ kfree(al3006_pls);
+
+ return 0;
+}
+
+static const struct i2c_device_id al3006_pls_id[] = {
+ { AL3006_PLS_DEVICE, 0 },
+ { }
+};
+/*----------------------------------------------------------------------------*/
+static struct i2c_driver al3006_pls_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = AL3006_PLS_DEVICE,
+ },
+ .probe = al3006_pls_probe,
+ .remove = al3006_pls_remove,
+ .id_table = al3006_pls_id,
+};
+/*----------------------------------------------------------------------------*/
+
+static int __init al3006_pls_init(void)
+{
+ PLS_DBG("%s", __func__);
+ return i2c_add_driver(&al3006_pls_driver);
+}
+
+static void __exit al3006_pls_exit(void)
+{
+ PLS_DBG("%s", __func__);
+ i2c_del_driver(&al3006_pls_driver);
+}
+
+module_init(al3006_pls_init);
+module_exit(al3006_pls_exit);
+
+
+MODULE_AUTHOR("Yunlong Wang <Yunlong.Wang@spreadtrum.com>");
+MODULE_DESCRIPTION("Proximity&Light Sensor AL3006 DRIVER");
+MODULE_LICENSE("GPL");
+