1311 lines
32 KiB
C
Executable File
1311 lines
32 KiB
C
Executable File
/******************************************************************************
|
|
* 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);
|