| /****************************************************************************** |
| * isl29044a.c - Linux kernel module for Intersil isl29044a ambient light sensor |
| * and proximity sensor |
| * |
| * Copyright 2008-2012 Intersil Inc.. |
| * |
| * DESCRIPTION: |
| * - This is the linux driver for isl29044a. |
| * Kernel version 3.0.8 |
| * |
| * modification history |
| * -------------------- |
| * v1.0 2010/04/06, Shouxian Chen(Simon Chen) create this file |
| * v1.1 2012/06/05, Shouxian Chen(Simon Chen) modified for Android 4.0 and |
| * linux 3.0.8 |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| ******************************************************************************/ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/slab.h> |
| #include <linux/i2c.h> |
| #include <linux/init.h> |
| #include <linux/input.h> |
| #include <linux/idr.h> |
| #include <linux/fs.h> |
| #include <linux/timer.h> |
| #include <linux/sched.h> |
| #include <linux/seq_file.h> |
| #include <linux/io.h> |
| #include <linux/ioctl.h> |
| #include <linux/types.h> |
| #include <linux/uaccess.h> |
| #include <linux/workqueue.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/platform_device.h> |
| #include <linux/of_gpio.h> |
| #include <linux/miscdevice.h> |
| #include <linux/device.h> |
| #include <linux/cdev.h> |
| #include <linux/delay.h> |
| #include <linux/sensors.h> |
| |
| /* chip config struct */ |
| struct isl29044a_cfg_t { |
| u8 als_range; /* als range, 0: 125 Lux, 1: 250, 2:2000, 3:4000 */ |
| u8 ps_lt; /* ps low limit */ |
| u8 ps_ht; /* ps high limit */ |
| /* led driver current, 0:31.25mA, 1:62.5mA, 2:125mA, 3:250mA*/ |
| u8 ps_led_drv_cur; |
| u8 ps_offset; /* ps offset comp */ |
| u8 als_ir_comp; /* als ir comp */ |
| int glass_factor; /* glass factor for als, percent */ |
| }; |
| |
| #define ISL29044A_ADDR 0x44 |
| #define DEVICE_NAME "isl29044a" |
| #define DRIVER_VERSION "1.3" |
| |
| #define ALS_EN_MSK (1 << 0) |
| #define PS_EN_MSK (1 << 1) |
| |
| #define PS_POLL_TIME 100 /* unit is ms */ |
| |
| /* POWER SUPPLY VOLTAGE RANGE */ |
| #define ISL_VDD_MIN_UV 2000000 |
| #define ISL_VDD_MAX_UV 3300000 |
| #define ISL_VIO_MIN_UV 1750000 |
| #define ISL_VIO_MAX_UV 1950000 |
| #define ISL29044A_PS_MAX_DELAY 100 |
| #define ISL29044A_ALS_MAX_DELAY 1000 |
| #define PROX_THRESHOLD_DELTA_LO 15 |
| #define PROX_THRESHOLD_DELTA_HI 15 |
| |
| /* Each client has this additional data */ |
| struct isl29044a_data_t { |
| struct i2c_client* client; |
| struct isl29044a_cfg_t *cfg; |
| struct sensors_classdev als_cdev; |
| struct sensors_classdev ps_cdev; |
| atomic_t als_pwr_status; |
| atomic_t ps_pwr_status; |
| u8 ps_led_drv_cur; /* led driver current, 0: 110mA, 1: 220mA */ |
| atomic_t als_range; /* als range, 0: 125 Lux, 1: 2000Lux */ |
| u8 als_mode; /* als mode, 0: Visible light, 1: IR light */ |
| u8 ps_lt; /* ps low limit */ |
| u8 ps_ht; /* ps high limit */ |
| atomic_t poll_delay; /* poll delay set by hal */ |
| atomic_t als_delay; |
| atomic_t ps_delay; |
| atomic_t show_als_raw; /* show als raw data flag, used for debug */ |
| atomic_t show_ps_raw; /* show als raw data flag, used for debug */ |
| struct timer_list als_timer; /* als poll timer */ |
| struct timer_list ps_timer; /* ps poll timer */ |
| struct work_struct als_work; |
| struct work_struct ps_work; |
| struct workqueue_struct *als_wq; |
| struct workqueue_struct *ps_wq; |
| struct input_dev *als_input_dev; |
| struct input_dev *ps_input_dev; |
| int last_ps; |
| u8 als_range_using; /* the als range using now */ |
| u8 als_pwr_before_suspend; |
| u8 ps_pwr_before_suspend; |
| bool power_enabled; |
| struct regulator *vdd; |
| struct regulator *vio; |
| atomic_t show_pdata; |
| u8 ps_filter_cnt; |
| int last_lux; |
| int last_ps_raw; |
| |
| int als_chg_range_delay_cnt; |
| u8 is_do_factory_calib; |
| |
| struct cdev cdev; |
| }; |
| |
| /* Do not scan isl29044a automatic */ |
| static const unsigned short normal_i2c[] = {ISL29044A_ADDR, I2C_CLIENT_END }; |
| static struct sensors_classdev sensors_light_cdev = { |
| .name = "isl29044a-light", |
| .vendor = "intersil", |
| .version = 1, |
| .handle = SENSORS_LIGHT_HANDLE, |
| .type = SENSOR_TYPE_LIGHT, |
| .max_range = "30000", |
| .resolution = "0.0125", |
| .sensor_power = "0.20", |
| .min_delay = 100000, |
| .fifo_reserved_event_count = 0, |
| .fifo_max_event_count = 0, |
| .enabled = 0, |
| .delay_msec = 100, |
| .sensors_enable = NULL, |
| .sensors_poll_delay = NULL, |
| }; |
| |
| static struct sensors_classdev sensors_proximity_cdev = { |
| .name = "isl29044a-proximity", |
| .vendor = "intersil", |
| .version = 1, |
| .handle = SENSORS_PROXIMITY_HANDLE, |
| .type = SENSOR_TYPE_PROXIMITY, |
| .max_range = "5", |
| .resolution = "5.0", |
| .sensor_power = "3", |
| .min_delay = 1000, |
| .fifo_reserved_event_count = 0, |
| .fifo_max_event_count = 0, |
| .enabled = 0, |
| .delay_msec = 100, |
| .sensors_enable = NULL, |
| .sensors_poll_delay = NULL, |
| }; |
| |
| static void do_als_timer(unsigned long arg) |
| { |
| struct isl29044a_data_t *dev_dat; |
| |
| dev_dat = (struct isl29044a_data_t *)arg; |
| |
| if (atomic_read(&dev_dat->als_pwr_status) == 0) |
| return ; |
| |
| /* start a work queue, I cannot do i2c operation in timer context for |
| this context is atomic and i2c function maybe sleep. */ |
| queue_work(dev_dat->als_wq, &dev_dat->als_work); |
| } |
| |
| static void do_ps_timer(unsigned long arg) |
| { |
| struct isl29044a_data_t *dev_dat; |
| |
| dev_dat = (struct isl29044a_data_t *)arg; |
| |
| if (atomic_read(&dev_dat->ps_pwr_status) == 0) |
| return ; |
| |
| /* start a work queue, I cannot do i2c operation in timer context for |
| this context is atomic and i2c function maybe sleep. */ |
| queue_work(dev_dat->ps_wq, &dev_dat->ps_work); |
| } |
| |
| static void do_als_work(struct work_struct *work) |
| { |
| struct isl29044a_data_t *dev_dat; |
| int ret; |
| static int als_dat; |
| u8 show_raw_dat; |
| int lux; |
| u8 als_range; |
| |
| dev_dat = container_of(work, struct isl29044a_data_t, als_work); |
| |
| show_raw_dat = atomic_read(&dev_dat->show_als_raw); |
| |
| als_range = dev_dat->als_range_using; |
| |
| ret = i2c_smbus_read_byte_data(dev_dat->client, 0x09); |
| if (ret < 0) |
| goto err_rd; |
| |
| als_dat = (u8)ret; |
| |
| ret = i2c_smbus_read_byte_data(dev_dat->client, 0x0a); |
| if (ret < 0) |
| goto err_rd; |
| |
| als_dat = als_dat + ( ((u8)ret & 0x0f) << 8 ); |
| |
| if (als_range) |
| lux = (als_dat * 3200) / 4096; |
| else |
| lux = (als_dat * 200) / 4096; |
| |
| input_report_abs(dev_dat->als_input_dev, ABS_MISC, lux); |
| input_sync(dev_dat->als_input_dev); |
| if (show_raw_dat) |
| dev_info(&dev_dat->als_input_dev->dev, |
| "now als raw data is = %d, LUX = %d\n", |
| als_dat, lux); |
| |
| /* restart timer */ |
| if (atomic_read(&dev_dat->als_pwr_status) == 0) |
| return ; |
| |
| dev_dat->als_timer.expires = jiffies + |
| (HZ * atomic_read(&dev_dat->poll_delay)) / 1000; |
| add_timer(&dev_dat->als_timer); |
| |
| return ; |
| |
| err_rd: |
| dev_err(&dev_dat->als_input_dev->dev, |
| "Read als sensor error, ret = %d\n", ret); |
| return ; |
| } |
| |
| static void do_ps_work(struct work_struct *work) |
| { |
| struct isl29044a_data_t *dev_dat; |
| int last_ps; |
| int ret; |
| u8 show_raw_dat; |
| |
| dev_dat = container_of(work, struct isl29044a_data_t, ps_work); |
| |
| show_raw_dat = atomic_read(&dev_dat->show_ps_raw); |
| |
| ret = i2c_smbus_read_byte_data(dev_dat->client, 0x02); |
| if (ret < 0) |
| goto err_rd; |
| last_ps = dev_dat->last_ps; |
| dev_dat->last_ps = (ret & 0x80) ? 0 : 1; |
| |
| |
| ret = i2c_smbus_read_byte_data(dev_dat->client, 0x08); |
| if (ret < 0) |
| goto err_rd; |
| |
| atomic_set(&dev_dat->show_pdata, ret); |
| |
| if (last_ps != dev_dat->last_ps) { |
| input_report_abs(dev_dat->ps_input_dev, ABS_DISTANCE, |
| dev_dat->last_ps); |
| input_sync(dev_dat->ps_input_dev); |
| if (show_raw_dat) |
| dev_info(&dev_dat->ps_input_dev->dev, |
| "ps status changed, now = %d\n", |
| dev_dat->last_ps); |
| } |
| |
| /* restart timer */ |
| if (atomic_read(&dev_dat->ps_pwr_status) == 0) |
| return ; |
| dev_dat->ps_timer.expires = jiffies + (HZ * PS_POLL_TIME) / 1000; |
| add_timer(&dev_dat->ps_timer); |
| |
| return ; |
| |
| err_rd: |
| dev_err(&dev_dat->ps_input_dev->dev, "Read ps sensor error, ret = %d\n", |
| ret); |
| return ; |
| } |
| |
| /* enable to run als */ |
| static int set_sensor_reg(struct isl29044a_data_t *dev_dat) |
| { |
| u8 reg_dat[5]; |
| int i, ret; |
| dev_dbg(&dev_dat->client->dev, "set_sensor_reg()\n"); |
| reg_dat[2] = 0x22; |
| reg_dat[3] = dev_dat->ps_lt; |
| reg_dat[4] = dev_dat->ps_ht; |
| |
| reg_dat[1] = 0x50; /* set ps sleep time to 50ms */ |
| |
| if (atomic_read(&dev_dat->als_pwr_status)) |
| reg_dat[1] |= 0x04; |
| if (atomic_read(&dev_dat->ps_pwr_status)) |
| reg_dat[1] |= 0x80; |
| |
| if (dev_dat->als_mode) |
| reg_dat[1] |= 0x01; |
| if (atomic_read(&dev_dat->als_range)) |
| reg_dat[1] |= 0x02; |
| if (dev_dat->ps_led_drv_cur) |
| reg_dat[1] |= 0x08; |
| |
| for (i = 2 ; i <= 4; i++) { |
| ret = i2c_smbus_write_byte_data(dev_dat->client, i, reg_dat[i]); |
| if (ret < 0) { |
| pr_err("set_sensor_reg: write i2c error!!! i2c address is: %x", |
| dev_dat->client->addr); |
| return ret; |
| } |
| } |
| |
| ret = i2c_smbus_write_byte_data(dev_dat->client, 0x01, reg_dat[1]); |
| if (ret < 0) { |
| pr_err("set_sensor_reg: write i2c command 0x01 error!!!"); |
| return ret; |
| } |
| return 0; |
| } |
| |
| /* set power status */ |
| static int set_als_pwr_st(u8 state, struct isl29044a_data_t *dat) |
| { |
| int ret = 0; |
| |
| if (state) { |
| if (atomic_read(&dat->als_pwr_status)) |
| return ret; |
| atomic_set(&dat->als_pwr_status, 1); |
| ret = set_sensor_reg(dat); |
| if (ret < 0) { |
| dev_err(&dat->als_input_dev->dev, |
| "set light sensor reg error, ret = %d\n", |
| ret); |
| atomic_set(&dat->als_pwr_status, 0); |
| return ret; |
| } |
| |
| /* start timer */ |
| dat->als_timer.function = &do_als_timer; |
| dat->als_timer.data = (unsigned long)dat; |
| dat->als_timer.expires = jiffies + |
| (HZ * atomic_read(&dat->poll_delay)) / 1000; |
| |
| dat->als_range_using = atomic_read(&dat->als_range); |
| add_timer(&dat->als_timer); |
| } else { |
| if (atomic_read(&dat->als_pwr_status) == 0) |
| return ret; |
| atomic_set(&dat->als_pwr_status, 0); |
| ret = set_sensor_reg(dat); |
| |
| /* delete timer */ |
| del_timer_sync(&dat->als_timer); |
| } |
| |
| return ret; |
| } |
| |
| static int set_ps_pwr_st(u8 state, struct isl29044a_data_t *dat) |
| { |
| int ret = 0; |
| |
| if (state) { |
| if (atomic_read(&dat->ps_pwr_status)) |
| return ret; |
| atomic_set(&dat->ps_pwr_status, 1); |
| |
| dat->last_ps = -1; |
| ret = set_sensor_reg(dat); |
| if (ret < 0) { |
| dev_err(&dat->ps_input_dev->dev, |
| "set proximity sensor reg error, ret = %d\n", |
| ret); |
| atomic_set(&dat->ps_pwr_status, 0); |
| return ret; |
| } |
| |
| /* start timer */ |
| dat->ps_timer.function = &do_ps_timer; |
| dat->ps_timer.data = (unsigned long)dat; |
| dat->ps_timer.expires = jiffies + (HZ * PS_POLL_TIME) / 1000; |
| add_timer(&dat->ps_timer); |
| } else { |
| if (atomic_read(&dat->ps_pwr_status) == 0) |
| return ret; |
| atomic_set(&dat->ps_pwr_status, 0); |
| |
| ret = set_sensor_reg(dat); |
| |
| /* delete timer */ |
| del_timer_sync(&dat->ps_timer); |
| } |
| |
| return ret; |
| } |
| |
| /* device attribute */ |
| /* enable als attribute */ |
| static ssize_t show_enable_als_sensor(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct isl29044a_data_t *dat; |
| u8 pwr_status; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| |
| pwr_status = atomic_read(&dat->als_pwr_status); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", pwr_status); |
| } |
| static ssize_t store_enable_als_sensor(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct isl29044a_data_t *dat; |
| ssize_t ret; |
| unsigned long val; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| |
| val = kstrtoul(buf, 10, NULL); |
| ret = set_als_pwr_st(val, dat); |
| |
| if (ret == 0) |
| ret = count; |
| return ret; |
| } |
| static DEVICE_ATTR(enable_als_sensor, S_IRUGO|S_IWUSR|S_IWGRP, |
| show_enable_als_sensor, store_enable_als_sensor); |
| |
| /* enable ps attribute */ |
| static ssize_t show_enable_ps_sensor(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct isl29044a_data_t *dat; |
| u8 pwr_status; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| |
| pwr_status = atomic_read(&dat->ps_pwr_status); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", pwr_status); |
| } |
| static ssize_t store_enable_ps_sensor(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct isl29044a_data_t *dat; |
| ssize_t ret; |
| unsigned long val; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| |
| val = kstrtoul(buf, 10, NULL); |
| ret = set_ps_pwr_st(val, dat); |
| |
| if (ret == 0) |
| ret = count; |
| return ret; |
| } |
| static DEVICE_ATTR(enable_ps_sensor, S_IRUGO|S_IWUSR|S_IWGRP, |
| show_enable_ps_sensor, store_enable_ps_sensor); |
| |
| /* ps led driver current attribute */ |
| static ssize_t show_ps_led_drv(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct isl29044a_data_t *dat; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| return snprintf(buf, PAGE_SIZE, "%d\n", dat->ps_led_drv_cur); |
| } |
| static ssize_t store_ps_led_drv(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct isl29044a_data_t *dat; |
| int val; |
| |
| if (sscanf(buf, "%d", &val) != 1) |
| return -EINVAL; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| if (val) |
| dat->ps_led_drv_cur = 1; |
| else |
| dat->ps_led_drv_cur = 0; |
| |
| return count; |
| } |
| static DEVICE_ATTR(ps_led_driver_current, S_IRUGO|S_IWUSR|S_IWGRP, |
| show_ps_led_drv, store_ps_led_drv); |
| |
| /* als range attribute */ |
| static ssize_t show_als_range(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct isl29044a_data_t *dat; |
| u8 range; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| range = atomic_read(&dat->als_range); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", range); |
| } |
| static ssize_t store_als_range(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct isl29044a_data_t *dat; |
| int val; |
| |
| if (sscanf(buf, "%d", &val) != 1) |
| return -EINVAL; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| |
| if (val) |
| atomic_set(&dat->als_range, 1); |
| else |
| atomic_set(&dat->als_range, 0); |
| |
| return count; |
| } |
| static DEVICE_ATTR(als_range, S_IRUGO|S_IWUSR|S_IWGRP, show_als_range, |
| store_als_range); |
| |
| /* als mode attribute */ |
| static ssize_t show_als_mode(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct isl29044a_data_t *dat; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| return snprintf(buf, PAGE_SIZE, "%d\n", dat->als_mode); |
| } |
| static ssize_t store_als_mode(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct isl29044a_data_t *dat; |
| int val; |
| |
| if (sscanf(buf, "%d", &val) != 1) |
| return -EINVAL; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| if (val) |
| dat->als_mode = 1; |
| else |
| dat->als_mode = 0; |
| |
| return count; |
| } |
| static DEVICE_ATTR(als_mode, S_IRUGO|S_IWUSR|S_IWGRP, show_als_mode, |
| store_als_mode); |
| |
| /* ps limit range attribute */ |
| static ssize_t show_ps_limit(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct isl29044a_data_t *dat; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| return snprintf(buf, PAGE_SIZE, "%d %d\n", dat->ps_lt, dat->ps_ht); |
| } |
| static ssize_t store_ps_limit(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct isl29044a_data_t *dat; |
| int lt, ht; |
| |
| if (sscanf(buf, "%d %d", <, &ht) != 2) |
| return -EINVAL; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| |
| if (lt > 255) |
| dat->ps_lt = 255; |
| else if (lt < 0) |
| dat->ps_lt = 0; |
| else |
| dat->ps_lt = lt; |
| |
| if (ht > 255) |
| dat->ps_ht = 255; |
| else if (ht < 0) |
| dat->ps_ht = 0; |
| else |
| dat->ps_ht = ht; |
| |
| return count; |
| } |
| static DEVICE_ATTR(ps_limit, S_IRUGO|S_IWUSR|S_IWGRP, show_ps_limit, |
| store_ps_limit); |
| |
| /* poll delay attribute */ |
| static ssize_t show_poll_delay (struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct isl29044a_data_t *dat; |
| int delay; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| delay = atomic_read(&dat->poll_delay); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", delay); |
| } |
| static ssize_t store_poll_delay (struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct isl29044a_data_t *dat; |
| int64_t ns; |
| int delay; |
| |
| if (sscanf(buf, "%lld", &ns) != 1) |
| return -EINVAL; |
| delay = (int)ns/1000/1000; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| |
| if (delay < 120) |
| atomic_set(&dat->poll_delay, 120); |
| else if (delay > 65535) |
| atomic_set(&dat->poll_delay, 65535); |
| else |
| atomic_set(&dat->poll_delay, delay); |
| |
| return count; |
| } |
| static DEVICE_ATTR(poll_delay, S_IRUGO|S_IWUSR|S_IWGRP, show_poll_delay, |
| store_poll_delay); |
| |
| /* show als raw data attribute */ |
| static ssize_t show_als_show_raw (struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct isl29044a_data_t *dat; |
| u8 flag; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| flag = atomic_read(&dat->show_als_raw); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", flag); |
| } |
| |
| static ssize_t store_als_show_raw (struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct isl29044a_data_t *dat; |
| int flag; |
| |
| if (sscanf(buf, "%d", &flag) != 1) |
| return -EINVAL; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| |
| if (flag == 0) |
| atomic_set(&dat->show_als_raw, (u8)0); |
| else |
| atomic_set(&dat->show_als_raw, (u8)1); |
| |
| return count; |
| } |
| static DEVICE_ATTR(als_show_raw, S_IRUGO|S_IWUSR|S_IWGRP, show_als_show_raw, |
| store_als_show_raw); |
| |
| /* show ps raw data attribute */ |
| static ssize_t show_ps_show_raw (struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct isl29044a_data_t *dat; |
| u8 flag; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| |
| flag = atomic_read(&dat->show_pdata); |
| |
| return snprintf(buf, PAGE_SIZE, "%d\n", flag); |
| } |
| |
| static ssize_t store_ps_show_raw (struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct isl29044a_data_t *dat; |
| int flag; |
| |
| if (sscanf(buf, "%d", &flag) != 1) |
| return -EINVAL; |
| |
| dat = (struct isl29044a_data_t *)dev->platform_data; |
| |
| if (flag == 0) |
| atomic_set(&dat->show_ps_raw, 0); |
| else |
| atomic_set(&dat->show_ps_raw, 1); |
| |
| return count; |
| } |
| static DEVICE_ATTR(ps_show_raw, S_IRUGO|S_IWUSR|S_IWGRP, show_ps_show_raw, |
| store_ps_show_raw); |
| |
| static int isl29044a_als_set_enable(struct sensors_classdev *sensors_cdev, |
| unsigned int enable) |
| { |
| int ret = 0; |
| |
| struct isl29044a_data_t *dat = container_of(sensors_cdev, |
| struct isl29044a_data_t, als_cdev); |
| if ((enable != 0) && (enable != 1)) { |
| pr_err("%s: invalid value(%d)\n", __func__, enable); |
| return -EINVAL; |
| } |
| |
| ret = set_als_pwr_st(enable, dat); |
| |
| return ret; |
| |
| }; |
| |
| static int isl29044a_ps_set_enable(struct sensors_classdev *sensors_cdev, |
| unsigned int enable) |
| { |
| int ret = 0; |
| |
| struct isl29044a_data_t *dat = container_of(sensors_cdev, |
| struct isl29044a_data_t, ps_cdev); |
| if ((enable != 0) && (enable != 1)) { |
| pr_err("%s: invalid value(%d)\n", __func__, enable); |
| return -EINVAL; |
| } |
| |
| ret = set_ps_pwr_st(enable, dat); |
| |
| return ret; |
| }; |
| |
| static int isl29044a_als_poll_delay_enable(struct sensors_classdev *sensors_cdev, |
| unsigned int delay_ms) |
| { |
| struct isl29044a_data_t *dat = container_of(sensors_cdev, |
| struct isl29044a_data_t, als_cdev); |
| |
| if (delay_ms > ISL29044A_ALS_MAX_DELAY) |
| delay_ms = ISL29044A_ALS_MAX_DELAY; |
| atomic_set(&dat->als_delay, (unsigned int) delay_ms); |
| |
| return 0; |
| } |
| |
| static int isl29044a_ps_poll_delay_enable(struct sensors_classdev *sensors_cdev, |
| unsigned int delay_ms) |
| { |
| struct isl29044a_data_t *dat = container_of(sensors_cdev, |
| struct isl29044a_data_t, ps_cdev); |
| |
| if (delay_ms > ISL29044A_PS_MAX_DELAY) |
| delay_ms = ISL29044A_PS_MAX_DELAY; |
| atomic_set(&dat->ps_delay, (unsigned int) delay_ms); |
| |
| return 0; |
| } |
| |
| static struct attribute *als_attr[] = { |
| &dev_attr_enable_als_sensor.attr, |
| &dev_attr_als_range.attr, |
| &dev_attr_als_mode.attr, |
| &dev_attr_poll_delay.attr, |
| &dev_attr_als_show_raw.attr, |
| NULL |
| }; |
| |
| static struct attribute_group als_attr_grp = { |
| .name = "light sensor", |
| .attrs = als_attr |
| }; |
| |
| static struct attribute *ps_attr[] = { |
| &dev_attr_enable_ps_sensor.attr, |
| &dev_attr_ps_led_driver_current.attr, |
| &dev_attr_ps_limit.attr, |
| &dev_attr_ps_show_raw.attr, |
| NULL |
| }; |
| |
| static struct attribute_group ps_attr_grp = { |
| .name = "proximity sensor", |
| .attrs = ps_attr |
| }; |
| |
| /* initial and register a input device for sensor */ |
| static int init_input_dev(struct isl29044a_data_t *dev_dat) |
| { |
| int err; |
| struct input_dev *als_dev; |
| struct input_dev *ps_dev; |
| |
| als_dev = input_allocate_device(); |
| if (!als_dev) |
| return -ENOMEM; |
| |
| ps_dev = input_allocate_device(); |
| if (!ps_dev) { |
| err = -ENOMEM; |
| goto err_free_als; |
| } |
| |
| als_dev->name = "light"; |
| als_dev->id.bustype = BUS_I2C; |
| als_dev->id.vendor = 0x0001; |
| als_dev->id.product = 0x0001; |
| als_dev->id.version = 0x0100; |
| als_dev->evbit[0] = BIT_MASK(EV_ABS); |
| als_dev->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC); |
| als_dev->dev.platform_data = dev_dat; |
| input_set_abs_params(als_dev, ABS_MISC, 0, 2000, 0, 0); |
| |
| ps_dev->name = "proximity"; |
| ps_dev->id.bustype = BUS_I2C; |
| ps_dev->id.vendor = 0x0001; |
| ps_dev->id.product = 0x0002; |
| ps_dev->id.version = 0x0100; |
| ps_dev->evbit[0] = BIT_MASK(EV_ABS); |
| ps_dev->absbit[BIT_WORD(ABS_DISTANCE)] |= BIT_MASK(ABS_DISTANCE); |
| ps_dev->dev.platform_data = dev_dat; |
| input_set_abs_params(ps_dev, ABS_DISTANCE, 0, 1, 0, 0); |
| |
| err = input_register_device(als_dev); |
| if (err) |
| goto err_free_als; |
| |
| err = input_register_device(ps_dev); |
| if (err) |
| goto err_free_ps; |
| |
| err = sysfs_create_group(&als_dev->dev.kobj, &als_attr_grp); |
| if (err) { |
| pr_err("isl29044a: device create als file failed\n"); |
| goto err_free_als_sysfs; |
| } |
| |
| err = sysfs_create_group(&ps_dev->dev.kobj, &ps_attr_grp); |
| if (err) { |
| pr_err("isl29044a: device create ps file failed\n"); |
| goto err_free_ps_sysfs; |
| } |
| |
| dev_dat->als_input_dev = als_dev; |
| dev_dat->ps_input_dev = ps_dev; |
| |
| return 0; |
| |
| err_free_ps_sysfs: |
| sysfs_remove_group(&ps_dev->dev.kobj, &ps_attr_grp); |
| err_free_als_sysfs: |
| sysfs_remove_group(&als_dev->dev.kobj, &als_attr_grp); |
| err_free_ps: |
| input_free_device(ps_dev); |
| err_free_als: |
| input_free_device(als_dev); |
| pr_err("init_input_dev failed!\n"); |
| return err; |
| } |
| |
| /* Return 0 if detection is successful, -ENODEV otherwise */ |
| static int isl29044a_detect(struct i2c_client *client, |
| struct i2c_board_info *info) |
| { |
| struct i2c_adapter *adapter = client->adapter; |
| |
| dev_dbg(&client->dev, "In isl29044a_detect()\n"); |
| if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
| | I2C_FUNC_SMBUS_READ_BYTE)) { |
| pr_warn("I2c adapter don't support ISL29044A\n"); |
| return -ENODEV; |
| } |
| |
| /* probe that if isl29044a is at the i2 address */ |
| if (i2c_smbus_xfer(adapter, client->addr, 0, I2C_SMBUS_WRITE, |
| 0, I2C_SMBUS_QUICK, NULL) < 0) |
| return -ENODEV; |
| |
| strlcpy(info->type, "isl29044a", I2C_NAME_SIZE); |
| pr_info("%s is found at i2c device address %d\n", info->type, |
| client->addr); |
| |
| pr_info("isl29044a_detect OK!\n"); |
| return 0; |
| } |
| |
| static int isl_power_on(struct isl29044a_data_t *data, bool on) |
| { |
| int rc = 0; |
| |
| if (!on && data->power_enabled) { |
| rc = regulator_disable(data->vdd); |
| if (rc) { |
| dev_err(&data->client->dev, |
| "Regulator vdd disable failed rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = regulator_disable(data->vio); |
| if (rc) { |
| dev_err(&data->client->dev, |
| "Regulator vio disable failed rc=%d\n", rc); |
| rc = regulator_enable(data->vdd); |
| } |
| |
| data->power_enabled = false; |
| } else if (on && !data->power_enabled) { |
| rc = regulator_enable(data->vdd); |
| if (rc) { |
| dev_err(&data->client->dev, |
| "Regulator vdd enable failed rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = regulator_enable(data->vio); |
| if (rc) { |
| dev_err(&data->client->dev, |
| "Regulator vio enable failed rc=%d\n", rc); |
| regulator_disable(data->vdd); |
| } |
| |
| data->power_enabled = true; |
| } else { |
| dev_warn(&data->client->dev, |
| "Power on=%d. enabled=%d\n", |
| on, data->power_enabled); |
| } |
| |
| return rc; |
| } |
| |
| static int isl_power_init(struct isl29044a_data_t *data, bool on) |
| { |
| int rc; |
| |
| if (!on) { |
| if (regulator_count_voltages(data->vdd) > 0) |
| regulator_set_voltage(data->vdd, 0, ISL_VDD_MAX_UV); |
| |
| regulator_put(data->vdd); |
| |
| if (regulator_count_voltages(data->vio) > 0) |
| regulator_set_voltage(data->vio, 0, ISL_VIO_MAX_UV); |
| |
| regulator_put(data->vio); |
| } else { |
| data->vdd = regulator_get(&data->client->dev, "vdd"); |
| if (IS_ERR(data->vdd)) { |
| rc = PTR_ERR(data->vdd); |
| dev_err(&data->client->dev, |
| "Regulator get failed vdd rc=%d\n", rc); |
| return rc; |
| } |
| |
| if (regulator_count_voltages(data->vdd) > 0) { |
| rc = regulator_set_voltage(data->vdd, ISL_VDD_MIN_UV, |
| ISL_VDD_MAX_UV); |
| if (rc) { |
| dev_err(&data->client->dev, |
| "Regulator set failed vdd rc=%d\n", |
| rc); |
| goto reg_vdd_put; |
| } |
| } |
| |
| data->vio = regulator_get(&data->client->dev, "vio"); |
| if (IS_ERR(data->vio)) { |
| rc = PTR_ERR(data->vio); |
| dev_err(&data->client->dev, |
| "Regulator get failed vio rc=%d\n", rc); |
| goto reg_vdd_set; |
| } |
| |
| if (regulator_count_voltages(data->vio) > 0) { |
| rc = regulator_set_voltage(data->vio, ISL_VIO_MIN_UV, |
| ISL_VIO_MAX_UV); |
| if (rc) { |
| dev_err(&data->client->dev, |
| "Regulator set failed vio rc=%d\n", rc); |
| goto reg_vio_put; |
| } |
| } |
| } |
| |
| return 0; |
| reg_vio_put: |
| regulator_put(data->vio); |
| reg_vdd_set: |
| if (regulator_count_voltages(data->vdd) > 0) |
| regulator_set_voltage(data->vdd, 0, ISL_VDD_MAX_UV); |
| reg_vdd_put: |
| regulator_put(data->vdd); |
| return rc; |
| } |
| |
| static int sensor_parse_dt(struct device *dev, struct isl29044a_cfg_t *cfg) |
| { |
| struct device_node *np = dev->of_node; |
| unsigned int tmp; |
| int rc = 0; |
| |
| rc = of_property_read_u32(np, "intersil,als-range", &tmp); |
| if (rc) { |
| dev_err(dev, "Unable to read als-range\n"); |
| return rc; |
| } |
| cfg->als_range = tmp; |
| |
| rc = of_property_read_u32(np, "intersil,ps-ht", &tmp); |
| if (rc) { |
| dev_err(dev, "Unable to read intersil,ps-ht\n"); |
| return rc; |
| } |
| cfg->ps_ht = tmp; |
| |
| rc = of_property_read_u32(np, "intersil,ps-lt", &tmp); |
| if (rc) { |
| dev_err(dev, "Unable to read intersil,ps-lt\n"); |
| return rc; |
| } |
| cfg->ps_lt = tmp; |
| |
| return 0; |
| } |
| |
| /* isl29044a probed */ |
| static int isl29044a_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| int err, i,ret1,ret2; |
| u8 reg_dat[8]; |
| struct isl29044a_data_t *isl29044a_data; |
| struct isl29044a_cfg_t *cfgdata; |
| |
| if (client->dev.of_node) { |
| cfgdata = devm_kzalloc(&client->dev, |
| sizeof(struct isl29044a_cfg_t), |
| GFP_KERNEL); |
| if (!cfgdata) { |
| dev_err(&client->dev, "Failed to allocate memory\n"); |
| return -ENOMEM; |
| } |
| |
| client->dev.platform_data = cfgdata; |
| err = sensor_parse_dt(&client->dev, cfgdata); |
| if (err) { |
| pr_err("%s: sensor_parse_dt() err\n", __func__); |
| return err; |
| } |
| } else { |
| cfgdata = client->dev.platform_data; |
| if (!cfgdata) { |
| dev_err(&client->dev, "No platform data\n"); |
| return -ENODEV; |
| } |
| } |
| /* initial device data struct */ |
| isl29044a_data = devm_kzalloc(&client->dev, |
| sizeof(struct isl29044a_data_t), |
| GFP_KERNEL); |
| if (!isl29044a_data) { |
| dev_err(&client->dev, |
| "failed to allocate memory for module data:""%d\n", |
| err); |
| return -ENOMEM; |
| } |
| |
| isl29044a_data->cfg = cfgdata; |
| isl29044a_data->client = client; |
| |
| atomic_set(&isl29044a_data->als_pwr_status, 0); |
| atomic_set(&isl29044a_data->ps_pwr_status, 0); |
| isl29044a_data->ps_led_drv_cur = 0; |
| atomic_set(&isl29044a_data->als_range, cfgdata->als_range); |
| isl29044a_data->ps_lt = cfgdata->ps_lt; |
| isl29044a_data->ps_ht = cfgdata->ps_ht; |
| atomic_set(&isl29044a_data->poll_delay, 100); |
| atomic_set(&isl29044a_data->show_als_raw, 0); |
| atomic_set(&isl29044a_data->show_ps_raw, 0); |
| |
| INIT_WORK(&isl29044a_data->als_work, &do_als_work); |
| INIT_WORK(&isl29044a_data->ps_work, &do_ps_work); |
| init_timer(&isl29044a_data->als_timer); |
| init_timer(&isl29044a_data->ps_timer); |
| |
| isl29044a_data->als_wq = create_workqueue("als wq"); |
| if (!isl29044a_data->als_wq) { |
| destroy_workqueue(isl29044a_data->als_wq); |
| return -ENOMEM; |
| } |
| |
| isl29044a_data->ps_wq = create_workqueue("ps wq"); |
| if (!isl29044a_data->ps_wq) { |
| destroy_workqueue(isl29044a_data->ps_wq); |
| return -ENOMEM; |
| } |
| |
| i2c_set_clientdata(client,isl29044a_data); |
| |
| ret1 = isl_power_init(isl29044a_data,true); |
| if (ret1 < 0) { |
| dev_err(&client->dev, "%s:isl29044 power init error!\n", |
| __func__); |
| } |
| |
| ret2 = isl_power_on(isl29044a_data,true); |
| if (ret2 < 0) { |
| dev_err(&client->dev, "%s:isl29044 power on error!\n", |
| __func__); |
| } |
| |
| /* initial isl29044a */ |
| err = set_sensor_reg(isl29044a_data); |
| if (err < 0) { |
| pr_err("isl29044 set_sensor_reg error\n"); |
| return err; |
| } |
| /* initial als interrupt limit to low = 0, high = 4095, so als cannot |
| trigger a interrupt. We use ps interrupt only */ |
| reg_dat[5] = 0x00; |
| reg_dat[6] = 0xf0; |
| reg_dat[7] = 0xff; |
| for (i = 5; i <= 7; i++) { |
| err = i2c_smbus_write_byte_data(client, i, reg_dat[i]); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "isl29044 write i2c error in probe.\n"); |
| return err; |
| } |
| } |
| |
| /* Add input device register here */ |
| err = init_input_dev(isl29044a_data); |
| if (err < 0) { |
| dev_err(&client->dev, |
| "isl29044 init_input_dev error in probe.\n"); |
| destroy_workqueue(isl29044a_data->als_wq); |
| destroy_workqueue(isl29044a_data->ps_wq); |
| return -ENOMEM; |
| } |
| |
| isl29044a_data->als_cdev = sensors_light_cdev; |
| isl29044a_data->als_cdev.sensors_enable = isl29044a_als_set_enable; |
| isl29044a_data->als_cdev.sensors_poll_delay = |
| isl29044a_als_poll_delay_enable; |
| |
| isl29044a_data->ps_cdev = sensors_proximity_cdev; |
| isl29044a_data->ps_cdev.sensors_enable = isl29044a_ps_set_enable; |
| isl29044a_data->ps_cdev.sensors_poll_delay = |
| isl29044a_ps_poll_delay_enable; |
| |
| err = sensors_classdev_register(&client->dev, |
| &isl29044a_data->als_cdev); |
| if (err) |
| dev_err(&client->dev, |
| "create als_cdev class device file failed!\n"); |
| |
| err = sensors_classdev_register(&client->dev, &isl29044a_data->ps_cdev); |
| if (err) { |
| dev_err(&client->dev, |
| "create ps_cdev class device file failed!\n"); |
| err = -EINVAL; |
| } |
| |
| return err; |
| } |
| |
| static int isl29044a_remove(struct i2c_client *client) |
| { |
| struct input_dev *als_dev; |
| struct input_dev *ps_dev; |
| struct isl29044a_data_t *isl29044a_data = i2c_get_clientdata(client); |
| pr_info("%s at address %d is removed\n", client->name, client->addr); |
| |
| /* clean the isl29044a data struct when isl29044a device remove */ |
| isl29044a_data->client = NULL; |
| atomic_set(&isl29044a_data->als_pwr_status, 0); |
| atomic_set(&isl29044a_data->ps_pwr_status, 0); |
| |
| als_dev = isl29044a_data->als_input_dev; |
| ps_dev = isl29044a_data->ps_input_dev; |
| |
| sysfs_remove_group(&als_dev->dev.kobj, &als_attr_grp); |
| sysfs_remove_group(&ps_dev->dev.kobj, &ps_attr_grp); |
| |
| input_unregister_device(als_dev); |
| input_unregister_device(ps_dev); |
| sensors_classdev_unregister(&isl29044a_data->als_cdev); |
| sensors_classdev_unregister(&isl29044a_data->ps_cdev); |
| |
| destroy_workqueue(isl29044a_data->ps_wq); |
| destroy_workqueue(isl29044a_data->als_wq); |
| isl29044a_data->als_input_dev = NULL; |
| isl29044a_data->ps_input_dev = NULL; |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM_SLEEP |
| /* if define power manager, define suspend and resume function */ |
| static int isl29044a_suspend(struct device *dev) |
| { |
| struct i2c_client *client = to_i2c_client(dev); |
| struct isl29044a_data_t *dat = i2c_get_clientdata(client); |
| int ret; |
| |
| dat->als_pwr_before_suspend = atomic_read(&dat->als_pwr_status); |
| ret = set_als_pwr_st(0, dat); |
| if (ret < 0) |
| dev_err(dev, "%s:could not set ALS power state.\n", __func__); |
| |
| return 0; |
| } |
| |
| static int isl29044a_resume(struct device *dev) |
| { |
| struct i2c_client *client = to_i2c_client(dev); |
| struct isl29044a_data_t *dat = i2c_get_clientdata(client); |
| int ret; |
| |
| ret = set_als_pwr_st(dat->als_pwr_before_suspend, dat); |
| if (ret < 0) |
| return ret; |
| |
| ret = set_ps_pwr_st(dat->ps_pwr_before_suspend, dat); |
| if (ret < 0) |
| return ret; |
| |
| return 0; |
| } |
| #else |
| #define isl29044a_suspend NULL |
| #define isl29044a_resume NULL |
| #endif /*ifdef CONFIG_PM_SLEEP end*/ |
| |
| static SIMPLE_DEV_PM_OPS(isl29044a_pm_ops, isl29044a_suspend, isl29044a_resume); |
| |
| static const struct i2c_device_id isl29044a_id[] = { |
| { "isl29044a", 0 }, |
| { } |
| }; |
| |
| static struct of_device_id intersil_match_table[] = { |
| { .compatible = "intersil,isl29044a",}, |
| { }, |
| }; |
| |
| static struct i2c_driver isl29044a_driver = { |
| .driver = { |
| .name = "isl29044a", |
| .pm = &isl29044a_pm_ops, |
| .of_match_table = intersil_match_table, |
| }, |
| .probe = isl29044a_probe, |
| .remove = isl29044a_remove, |
| .id_table = isl29044a_id, |
| .detect = isl29044a_detect, |
| .address_list = normal_i2c, |
| }; |
| |
| struct i2c_client *isl29044a_client; |
| |
| static int __init isl29044a_init(void) |
| { |
| int ret; |
| |
| /* register the i2c driver for isl29044a */ |
| ret = i2c_add_driver(&isl29044a_driver); |
| if (ret < 0) |
| pr_err("Add isl29044a driver error, ret = %d\n", ret); |
| pr_debug("init isl29044a module\n"); |
| |
| return ret; |
| } |
| |
| static void __exit isl29044a_exit(void) |
| { |
| pr_debug("exit isl29044a module\n"); |
| i2c_del_driver(&isl29044a_driver); |
| } |
| |
| MODULE_AUTHOR("Chen Shouxian"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("isl29044a ambient light sensor driver"); |
| MODULE_VERSION(DRIVER_VERSION); |
| |
| module_init(isl29044a_init); |
| module_exit(isl29044a_exit); |