diff options
Diffstat (limited to 'drivers/input/misc/epl2182_pls_v2.c')
| -rw-r--r-- | drivers/input/misc/epl2182_pls_v2.c | 1496 |
1 files changed, 1496 insertions, 0 deletions
diff --git a/drivers/input/misc/epl2182_pls_v2.c b/drivers/input/misc/epl2182_pls_v2.c new file mode 100644 index 00000000..6d14770c --- /dev/null +++ b/drivers/input/misc/epl2182_pls_v2.c @@ -0,0 +1,1496 @@ +/* drivers/i2c/chips/epl2182.c - light and proxmity sensors driver + * Copyright (C) 2011 ELAN Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/hrtimer.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/earlysuspend.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/irq.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <asm/uaccess.h> +//#include <asm/mach-types.h> +#include <asm/setup.h> +#include <linux/wakelock.h> +#include <linux/jiffies.h> +#include <linux/regulator/consumer.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/i2c/epl2182_pls_v2.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> + +/****************************************************************************** + * configuration +*******************************************************************************/ +#define PS_INTERRUPT_MODE 1 // 0 is polling mode, 1 is interrupt mode +#define PLSENSOR_ADAPTIVE +#ifdef PLSENSOR_ADAPTIVE +/*ensure the initial value can enable Psensor interrupt,the ps_state of the initial value will be 0*/ +static u16 P_SENSOR_LTHD = 0x7fff; //init +static u16 P_SENSOR_HTHD = 0x0; //init +#else +#define P_SENSOR_LTHD 1900//120 //100 +#define P_SENSOR_HTHD 2000//170 //500 +#endif + +#define PS_POLLING_RATE 100 +#define ALS_POLLING_RATE 1000 + +#define LUX_PER_COUNT 440 // 660 = 1.1*0.6*1000 +#ifdef PLSENSOR_ADAPTIVE +#define DEBOUNCE 30 +#define MIN_SPACING 250 +#define DIVEDE 4 +static bool first_do_irq = true; +static u16 ps_min = 0; +static u16 ps_max = 0; +static u16 ps_threshold = 0; +#endif +/****************************************************************************** + * configuration +*******************************************************************************/ + +typedef enum +{ + CMC_MODE_ALS = 0x00, + CMC_MODE_PS = 0x10, +} CMC_MODE; + +#define TXBYTES 2 +#define RXBYTES 2 + +#define PS_DELAY 50 +#define ALS_DELAY 55 + +#define PACKAGE_SIZE 2 +#define I2C_RETRY_COUNT 10 +#define P_INTT 1 + +#define PS_INTT 4 +#define ALS_INTT 6 //5-8 + +static int set_psensor_intr_threshold(uint16_t low_thd, uint16_t high_thd); + +#if PS_INTERRUPT_MODE +static void epl_sensor_irq_do_work(struct work_struct *work); +static DECLARE_WORK(epl_sensor_irq_work, epl_sensor_irq_do_work); +#endif + +static void report_polling_do_work(struct work_struct *work); +static DECLARE_DELAYED_WORK(report_polling_work, report_polling_do_work); +static void polling_do_work(struct work_struct *work); +static DECLARE_DELAYED_WORK(polling_work, polling_do_work); + +/* primitive raw data from I2C */ +typedef struct _epl_raw_data +{ + u8 raw_bytes[PACKAGE_SIZE]; + u16 ps_state; + u16 ps_int_state; + u16 ps_ch1_raw; + u16 als_ch1_raw; +} epl_raw_data; + +struct elan_epl_data +{ + struct i2c_client *client; + //struct input_dev *als_input_dev; + struct input_dev *ps_input_dev; + struct workqueue_struct *epl_wq; + struct early_suspend early_suspend; + + int intr_pin; + //int (*power)(int on); + + int ps_opened; + int als_opened; + + int enable_pflag; + int enable_lflag; + int read_flag; + int irq; +} ; + +static DECLARE_WAIT_QUEUE_HEAD(ps_waitqueue); +static DECLARE_WAIT_QUEUE_HEAD(ls_waitqueue); + + +static int ps_data_changed; +static int ls_data_changed; +static struct i2c_client *this_client = NULL; + +//static struct wake_lock g_ps_wlock; +struct elan_epl_data *epl_data; +static epl_raw_data gRawData; +static int dual_count; + +static const char ElanPsensorName[] = "proximity"; +static const char ElanALsensorName[] = "lightsensor-level"; + +static int psensor_mode_suspend = 0; + +#define LOG_TAG "[EPL2182] " +#define LOG_FUN(f) printk(KERN_INFO LOG_TAG"%s\n", __FUNCTION__) +#define LOG_INFO(fmt, args...) printk(KERN_INFO LOG_TAG fmt, ##args) +#define LOG_ERR(fmt, args...) printk(KERN_ERR LOG_TAG"%s %d : "fmt, __FUNCTION__, __LINE__, ##args) + +/* +//====================I2C write operation===============// +//regaddr: ELAN Register Address. +//bytecount: How many bytes to be written to register via i2c bus. +//txbyte: I2C bus transmit byte(s). Single byte(0X01) transmit only slave address. +//data: setting value. +// +// Example: If you want to write single byte to 0x1D register address, show below +// elan_sensor_I2C_Write(client,0x1D,0x01,0X02,0xff); +// +*/ +static int elan_sensor_I2C_Write(struct i2c_client *client, uint8_t regaddr, uint8_t bytecount, uint8_t txbyte, uint8_t data) +{ + uint8_t buffer[2]; + int ret = 0; + int retry,val; + struct elan_epl_data *epld = epl_data; + + buffer[0] = (regaddr<<3) | bytecount ; + buffer[1] = data; + + for(retry = 0; retry < I2C_RETRY_COUNT; retry++) + { + ret = i2c_master_send(client, buffer, txbyte); + + if (ret == txbyte) + { + break; + } + + val = gpio_get_value(epld->intr_pin); + + LOG_INFO("INTERRUPT GPIO val = %d\n", val); + + msleep(10); + } + + if(retry>=I2C_RETRY_COUNT) + { + LOG_ERR(KERN_ERR "i2c write retry over %d\n", I2C_RETRY_COUNT); + return -EINVAL; + } + + return ret; +} + +static int elan_sensor_I2C_Read(struct i2c_client *client) +{ + uint8_t buffer[RXBYTES]; + int ret = 0, i =0; + int retry,val; + struct elan_epl_data *epld = epl_data; + + for(retry = 0; retry < I2C_RETRY_COUNT; retry++) + { + ret = i2c_master_recv(client, buffer, RXBYTES); + + if (ret == RXBYTES) + break; + + val = gpio_get_value(epld->intr_pin); + + LOG_INFO("INTERRUPT GPIO val = %d\n", val); + + msleep(10); + } + + if(retry>=I2C_RETRY_COUNT) + { + LOG_ERR("i2c read retry over %d\n", I2C_RETRY_COUNT); + return -EINVAL; + } + + for(i=0; i<PACKAGE_SIZE; i++) + { + gRawData.raw_bytes[i] = buffer[i]; + } + + return ret; +} + +static void elan_sensor_restart_work(void) +{ + struct elan_epl_data *epld = epl_data; + cancel_delayed_work(&polling_work); + cancel_delayed_work(&report_polling_work); + queue_delayed_work(epld->epl_wq, &polling_work,msecs_to_jiffies(10)); +} + +static int elan_sensor_psensor_enable(struct elan_epl_data *epld) +{ + int ret; + uint8_t regdata = 0; + struct i2c_client *client = epld->client; + + LOG_INFO("--- Proximity sensor Enable --- \n"); + + //disable_irq(epld->irq); + ret = elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_DISABLE); + + regdata = EPL_SENSING_8_TIME | EPL_PS_MODE | EPL_L_GAIN ; + regdata = regdata | (PS_INTERRUPT_MODE ? EPL_C_SENSING_MODE : EPL_S_SENSING_MODE); + ret = elan_sensor_I2C_Write(client,REG_0,W_SINGLE_BYTE,0X02,regdata); + + regdata = PS_INTT<<4 | EPL_PST_1_TIME | EPL_10BIT_ADC; + ret = elan_sensor_I2C_Write(client,REG_1,W_SINGLE_BYTE,0X02,regdata); + + set_psensor_intr_threshold(P_SENSOR_LTHD ,P_SENSOR_HTHD); +#ifdef PLSENSOR_ADAPTIVE + if (true == first_do_irq) { + msleep(PS_DELAY); + elan_sensor_I2C_Write(client,REG_13,R_SINGLE_BYTE,0x01,0); + elan_sensor_I2C_Read(client); + gRawData.ps_state= !((gRawData.raw_bytes[0]&0x04)>>2); + elan_sensor_I2C_Write(client,REG_16,R_TWO_BYTE,0x01,0x00); + elan_sensor_I2C_Read(client); + gRawData.ps_ch1_raw = (gRawData.raw_bytes[1]<<8) | gRawData.raw_bytes[0]; + P_SENSOR_LTHD = gRawData.ps_ch1_raw + MIN_SPACING - DEBOUNCE; + P_SENSOR_HTHD = gRawData.ps_ch1_raw + MIN_SPACING - DEBOUNCE; + set_psensor_intr_threshold(P_SENSOR_LTHD ,P_SENSOR_HTHD); + } +#endif + + ret = elan_sensor_I2C_Write(client,REG_7,W_SINGLE_BYTE,0X02,EPL_C_RESET); + ret = elan_sensor_I2C_Write(client,REG_7,W_SINGLE_BYTE,0x02,EPL_C_START_RUN); + +#if PS_INTERRUPT_MODE + if(epld->enable_lflag) + { + msleep(PS_DELAY); + elan_sensor_I2C_Write(client,REG_13,R_SINGLE_BYTE,0x01,0); + elan_sensor_I2C_Read(client); + gRawData.ps_state= !((gRawData.raw_bytes[0]&0x04)>>2); + + if(gRawData.ps_state!= gRawData.ps_int_state) + { + elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_FRAME_ENABLE); + } + else + { + elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_ACTIVE_LOW); + } + + } + else + { + elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_ACTIVE_LOW); + } + //enable_irq(epld->irq); +#endif + + if (ret != 0x02) + { + LOG_INFO("P-sensor i2c err\n"); + } + + return ret; +} + +static int elan_sensor_lsensor_enable(struct elan_epl_data *epld) +{ + int ret; + uint8_t regdata = 0; + struct i2c_client *client = epld->client; + + LOG_INFO("--- ALS sensor Enable --- \n"); + + //disable_irq(epld->irq); + regdata = EPL_INT_DISABLE; + ret = elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02, regdata); + + regdata = EPL_S_SENSING_MODE | EPL_SENSING_8_TIME | EPL_ALS_MODE | EPL_AUTO_GAIN; + ret = elan_sensor_I2C_Write(client,REG_0,W_SINGLE_BYTE,0X02,regdata); + + regdata = ALS_INTT<<4 | EPL_PST_1_TIME | EPL_10BIT_ADC; + ret = elan_sensor_I2C_Write(client,REG_1,W_SINGLE_BYTE,0X02,regdata); + + ret = elan_sensor_I2C_Write(client,REG_10,W_SINGLE_BYTE,0x02, EPL_GO_MID); + ret = elan_sensor_I2C_Write(client,REG_11,W_SINGLE_BYTE,0x02, EPL_GO_LOW); + + ret = elan_sensor_I2C_Write(client,REG_7,W_SINGLE_BYTE,0X02,EPL_C_RESET); + ret = elan_sensor_I2C_Write(client,REG_7,W_SINGLE_BYTE,0x02,EPL_C_START_RUN); + + if(ret != 0x02) + { + LOG_INFO(" ALS-sensor i2c err\n"); + } + + return ret; + +} + +/* +//====================elan_epl_ps_poll_rawdata===============// +//polling method for proximity sensor detect. Report proximity sensor raw data. +//Report "ABS_DISTANCE" event to HAL layer. +//Variable "value" 0 and 1 to represent which distance from psensor to target(human's face..etc). +//value: 0 represent near. +//value: 1 represent far. +*/ +static int elan_epl_ps_poll_rawdata(void) +{ + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + u16 value = 0; + + elan_sensor_I2C_Write(epld->client,REG_7,W_SINGLE_BYTE,0x02,EPL_DATA_LOCK); + + elan_sensor_I2C_Write(client,REG_13,R_SINGLE_BYTE,0x01,0); + elan_sensor_I2C_Read(client); + gRawData.ps_state= !((gRawData.raw_bytes[0]&0x04)>>2); + + elan_sensor_I2C_Write(client,REG_16,R_TWO_BYTE,0x01,0x00); + elan_sensor_I2C_Read(client); + gRawData.ps_ch1_raw = (gRawData.raw_bytes[1]<<8) | gRawData.raw_bytes[0]; + +#ifdef PLSENSOR_ADAPTIVE + value = gRawData.ps_ch1_raw; + /*threshold detect process*/ + if(0 == ps_max || 0 == ps_min) { + ps_min = value; + ps_max = value; + ps_threshold = ps_min + MIN_SPACING; + } + if(value > ps_max) { + ps_max = value; + ps_threshold = ((ps_max - ps_min) / DIVEDE) + ps_min; + if(ps_threshold - ps_min < MIN_SPACING) + ps_threshold = ps_min + MIN_SPACING; + } else if(value < ps_min) { + ps_min = value; + ps_threshold = ((ps_max - ps_min) / DIVEDE) + ps_min; + if(ps_threshold - ps_min < MIN_SPACING) + ps_threshold = ps_min + MIN_SPACING; + } + P_SENSOR_LTHD = ps_threshold - DEBOUNCE; + P_SENSOR_HTHD = ps_threshold + DEBOUNCE; + set_psensor_intr_threshold(P_SENSOR_LTHD ,P_SENSOR_HTHD); + /*threshold detect process end*/ + LOG_INFO("ps_min (%4d), ps_max (%4d), ps_threshold (%4d)\n", ps_min, ps_max, ps_threshold); + if (true == first_do_irq) + first_do_irq = false; +#endif + elan_sensor_I2C_Write(epld->client,REG_7,W_SINGLE_BYTE,0x02,EPL_DATA_UNLOCK); + LOG_INFO("### ps_ch1_raw_data (%d), value(%d) ###\n", gRawData.ps_ch1_raw, gRawData.ps_state); + ps_data_changed = 1; + + input_report_abs(epld->ps_input_dev, ABS_DISTANCE, gRawData.ps_state); + input_sync(epld->ps_input_dev); + return 0; +} + +static void elan_epl_als_rawdata(void) +{ + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + uint32_t lux; + + elan_sensor_I2C_Write(client,REG_16,R_TWO_BYTE,0x01,0x00); + elan_sensor_I2C_Read(client); + gRawData.als_ch1_raw = (gRawData.raw_bytes[1]<<8) | gRawData.raw_bytes[0]; + + lux = (gRawData.als_ch1_raw* LUX_PER_COUNT) / 1000 * 15 / 100; + if(lux>20000) + lux=20000; + + LOG_INFO("------------------- ALS raw = %d, lux = %d\n\n", gRawData.als_ch1_raw, lux); + ls_data_changed = 1; +#if 1 + input_report_abs(epld->ps_input_dev, ABS_MISC, lux); + input_sync(epld->ps_input_dev); +#else + input_report_abs(epld->als_input_dev, ABS_MISC, lux); + input_sync(epld->als_input_dev); +#endif +} + +/* +//====================set_psensor_intr_threshold===============// +//low_thd: The value is psensor interrupt low threshold. +//high_thd: The value is psensor interrupt hihg threshold. +//When psensor rawdata > hihg_threshold, interrupt pin will be pulled low. +//After interrupt occur, psensor rawdata < low_threshold, interrupt pin will be pulled high. +*/ +static int set_psensor_intr_threshold(uint16_t low_thd, uint16_t high_thd) +{ + int ret = 0; + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + + uint8_t high_msb ,high_lsb, low_msb, low_lsb; + + high_msb = (uint8_t) (high_thd >> 8); + high_lsb = (uint8_t) (high_thd & 0x00ff); + low_msb = (uint8_t) (low_thd >> 8); + low_lsb = (uint8_t) (low_thd & 0x00ff); + + elan_sensor_I2C_Write(client,REG_2,W_SINGLE_BYTE,0x02,high_lsb); + elan_sensor_I2C_Write(client,REG_3,W_SINGLE_BYTE,0x02,high_msb); + elan_sensor_I2C_Write(client,REG_4,W_SINGLE_BYTE,0x02,low_lsb); + elan_sensor_I2C_Write(client,REG_5,W_SINGLE_BYTE,0x02,low_msb); + + return ret; +} + +#if PS_INTERRUPT_MODE +static void epl_sensor_irq_do_work(struct work_struct *work) +{ + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + int mode = 0; + LOG_FUN(); + + elan_sensor_I2C_Write(epld->client,REG_7,W_SINGLE_BYTE,0x02,EPL_DATA_LOCK); + + elan_sensor_I2C_Write(client,REG_13,R_SINGLE_BYTE,0x01,0); + elan_sensor_I2C_Read(client); + mode = gRawData.raw_bytes[0]&(3<<4); + + // 0x10 is ps mode + if(mode==CMC_MODE_PS && epld->enable_pflag) + { + gRawData.ps_int_state= !((gRawData.raw_bytes[0]&0x04)>>2); + elan_epl_ps_poll_rawdata(); + } + else + { + LOG_INFO("interrupt in als\n"); + } + elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_ACTIVE_LOW); + elan_sensor_I2C_Write(client,REG_7,W_SINGLE_BYTE,0x02,EPL_DATA_UNLOCK); + enable_irq(epld->irq); +} + +static irqreturn_t elan_sensor_irq_handler(int irqNo, void *handle) +{ + struct elan_epl_data *epld = (struct elan_epl_data*)handle; + + disable_irq_nosync(epld->irq); + queue_work(epld->epl_wq, &epl_sensor_irq_work); + + return IRQ_HANDLED; +} +#endif + +static void report_polling_do_work(struct work_struct *work) +{ + struct elan_epl_data *epld = epl_data; + + if(dual_count==CMC_MODE_PS) + { + elan_epl_ps_poll_rawdata(); + } + else if(dual_count==CMC_MODE_ALS) + { + elan_epl_als_rawdata(); + } + + if(epld->enable_pflag) + { + elan_sensor_psensor_enable(epld); + + if(PS_INTERRUPT_MODE==0) + { + dual_count=CMC_MODE_PS; // ps mode + queue_delayed_work(epld->epl_wq, &report_polling_work,msecs_to_jiffies(PS_POLLING_RATE)); + } + } +} + +static void polling_do_work(struct work_struct *work) +{ + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + + bool isInterleaving = epld->enable_pflag==1 && epld->enable_lflag==1; + bool isAlsOnly = epld->enable_pflag==0 && epld->enable_lflag==1; + bool isPsOnly = epld->enable_pflag==1 && epld->enable_lflag==0; + + cancel_delayed_work(&polling_work); + cancel_delayed_work(&report_polling_work); + + LOG_INFO("enable_pflag = %d, enable_lflag = %d\n", epld->enable_pflag, epld->enable_lflag); + + if(isAlsOnly || isInterleaving) + { + elan_sensor_lsensor_enable(epld); + dual_count = CMC_MODE_ALS; + + queue_delayed_work(epld->epl_wq, &report_polling_work,msecs_to_jiffies(ALS_DELAY)); + queue_delayed_work(epld->epl_wq, &polling_work,msecs_to_jiffies(ALS_POLLING_RATE)); + } + else if(isPsOnly) + { + elan_sensor_psensor_enable(epld); + dual_count = CMC_MODE_PS; + + if(PS_INTERRUPT_MODE) + { + // do nothing + } + else + { + queue_delayed_work(epld->epl_wq, &report_polling_work,msecs_to_jiffies(PS_DELAY)); + queue_delayed_work(epld->epl_wq, &polling_work,msecs_to_jiffies(PS_POLLING_RATE)); + } + } + else + { + elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_DISABLE); + elan_sensor_I2C_Write(client,REG_0,W_SINGLE_BYTE,0X02,EPL_S_SENSING_MODE); + } +} + +#if 0 +static ssize_t elan_ls_operationmode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + uint16_t mode=0; + struct elan_epl_data *epld = epl_data; + LOG_FUN(); + + sscanf(buf, "%hu",&mode); + LOG_INFO("==>[operation mode]=%d\n", mode); + + if(mode == 0) + { + epld->enable_lflag = 0; + epld->enable_pflag = 0; + } + else if(mode == 1) + { + epld->enable_lflag = 1; + epld->enable_pflag = 0; + } + else if(mode == 2) + { + epld->enable_lflag = 0; + epld->enable_pflag = 1; + } + else if(mode == 3) + { + epld->enable_lflag = 1; + epld->enable_pflag = 1; + } + else + { + LOG_INFO("0: none\n1: als only\n2: ps only\n3: interleaving"); + } + + elan_sensor_restart_work(); + return count; +} + +static ssize_t elan_ls_operationmode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct elan_epl_data *epld = epl_data; + long *tmp = (long*)buf; + uint16_t mode =0; + LOG_FUN(); + + if( epld->enable_pflag==0 && epld->enable_lflag==0) + { + mode = 0; + } + else if( epld->enable_pflag==0 && epld->enable_lflag==1) + { + mode = 1; + } + else if( epld->enable_pflag==1 && epld->enable_lflag==0) + { + mode = 2; + } + else if( epld->enable_pflag==1 && epld->enable_lflag==1) + { + mode = 3; + } + tmp[0] = mode; + return sprintf(buf, "%d \n", mode); +} + +static ssize_t elan_ls_status_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct elan_epl_data *epld = epl_data; + u16 ch1; + + if(!epl_data) + { + LOG_INFO("epl_data is null!!\n"); + return 0; + } + elan_sensor_I2C_Write(epld->client,REG_7,W_SINGLE_BYTE,0x02,EPL_DATA_LOCK); + + elan_sensor_I2C_Write(epld->client,REG_16,R_TWO_BYTE,0x01,0x00); + elan_sensor_I2C_Read(epld->client); + ch1 = (gRawData.raw_bytes[1]<<8) | gRawData.raw_bytes[0]; + LOG_INFO("ch1 raw_data = %d\n", ch1); + + elan_sensor_I2C_Write(epld->client,REG_7,W_SINGLE_BYTE,0x02,EPL_DATA_UNLOCK); + + return sprintf(buf, "%d\n", ch1); +} + +static DEVICE_ATTR(elan_ls_operationmode, S_IROTH|S_IWOTH, elan_ls_operationmode_show,elan_ls_operationmode_store); +static DEVICE_ATTR(elan_ls_status, S_IROTH|S_IWOTH, elan_ls_status_show,NULL); + +static struct attribute *ets_attributes[] = +{ + &dev_attr_elan_ls_operationmode.attr, + &dev_attr_elan_ls_status.attr, + NULL, +}; + +static struct attribute_group ets_attr_group = +{ + .attrs = ets_attributes, +}; +#endif + +#if 0 //ices add +static int elan_als_open(struct inode *inode, struct file *file) +{ + struct elan_epl_data *epld = epl_data; + + LOG_FUN(); + + if (epld->als_opened) + { + return -EBUSY; + } + epld->als_opened = 1; + + return 0; +} + +static int elan_als_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct elan_epl_data *epld = epl_data; + int buf[1]; + if(epld->read_flag ==1) + { + buf[0] = gRawData.als_ch1_raw; + if(copy_to_user(buffer, &buf , sizeof(buf))) + return 0; + epld->read_flag = 0; + return 12; + } + else + { + return 0; + } +} + +static int elan_als_release(struct inode *inode, struct file *file) +{ + struct elan_epl_data *epld = epl_data; + + LOG_FUN(); + + epld->als_opened = 0; + + return 0; +} +#if 1 +static long elan_als_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int flag; + unsigned long buf[1]; + struct elan_epl_data *epld = epl_data; + + void __user *argp = (void __user *)arg; + + LOG_INFO("als io ctrl cmd %d\n", _IOC_NR(cmd)); + + switch(cmd) + { + case ELAN_EPL6800_IOCTL_GET_LFLAG: + + LOG_INFO("elan ambient-light IOCTL Sensor get lflag \n"); + flag = epld->enable_lflag; + if (copy_to_user(argp, &flag, sizeof(flag))) + return -EFAULT; + + LOG_INFO("elan ambient-light Sensor get lflag %d\n",flag); + break; + + case ELAN_EPL6800_IOCTL_ENABLE_LFLAG: + + LOG_INFO("elan ambient-light IOCTL Sensor set lflag \n"); + if (copy_from_user(&flag, argp, 1)) + return -EFAULT; + LOG_INFO("elan ambient-light Sensor set lflag %d\n",flag); + if(flag) { + flag = 1; + }else { + flag = 0; + } + //if (flag < 0 || flag > 1) + // return -EINVAL; + + epld->enable_lflag = flag; + elan_sensor_restart_work(); + + //LOG_INFO("elan ambient-light Sensor set lflag %d\n",flag); + break; + + case ELAN_EPL6800_IOCTL_GETDATA: + buf[0] = (unsigned long)gRawData.als_ch1_raw; + if(copy_to_user(argp, &buf , 2)) + return -EFAULT; + + break; + + default: + LOG_ERR("invalid cmd %d\n", _IOC_NR(cmd)); + return -EINVAL; + } + + return 0; + +} +#endif +static unsigned int elan_als_poll(struct file *fp, poll_table * wait) +{ + if(ls_data_changed){ + //pr_info("%s, ls_data_changed 1st, ls_data = 0x%x\n", __func__, ls_data); + ls_data_changed = 0; + //mutex_unlock(&stk3x1x_mutex); + return POLLIN | POLLRDNORM; + } + poll_wait(fp, &ls_waitqueue, wait); + //mutex_unlock(&stk3x1x_mutex); + return 0; +} + +static struct file_operations elan_als_fops = +{ + .owner = THIS_MODULE, + .open = elan_als_open, + .read = elan_als_read, + .release = elan_als_release, + .unlocked_ioctl = elan_als_ioctl, + .poll = elan_als_poll, +}; + +static struct miscdevice elan_als_device = +{ + .minor = MISC_DYNAMIC_MINOR, + .name = "elan_als", + .fops = &elan_als_fops +}; +#endif //ices end + +static int elan_ps_open(struct inode *inode, struct file *file) +{ + struct elan_epl_data *epld = epl_data; + + LOG_FUN(); + + if (epld->ps_opened) + return -EBUSY; + + epld->ps_opened = 1; + + return 0; +} + +static int elan_ps_release(struct inode *inode, struct file *file) +{ + struct elan_epl_data *epld = epl_data; + + LOG_FUN(); + + epld->ps_opened = 0; + + psensor_mode_suspend = 0; + + return 0; +} + +static int epl2182_read_chip_info(struct i2c_client *client, char *buf) +{ + if(NULL == buf) { + return -1; + } + if(NULL == client) { + *buf = 0; + return -2; + } + + sprintf(buf, "EPL2182"); + printk("[EPL2182] epl2182_read_chip_info %s\n",buf); + return 0; +} + +static long elan_ps_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int flag, err; + unsigned long buf; + char strbuf[256]; + struct elan_epl_data *epld = epl_data; + void __user *argp = (void __user *)arg; + + LOG_INFO("ps io ctrl cmd %d\n", _IOC_NR(cmd)); + //ioctl message handle must define by android sensor library (case by case) + switch(cmd) + { + case ELAN_EPL6800_IOCTL_GET_PFLAG: + flag = epld->enable_pflag; + if (copy_to_user(argp, &flag, sizeof(flag))) + return -EFAULT; + LOG_INFO("elan Proximity Sensor get pflag %d\n",flag); + break; + + case ELAN_EPL6800_IOCTL_ENABLE_PFLAG: + if (copy_from_user(&flag, argp, sizeof(flag))) + return -EFAULT; + LOG_INFO("elan Proximity Sensor set pflag %d\n",flag); + if(flag) { + flag = 1; + }else { + flag = 0; + } + epld->enable_pflag = flag; + elan_sensor_restart_work(); + break; + + case ELAN_EPL6800_IOCTL_GET_CHIPINFO: + err = epl2182_read_chip_info(this_client, strbuf); + if(err < 0) + return -EFAULT; + if(copy_to_user(argp, strbuf, strlen(strbuf)+1)) + return -EFAULT; + break; + + case ELAN_EPL6800_IOCTL_GET_LFLAG: + flag = epld->enable_lflag; + if (copy_to_user(argp, &flag, sizeof(flag))) + return -EFAULT; + LOG_INFO("elan ambient-light Sensor get lflag %d\n",flag); + break; + + case ELAN_EPL6800_IOCTL_ENABLE_LFLAG: + if (copy_from_user(&flag, argp, sizeof(flag))) + return -EFAULT; + LOG_INFO("elan ambient-light Sensor set lflag %d\n",flag); + if(flag) { + flag = 1; + }else { + flag = 0; + } + epld->enable_lflag = flag; + elan_sensor_restart_work(); + break; + + case ELAN_EPL6800_IOCTL_GETDATA: + buf = (unsigned long)gRawData.als_ch1_raw; + if(copy_to_user(argp, &buf , sizeof(buf))) + return -EFAULT; + LOG_INFO("elan als Sensor get data (%lu)\n",buf); + break; + + default: + LOG_ERR("invalid cmd %d\n", _IOC_NR(cmd)); + return -EINVAL; + } + + return 0; +} + +static unsigned int elan_ps_poll(struct file *fp, poll_table * wait) +{ + if(ps_data_changed) { + ps_data_changed = 0; + return POLLIN | POLLRDNORM; + } + poll_wait(fp, &ps_waitqueue, wait); + return 0; +} + + +static struct file_operations elan_ps_fops = +{ + .owner = THIS_MODULE, + .open = elan_ps_open, + .release = elan_ps_release, + .unlocked_ioctl = elan_ps_ioctl, + .poll = elan_ps_poll, +}; + +static struct miscdevice elan_ps_device = +{ + .minor = MISC_DYNAMIC_MINOR, + .name = EPL2182_PLS_DEVICE, + .fops = &elan_ps_fops +}; + +static int initial_sensor(struct elan_epl_data *epld) +{ + struct i2c_client *client = epld->client; + + int ret = 0; + + LOG_INFO("initial_sensor enter!\n"); + + ret = elan_sensor_I2C_Read(client); + + if(ret < 0) + return -EINVAL; + + elan_sensor_I2C_Write(client,REG_0,W_SINGLE_BYTE,0x02, EPL_S_SENSING_MODE); + elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_DISABLE); + set_psensor_intr_threshold(P_SENSOR_LTHD , P_SENSOR_HTHD); + + msleep(2); + + epld->enable_lflag = 0; + epld->enable_pflag = 0; + + return ret; +} +#if 0 //ices add +/*----------------------------------------------------------------------------*/ +static ssize_t light_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct elan_epl_data *epld = epl_data; + printk("%s: ALS_status=%d\n", __func__, epld->enable_lflag); + return sprintf(buf, "%d\n", epld->enable_lflag); +} + +static ssize_t light_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct elan_epl_data *epld = epl_data; + + LOG_INFO("light_enable_store: enable=%s \n", buf); + + if (sysfs_streq(buf, "1")) + epld->enable_lflag= 1; + else if (sysfs_streq(buf, "0")) + epld->enable_lflag= 0; + else { + pr_err("%s: invalid value %d\n", __func__, *buf); + return 0; + } + + elan_sensor_restart_work(); + return size; +} + +/*----------------------------------------------------------------------------*/ +static struct device_attribute dev_attr_light_enable = +__ATTR(enable, S_IRWXUGO, + light_enable_show, light_enable_store); + +static struct attribute *light_sysfs_attrs[] = { + &dev_attr_light_enable.attr, + NULL +}; + +static struct attribute_group light_attribute_group = { + .attrs = light_sysfs_attrs, +}; +/*----------------------------------------------------------------------------*/ + +static int lightsensor_setup(struct elan_epl_data *epld) +{ + int err = 0; + LOG_INFO("lightsensor_setup enter.\n"); + + epld->als_input_dev = input_allocate_device(); + if (!epld->als_input_dev) + { + LOG_ERR( "could not allocate ls input device\n"); + return -ENOMEM; + } + epld->als_input_dev->name = ElanALsensorName; + set_bit(EV_ABS, epld->als_input_dev->evbit); + input_set_abs_params(epld->als_input_dev, ABS_MISC, 0, 9, 0, 0); + + err = input_register_device(epld->als_input_dev); + if (err < 0) + { + LOG_ERR("can not register ls input device\n"); + goto err_free_ls_input_device; + } +#if 0 //ices add + err = misc_register(&elan_als_device); + if (err < 0) + { + LOG_ERR("can not register ls misc device\n"); + goto err_unregister_ls_input_device; + } +#endif //ices end + err = sysfs_create_group(&epld->als_input_dev->dev.kobj, &light_attribute_group); + + if (err) { + pr_err("%s: could not create sysfs group\n", __func__); + goto err_free_ls_input_device; + } + + return err; + +err_unregister_ls_input_device: + input_unregister_device(epld->als_input_dev); +err_free_ls_input_device: + input_free_device(epld->als_input_dev); + return err; +} +#endif +/*----------------------------------------------------------------------------*/ +static ssize_t proximity_enable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct elan_epl_data *epld = epl_data; + printk("%s: PS status=%d\n", __func__, epld->enable_pflag); + LOG_INFO("epl2182_setup_psensor enter.\n"); + return sprintf(buf, "%d\n", epld->enable_pflag); +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static ssize_t proximity_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + struct elan_epl_data *epld = epl_data; + + LOG_INFO("proximity_enable_store: enable=%s \n", buf); + if (sysfs_streq(buf, "1")) + epld->enable_pflag =1; + else if (sysfs_streq(buf, "0")) + epld->enable_pflag =0; + else { + pr_err("%s: invalid value %d\n", __func__, *buf); + return 0; + } + + elan_sensor_restart_work(); + return size; +} +/*----------------------------------------------------------------------------*/ +static struct device_attribute dev_attr_ps_enable = +__ATTR(enable, S_IRWXUGO, + proximity_enable_show, proximity_enable_store); + +static struct attribute *proximity_sysfs_attrs[] = { + &dev_attr_ps_enable.attr, + NULL +}; + +static struct attribute_group proximity_attribute_group = { + .attrs = proximity_sysfs_attrs, +}; +/*----------------------------------------------------------------------------*/ + +static int psensor_setup(struct elan_epl_data *epld) +{ + int err = 0; + LOG_INFO("psensor_setup enter.\n"); + + epld->ps_input_dev = input_allocate_device(); + if (!epld->ps_input_dev) + { + LOG_ERR("could not allocate ps input device\n"); + return -ENOMEM; + } + epld->ps_input_dev->name = ElanPsensorName; + + set_bit(EV_ABS, epld->ps_input_dev->evbit); + input_set_abs_params(epld->ps_input_dev, ABS_DISTANCE, 0, 1, 0, 0); +#if 1 + set_bit(EV_ABS, epld->ps_input_dev->evbit); + input_set_abs_params(epld->ps_input_dev, ABS_MISC, 0, 9, 0, 0); +#endif + + err = input_register_device(epld->ps_input_dev); + if (err < 0) + { + LOG_ERR("could not register ps input device\n"); + goto err_free_ps_input_device; + } + + err = misc_register(&elan_ps_device); + if (err < 0) + { + LOG_ERR("could not register ps misc device\n"); + goto err_unregister_ps_input_device; + } + + err = sysfs_create_group(&epld->ps_input_dev->dev.kobj, &proximity_attribute_group); + if (err) { + pr_err("%s: PS could not create sysfs group\n", __func__); + goto err_free_ps_input_device; + } + + return err; + +err_unregister_ps_input_device: + input_unregister_device(epld->ps_input_dev); +err_free_ps_input_device: + input_free_device(epld->ps_input_dev); + return err; +} + +#if PS_INTERRUPT_MODE +static int setup_interrupt(struct elan_epl_data *epld) +{ + struct i2c_client *client = epld->client; + struct elan_epl_platform_data *pdata = client->dev.platform_data; + + int err = 0; + msleep(5); +#if 1 + err = gpio_request(pdata->irq_gpio_number, "Elan EPL IRQ"); + if (err) + { + LOG_ERR("gpio pin request fail (%d)\n", err); + goto initial_fail; + } + else + { + + gpio_direction_input(pdata->irq_gpio_number); + + /*get irq*/ + client->irq = gpio_to_irq(pdata->irq_gpio_number); + epld->irq = client->irq; + + printk("IRQ number is %d\n", client->irq); + + } +#endif + err = request_irq(epld->irq,elan_sensor_irq_handler, IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND , + client->dev.driver->name, epld); + if(err <0) + { + LOG_ERR("request irq pin %d fail for gpio\n",err); + goto fail_free_intr_pin; + } + + return err; + +initial_fail: +fail_free_intr_pin: + gpio_free(epld->intr_pin); + return err; +} +#endif + +#ifdef CONFIG_SUSPEND +static int elan_sensor_suspend(struct i2c_client *client, pm_message_t mesg) +{ + LOG_FUN(); + + if(epl_data->enable_pflag==0 ) + { + elan_sensor_I2C_Write(client,REG_7, W_SINGLE_BYTE, 0x02, EPL_C_P_DOWN); + cancel_delayed_work(&polling_work); + } + return 0; +} + +static int elan_sensor_resume(struct i2c_client *client) +{ + struct elan_epl_data *epld = epl_data; + LOG_FUN(); + + if(epld->enable_pflag | epld->enable_lflag) + { + elan_sensor_I2C_Write(client,REG_7, W_SINGLE_BYTE, 0x02, EPL_C_P_UP); + } + + if(epld->enable_pflag) + { + elan_sensor_restart_work(); + } + return 0; +} +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void elan_sensor_early_suspend(struct early_suspend *h) +{ + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + LOG_FUN(); + + if( epld->enable_pflag==0) + { + elan_sensor_I2C_Write(client,REG_7, W_SINGLE_BYTE, 0x02, EPL_C_P_DOWN); + cancel_delayed_work(&polling_work); + } + + psensor_mode_suspend = 1; +} + +static void elan_sensor_late_resume(struct early_suspend *h) +{ + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + + LOG_FUN(); + + if(epld->enable_pflag | epld->enable_lflag) + { + elan_sensor_I2C_Write(client,REG_7, W_SINGLE_BYTE, 0x02, EPL_C_P_UP); + } + + if(epld->enable_pflag || epld->enable_lflag) + { + elan_sensor_restart_work(); + } + psensor_mode_suspend = 0; +} +#endif + +static int elan_sensor_probe(struct i2c_client *client,const struct i2c_device_id *id) +{ + int err = 0; + int chip_id = 0; + struct elan_epl_data *epld ; + struct elan_epl_platform_data *pdata = client->dev.platform_data; + //static struct platform_device *sensor_dev; + struct device_node *np = client->dev.of_node; + LOG_INFO("elan sensor probe enter.\n"); +#ifdef CONFIG_OF + if (np && !pdata){ + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, "Could not allocate struct elan_epl_platform_data"); + goto exit_allocate_pdata_failed; + } + pdata->irq_gpio_number = of_get_gpio(np, 0); + if(pdata->irq_gpio_number < 0){ + dev_err(&client->dev, "fail to get irq_gpio_number\n"); + kfree(pdata); + goto exit_irq_gpio_read_fail; + } + client->dev.platform_data = pdata; + } +#endif + + epld = kzalloc(sizeof(struct elan_epl_data), GFP_KERNEL); + if (!epld) { + err = -ENOMEM; + LOG_ERR("kzalloc elan_epl_data failed!\n"); + goto exit_kzalloc_epld_failed; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + { + dev_err(&client->dev,"No supported i2c func what we need?!!\n"); + err = -ENOTSUPP; + goto i2c_fail; + } + chip_id = i2c_smbus_read_byte_data(client, 0x00); + if (chip_id < 0) + { + dev_err(&client->dev,"read chip id REG 0x00 failed\n"); + err = -ENODEV; + goto exit_read_chipid_failed; + } + LOG_INFO("chip id REG 0x00 value = %8x\n", i2c_smbus_read_byte_data(client, 0x00)); + LOG_INFO("chip id REG 0x01 value = %8x\n", i2c_smbus_read_byte_data(client, 0x08)); + LOG_INFO("chip id REG 0x02 value = %8x\n", i2c_smbus_read_byte_data(client, 0x10)); + LOG_INFO("chip id REG 0x03 value = %8x\n", i2c_smbus_read_byte_data(client, 0x18)); + LOG_INFO("chip id REG 0x04 value = %8x\n", i2c_smbus_read_byte_data(client, 0x20)); + LOG_INFO("chip id REG 0x05 value = %8x\n", i2c_smbus_read_byte_data(client, 0x28)); + LOG_INFO("chip id REG 0x06 value = %8x\n", i2c_smbus_read_byte_data(client, 0x30)); + LOG_INFO("chip id REG 0x07 value = %8x\n", i2c_smbus_read_byte_data(client, 0x38)); + LOG_INFO("chip id REG 0x09 value = %8x\n", i2c_smbus_read_byte_data(client, 0x48)); + LOG_INFO("chip id REG 0x0D value = %8x\n", i2c_smbus_read_byte_data(client, 0x68)); + LOG_INFO("chip id REG 0x0E value = %8x\n", i2c_smbus_read_byte_data(client, 0x70)); + LOG_INFO("chip id REG 0x0F value = %8x\n", i2c_smbus_read_byte_data(client, 0x71)); + LOG_INFO("chip id REG 0x10 value = %8x\n", i2c_smbus_read_byte_data(client, 0x80)); + LOG_INFO("chip id REG 0x11 value = %8x\n", i2c_smbus_read_byte_data(client, 0x88)); + LOG_INFO("chip id REG 0x13 value = %8x\n", i2c_smbus_read_byte_data(client, 0x98)); + + epld->client = client; + this_client = client; + epld->irq = client->irq; + i2c_set_clientdata(client, epld); + + epl_data = epld; + + epld->epl_wq = create_singlethread_workqueue(EPL2182_PLS_DEVICE); + if (!epld->epl_wq) + { + LOG_ERR("can't create workqueue\n"); + err = -ENOMEM; + goto err_create_singlethread_workqueue; + } +#if 0 //ices add + err = lightsensor_setup(epld); + if (err < 0) + { + LOG_ERR("lightsensor_setup error!!\n"); + goto err_lightsensor_setup; + } +#endif + err = psensor_setup(epld); + if (err < 0) + { + LOG_ERR("psensor_setup error!!\n"); + goto err_psensor_setup; + } + + err = initial_sensor(epld); + if (err < 0) + { + LOG_ERR("fail to initial sensor (%d)\n", err); + goto err_sensor_setup; + } + +#if PS_INTERRUPT_MODE + err = setup_interrupt(epld); + if (err < 0) + { + LOG_ERR("setup error!\n"); + goto err_sensor_setup; + } +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND + epld->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + epld->early_suspend.suspend = elan_sensor_early_suspend; + epld->early_suspend.resume = elan_sensor_late_resume; + register_early_suspend(&epld->early_suspend); +#endif + +#if 0 + wake_lock_init(&g_ps_wlock, WAKE_LOCK_SUSPEND, "ps_wakelock"); + + sensor_dev = platform_device_register_simple("elan_alsps", -1, NULL, 0); + if (IS_ERR(sensor_dev)) + { + printk ("sensor_dev_init: error\n"); + goto err_fail; + } + + err = sysfs_create_group(&sensor_dev->dev.kobj, &ets_attr_group); + if (err !=0) + { + dev_err(&client->dev,"%s:create sysfs group error", __func__); + goto err_fail; + } +#endif + + LOG_INFO("sensor probe success.\n"); + + return err; + +err_fail: + //input_unregister_device(epld->als_input_dev); + input_unregister_device(epld->ps_input_dev); + //input_free_device(epld->als_input_dev); + input_free_device(epld->ps_input_dev); +//err_lightsensor_setup: +err_psensor_setup: +err_sensor_setup: + destroy_workqueue(epld->epl_wq); + misc_deregister(&elan_ps_device); +// misc_deregister(&elan_als_device); //ices add +err_create_singlethread_workqueue: +exit_read_chipid_failed: +i2c_fail: +//err_platform_data_null: + kfree(epld); +exit_kzalloc_epld_failed: +#ifdef CONFIG_OF +exit_irq_gpio_read_fail: +exit_allocate_pdata_failed: +#endif + return err; +} + +static int elan_sensor_remove(struct i2c_client *client) +{ + struct elan_epl_data *epld = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "%s: enter.\n", __func__); + + unregister_early_suspend(&epld->early_suspend); + //input_unregister_device(epld->als_input_dev); + input_unregister_device(epld->ps_input_dev); + //input_free_device(epld->als_input_dev); + input_free_device(epld->ps_input_dev); + misc_deregister(&elan_ps_device); +// misc_deregister(&elan_als_device); //ices add + free_irq(epld->irq,epld); + destroy_workqueue(epld->epl_wq); + kfree(epld); + return 0; +} + +static const struct i2c_device_id elan_sensor_id[] = +{ + { EPL2182_PLS_DEVICE, 0 }, + { } +}; + +static const struct of_device_id epl2182_of_match[] = { + { .compatible = "ELAN,epl2182_pls", }, + {} +}; +MODULE_DEVICE_TABLE(of, epl2182_of_match); + +static struct i2c_driver elan_sensor_driver = +{ + .probe = elan_sensor_probe, + .remove = elan_sensor_remove, + .id_table = elan_sensor_id, + .driver = { + .name = EPL2182_PLS_DEVICE, + .owner = THIS_MODULE, + .of_match_table = epl2182_of_match, + }, +#ifdef CONFIG_SUSPEND + .suspend = elan_sensor_suspend, + .resume = elan_sensor_resume, +#endif +}; + +static int __init elan_sensor_init(void) +{ + return i2c_add_driver(&elan_sensor_driver); +} + +static void __exit elan_sensor_exit(void) +{ + i2c_del_driver(&elan_sensor_driver); +} + +module_init(elan_sensor_init); +module_exit(elan_sensor_exit); + +MODULE_AUTHOR("Renato Pan <renato.pan@eminent-tek.com>"); +MODULE_DESCRIPTION("ELAN epl2182 driver"); +MODULE_LICENSE("GPL"); + |
