1252 lines
29 KiB
C
1252 lines
29 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2020 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/sched/clock.h>
|
|
#include "cust_alsps.h"
|
|
#include "ltr303.h"
|
|
#include "alsps.h"
|
|
|
|
#define LTR303_DEV_NAME "ltr303"
|
|
#define APS_TAG "[ALS] "
|
|
#define APS_FUN(f) pr_info(APS_TAG"%s\n", __func__)
|
|
#define APS_ERR(fmt, args...) pr_info(APS_TAG"%s %d : "\
|
|
fmt, __func__, __LINE__, ##args)
|
|
#define APS_LOG(fmt, args...) pr_info(APS_TAG fmt, ##args)
|
|
#define APS_DBG(fmt, args...) pr_info(APS_TAG fmt, ##args)
|
|
|
|
#define DRIVER_ATTR(_name, _mode, _show, _store) \
|
|
struct driver_attribute driver_attr_##_name = \
|
|
__ATTR(_name, _mode, _show, _store)
|
|
|
|
static const struct i2c_device_id ltr303_i2c_id[] = {
|
|
{LTR303_DEV_NAME, 0},
|
|
{},
|
|
};
|
|
static unsigned long long int_top_time;
|
|
struct alsps_hw alsps_cust;
|
|
static struct alsps_hw *hw = &alsps_cust;
|
|
|
|
static int ltr303_i2c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id);
|
|
static int ltr303_i2c_remove(struct i2c_client *client);
|
|
static int ltr303_i2c_detect(struct i2c_client *client,
|
|
struct i2c_board_info *info);
|
|
static int ltr303_i2c_suspend(struct device *dev);
|
|
static int ltr303_i2c_resume(struct device *dev);
|
|
static int als_gainrange;
|
|
static int final_lux_val;
|
|
|
|
enum CMC_BIT {
|
|
CMC_BIT_ALS = 1,
|
|
};
|
|
|
|
struct ltr303_priv {
|
|
struct alsps_hw *hw;
|
|
struct i2c_client *client;
|
|
struct work_struct eint_work;
|
|
|
|
/*misc*/
|
|
u16 als_modulus;
|
|
atomic_t i2c_retry;
|
|
atomic_t als_suspend;
|
|
atomic_t als_debounce;
|
|
atomic_t als_deb_on;
|
|
atomic_t als_deb_end;
|
|
atomic_t trace;
|
|
|
|
#ifdef CONFIG_OF
|
|
struct device_node *irq_node;
|
|
int irq;
|
|
#endif
|
|
|
|
/*data*/
|
|
u16 als;
|
|
u8 _align;
|
|
u16 als_level_num;
|
|
u16 als_value_num;
|
|
u32 als_level[C_CUST_ALS_LEVEL-1];
|
|
u32 als_value[C_CUST_ALS_LEVEL];
|
|
atomic_t als_cmd_val;
|
|
atomic_t als_thd_val_high;
|
|
atomic_t als_thd_val_low;
|
|
ulong enable; /*enable mask*/
|
|
ulong pending_intr; /*pending interrupt*/
|
|
};
|
|
|
|
static struct ltr303_priv *ltr303_obj;
|
|
static struct i2c_client *ltr303_i2c_client;
|
|
static DEFINE_MUTEX(ltr303_mutex);
|
|
static int ltr303_local_init(void);
|
|
static int ltr303_remove(void);
|
|
static int ltr303_init_flag = -1;
|
|
static int irq_enabled;
|
|
static struct alsps_init_info ltr303_init_info = {
|
|
.name = "ltr303",
|
|
.init = ltr303_local_init,
|
|
.uninit = ltr303_remove,
|
|
};
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id alsps_of_match[] = {
|
|
{.compatible = "mediatek,alsps"},
|
|
{},
|
|
};
|
|
#endif
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static const struct dev_pm_ops ltr303_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(ltr303_i2c_suspend, ltr303_i2c_resume)
|
|
};
|
|
#endif
|
|
|
|
static struct i2c_driver ltr303_i2c_driver = {
|
|
.probe = ltr303_i2c_probe,
|
|
.remove = ltr303_i2c_remove,
|
|
.detect = ltr303_i2c_detect,
|
|
.id_table = ltr303_i2c_id,
|
|
.driver = {
|
|
.name = LTR303_DEV_NAME,
|
|
#ifdef CONFIG_OF
|
|
.of_match_table = alsps_of_match,
|
|
#endif
|
|
#ifdef CONFIG_PM_SLEEP
|
|
.pm = <r303_pm_ops,
|
|
#endif
|
|
},
|
|
};
|
|
|
|
/* I2C Read */
|
|
static int ltr303_i2c_read_reg(u8 regnum)
|
|
{
|
|
u8 buffer[1], reg_value[1];
|
|
int res = 0;
|
|
|
|
mutex_lock(<r303_mutex);
|
|
buffer[0] = regnum;
|
|
res = i2c_master_send(ltr303_obj->client, buffer, 0x1);
|
|
if (res <= 0) {
|
|
APS_ERR("read reg send res = %d\n", res);
|
|
mutex_unlock(<r303_mutex);
|
|
return res;
|
|
}
|
|
|
|
res = i2c_master_recv(ltr303_obj->client, reg_value, 0x1);
|
|
if (res <= 0) {
|
|
APS_ERR("read reg recv res = %d\n", res);
|
|
mutex_unlock(<r303_mutex);
|
|
return res;
|
|
}
|
|
|
|
mutex_unlock(<r303_mutex);
|
|
return reg_value[0];
|
|
}
|
|
|
|
/* I2C Write */
|
|
static int ltr303_i2c_write_reg(u8 regnum, u8 value)
|
|
{
|
|
u8 databuf[2];
|
|
int res = 0;
|
|
|
|
mutex_lock(<r303_mutex);
|
|
databuf[0] = regnum;
|
|
databuf[1] = value;
|
|
res = i2c_master_send(ltr303_obj->client, databuf, 0x2);
|
|
mutex_unlock(<r303_mutex);
|
|
if (res < 0) {
|
|
APS_ERR("write reg send res = %d\n", res);
|
|
return res;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
static void ltr303_power(struct alsps_hw *hw, unsigned int on)
|
|
{
|
|
#ifdef DEMO_BOARD
|
|
static unsigned int power_on;
|
|
|
|
if (hw->power_id != POWER_NONE_MACRO) {
|
|
if (power_on == on) {
|
|
APS_LOG("ignore power control: %d\n", on);
|
|
} else if (on) {
|
|
if (!hwPowerOn(hw->power_id, hw->power_vol, "ltr303"))
|
|
APS_ERR("power on fails!!\n");
|
|
} else {
|
|
if (!hwPowerDown(hw->power_id, "ltr303"))
|
|
APS_ERR("power off fail!!\n");
|
|
}
|
|
}
|
|
|
|
power_on = on;
|
|
#endif
|
|
}
|
|
|
|
static int ltr303_als_enable(struct i2c_client *client, int enable)
|
|
{
|
|
int err = 0;
|
|
u8 regdata = 0;
|
|
|
|
regdata = ltr303_i2c_read_reg(LTR303_ALS_CONTR);
|
|
if (enable != 0) {
|
|
APS_LOG("ALS(1): enable als only\n");
|
|
regdata |= 0x01;
|
|
} else {
|
|
APS_LOG("ALS(1): disable als only\n");
|
|
regdata &= 0xfe;
|
|
}
|
|
|
|
err = ltr303_i2c_write_reg(LTR303_ALS_CONTR, regdata);
|
|
if (err < 0) {
|
|
APS_ERR("ALS: enable als err: %d en: %d\n", err, enable);
|
|
return err;
|
|
}
|
|
|
|
mdelay(WAKEUP_DELAY);
|
|
return 0;
|
|
}
|
|
|
|
static int ltr303_als_read(struct i2c_client *client, u16 *data)
|
|
{
|
|
int alsval_ch0_lo, alsval_ch0_hi, alsval_ch0;
|
|
int alsval_ch1_lo, alsval_ch1_hi, alsval_ch1;
|
|
int luxdata_int;
|
|
int ratio;
|
|
|
|
alsval_ch1_lo = ltr303_i2c_read_reg(LTR303_ALS_DATA_CH1_0);
|
|
alsval_ch1_hi = ltr303_i2c_read_reg(LTR303_ALS_DATA_CH1_1);
|
|
alsval_ch1 = (alsval_ch1_hi * 256) + alsval_ch1_lo;
|
|
APS_DBG("alsval_ch1_lo = %d, alsval_ch1_hi=%d, alsval_ch1=%d\n",
|
|
alsval_ch1_lo, alsval_ch1_hi, alsval_ch1);
|
|
alsval_ch0_lo = ltr303_i2c_read_reg(LTR303_ALS_DATA_CH0_0);
|
|
alsval_ch0_hi = ltr303_i2c_read_reg(LTR303_ALS_DATA_CH0_1);
|
|
alsval_ch0 = (alsval_ch0_hi * 256) + alsval_ch0_lo;
|
|
APS_DBG("alsval_ch0_lo = %d, alsval_ch0_hi=%d, alsval_ch0=%d\n",
|
|
alsval_ch0_lo, alsval_ch0_hi, alsval_ch0);
|
|
|
|
if ((alsval_ch1 == 0) || (alsval_ch0 == 0)) {
|
|
luxdata_int = 0;
|
|
goto out;
|
|
}
|
|
ratio = (alsval_ch1 * 100) / (alsval_ch0 + alsval_ch1);
|
|
APS_DBG("ratio = %d gainrange = %d\n", ratio, als_gainrange);
|
|
if (ratio < 45)
|
|
luxdata_int = (((17743 * alsval_ch0) + (11059 * alsval_ch1))
|
|
/ als_gainrange) / 1000;
|
|
else if ((ratio < 64) && (ratio >= 45))
|
|
luxdata_int = (((42785 * alsval_ch0) - (19548 * alsval_ch1))
|
|
/ als_gainrange) / 1000;
|
|
else if ((ratio < 85) && (ratio >= 64))
|
|
luxdata_int = (((5926 * alsval_ch0) + (1185 * alsval_ch1))
|
|
/ als_gainrange) / 1000;
|
|
else
|
|
luxdata_int = 0;
|
|
|
|
APS_DBG("%s: als_value_lux = %d\n", __func__, luxdata_int);
|
|
out:
|
|
*data = luxdata_int;
|
|
final_lux_val = luxdata_int;
|
|
return luxdata_int;
|
|
}
|
|
|
|
static int ltr303_get_als_value(struct ltr303_priv *obj, u16 als)
|
|
{
|
|
int idx;
|
|
int invalid = 0;
|
|
|
|
APS_DBG("als = %d\n", als);
|
|
for (idx = 0; idx < obj->als_level_num; idx++) {
|
|
if (als < obj->hw->als_level[idx])
|
|
break;
|
|
}
|
|
if (idx >= obj->als_value_num) {
|
|
APS_ERR("exceed range\n");
|
|
idx = obj->als_value_num - 1;
|
|
}
|
|
|
|
if (atomic_read(&obj->als_deb_on) == 1) {
|
|
unsigned long endt = atomic_read(&obj->als_deb_end);
|
|
|
|
if (time_after(jiffies, endt))
|
|
atomic_set(&obj->als_deb_on, 0);
|
|
|
|
if (atomic_read(&obj->als_deb_on) == 1)
|
|
invalid = 1;
|
|
}
|
|
|
|
if (!invalid) {
|
|
APS_DBG("ALS: %05d => %05d\n", als, obj->hw->als_value[idx]);
|
|
return obj->hw->als_value[idx];
|
|
}
|
|
|
|
APS_ERR("ALS: %05d => %05d (-1)\n", als, obj->hw->als_value[idx]);
|
|
return -1;
|
|
}
|
|
|
|
/*---------------------attribute file for debugging------------------------*/
|
|
static ssize_t ltr303_show_config(struct device_driver *ddri, char *buf)
|
|
{
|
|
ssize_t res;
|
|
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return 0;
|
|
}
|
|
|
|
res = snprintf(buf, PAGE_SIZE, "(%d %d)\n",
|
|
atomic_read(<r303_obj->i2c_retry),
|
|
atomic_read(<r303_obj->als_debounce));
|
|
return res;
|
|
}
|
|
|
|
static ssize_t ltr303_store_config(struct device_driver *ddri, const char *buf,
|
|
size_t count)
|
|
{
|
|
int retry, als_deb;
|
|
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return 0;
|
|
}
|
|
|
|
if (sscanf(buf, "%d %d", &retry, &als_deb) == 2) {
|
|
atomic_set(<r303_obj->i2c_retry, retry);
|
|
atomic_set(<r303_obj->als_debounce, als_deb);
|
|
} else
|
|
APS_ERR("invalid content: '%s', length = %zu\n", buf, count);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t ltr303_show_trace(struct device_driver *ddri, char *buf)
|
|
{
|
|
ssize_t res;
|
|
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return 0;
|
|
}
|
|
res = snprintf(buf, PAGE_SIZE, "0x%04x\n",
|
|
atomic_read(<r303_obj->trace));
|
|
return res;
|
|
}
|
|
|
|
static ssize_t ltr303_store_trace(struct device_driver *ddri, const char *buf,
|
|
size_t count)
|
|
{
|
|
int trace;
|
|
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return 0;
|
|
}
|
|
|
|
if (sscanf(buf, "0x%x", &trace) == 1)
|
|
atomic_set(<r303_obj->trace, trace);
|
|
else
|
|
APS_ERR("invalid content: '%s', length = %zu\n", buf, count);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t ltr303_show_als(struct device_driver *ddri, char *buf)
|
|
{
|
|
int res;
|
|
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return 0;
|
|
}
|
|
|
|
res = ltr303_als_read(ltr303_obj->client, <r303_obj->als);
|
|
return snprintf(buf, PAGE_SIZE, "0x%04X(%d)\n", res, res);
|
|
}
|
|
|
|
static ssize_t ltr303_show_reg(struct device_driver *ddri, char *buf)
|
|
{
|
|
int i, len = 0;
|
|
int reg[] = {0x80, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8f,
|
|
0x97, 0x98, 0x99, 0x9a, 0x9e};
|
|
|
|
for (i = 0; i < 15; i++)
|
|
len += snprintf(buf+len, PAGE_SIZE-len,
|
|
"reg:0x%04x value: 0x%04x\n",
|
|
reg[i], ltr303_i2c_read_reg(reg[i]));
|
|
|
|
return len;
|
|
}
|
|
|
|
static ssize_t ltr303_show_send(struct device_driver *ddri, char *buf)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t ltr303_store_send(struct device_driver *ddri, const char *buf,
|
|
size_t count)
|
|
{
|
|
int addr, cmd;
|
|
u8 dat;
|
|
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return 0;
|
|
} else if (sscanf(buf, "%x %x", &addr, &cmd) != 2) {
|
|
APS_ERR("invalid format: '%s'\n", buf);
|
|
return 0;
|
|
}
|
|
|
|
dat = (u8)cmd;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t ltr303_show_recv(struct device_driver *ddri, char *buf)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t ltr303_store_recv(struct device_driver *ddri, const char *buf,
|
|
size_t count)
|
|
{
|
|
int addr;
|
|
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return 0;
|
|
} else if (kstrtouint(buf, 10, &addr) != 1) {
|
|
APS_ERR("invalid format: '%s'\n", buf);
|
|
return 0;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t ltr303_show_status(struct device_driver *ddri, char *buf)
|
|
{
|
|
ssize_t len = 0;
|
|
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return 0;
|
|
}
|
|
|
|
if (ltr303_obj->hw) {
|
|
len += snprintf(buf+len, PAGE_SIZE-len, "CUST: %d, (%d %d)\n",
|
|
ltr303_obj->hw->i2c_num, ltr303_obj->hw->power_id,
|
|
ltr303_obj->hw->power_vol);
|
|
} else {
|
|
len += snprintf(buf+len, PAGE_SIZE-len, "CUST: NULL\n");
|
|
}
|
|
|
|
len += snprintf(buf+len, PAGE_SIZE-len, "MISC: %d\n",
|
|
atomic_read(<r303_obj->als_suspend));
|
|
return len;
|
|
}
|
|
|
|
#define IS_SPACE(CH) (((CH) == ' ') || ((CH) == '\n'))
|
|
static int read_int_from_buf(struct ltr303_priv *obj, const char *buf,
|
|
size_t count, u32 data[], int len)
|
|
{
|
|
int idx = 0;
|
|
char *cur = (char *) buf, *end = (char *)(buf+count);
|
|
|
|
while (idx < len) {
|
|
while ((cur < end) && IS_SPACE(*cur))
|
|
cur++;
|
|
|
|
if (kstrtouint(cur, 10, &data[idx]) != 1)
|
|
break;
|
|
|
|
idx++;
|
|
while ((cur < end) && !IS_SPACE(*cur))
|
|
cur++;
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
static ssize_t ltr303_show_alslv(struct device_driver *ddri, char *buf)
|
|
{
|
|
ssize_t len = 0;
|
|
int idx;
|
|
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return 0;
|
|
}
|
|
|
|
for (idx = 0; idx < ltr303_obj->als_level_num; idx++)
|
|
len += snprintf(buf+len, PAGE_SIZE-len, "%d ",
|
|
ltr303_obj->hw->als_level[idx]);
|
|
|
|
len += snprintf(buf+len, PAGE_SIZE-len, "\n");
|
|
return len;
|
|
}
|
|
|
|
static ssize_t ltr303_store_alslv(struct device_driver *ddri, const char *buf,
|
|
size_t count)
|
|
{
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return 0;
|
|
} else if (!strcmp(buf, "def"))
|
|
memcpy(ltr303_obj->als_level, ltr303_obj->hw->als_level,
|
|
sizeof(ltr303_obj->als_level));
|
|
else if (ltr303_obj->als_level_num != read_int_from_buf(ltr303_obj,
|
|
buf, count, ltr303_obj->hw->als_level,
|
|
ltr303_obj->als_level_num))
|
|
APS_ERR("invalid format: '%s'\n", buf);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t ltr303_show_alsval(struct device_driver *ddri, char *buf)
|
|
{
|
|
ssize_t len = 0;
|
|
int idx;
|
|
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return 0;
|
|
}
|
|
|
|
for (idx = 0; idx < ltr303_obj->als_value_num; idx++)
|
|
len += snprintf(buf+len, PAGE_SIZE-len, "%d ",
|
|
ltr303_obj->hw->als_value[idx]);
|
|
|
|
len += snprintf(buf+len, PAGE_SIZE-len, "\n");
|
|
return len;
|
|
}
|
|
|
|
static ssize_t ltr303_store_alsval(struct device_driver *ddri,
|
|
const char *buf, size_t count)
|
|
{
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return 0;
|
|
} else if (!strcmp(buf, "def"))
|
|
memcpy(ltr303_obj->als_value, ltr303_obj->hw->als_value,
|
|
sizeof(ltr303_obj->als_value));
|
|
else if (ltr303_obj->als_value_num != read_int_from_buf(ltr303_obj,
|
|
buf, count, ltr303_obj->hw->als_value,
|
|
ltr303_obj->als_value_num))
|
|
APS_ERR("invalid format: '%s'\n", buf);
|
|
|
|
return count;
|
|
}
|
|
|
|
static DRIVER_ATTR(als, 0444, ltr303_show_als, NULL);
|
|
static DRIVER_ATTR(config, 0644, ltr303_show_config, ltr303_store_config);
|
|
static DRIVER_ATTR(alslv, 0644, ltr303_show_alslv, ltr303_store_alslv);
|
|
static DRIVER_ATTR(alsval, 0644, ltr303_show_alsval, ltr303_store_alsval);
|
|
static DRIVER_ATTR(trace, 0644, ltr303_show_trace, ltr303_store_trace);
|
|
static DRIVER_ATTR(status, 0444, ltr303_show_status, NULL);
|
|
static DRIVER_ATTR(send, 0644, ltr303_show_send, ltr303_store_send);
|
|
static DRIVER_ATTR(recv, 0644, ltr303_show_recv, ltr303_store_recv);
|
|
static DRIVER_ATTR(reg, 0444, ltr303_show_reg, NULL);
|
|
|
|
static struct driver_attribute *ltr303_attr_list[] = {
|
|
&driver_attr_als,
|
|
&driver_attr_trace, /*trace log*/
|
|
&driver_attr_config,
|
|
&driver_attr_alslv,
|
|
&driver_attr_alsval,
|
|
&driver_attr_status,
|
|
&driver_attr_send,
|
|
&driver_attr_recv,
|
|
&driver_attr_reg,
|
|
};
|
|
|
|
static int ltr303_create_attr(struct device_driver *driver)
|
|
{
|
|
int idx, err = 0;
|
|
int num = ARRAY_SIZE(ltr303_attr_list);
|
|
|
|
if (driver == NULL)
|
|
return -EINVAL;
|
|
|
|
|
|
for (idx = 0; idx < num; idx++) {
|
|
err = driver_create_file(driver, ltr303_attr_list[idx]);
|
|
if (err) {
|
|
APS_ERR("driver_create_file (%s) = %d\n",
|
|
ltr303_attr_list[idx]->attr.name, err);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ltr303_delete_attr(struct device_driver *driver)
|
|
{
|
|
int idx, err = 0;
|
|
int num = ARRAY_SIZE(ltr303_attr_list);
|
|
|
|
if (!driver)
|
|
return -EINVAL;
|
|
|
|
for (idx = 0; idx < num; idx++)
|
|
driver_remove_file(driver, ltr303_attr_list[idx]);
|
|
|
|
|
|
return err;
|
|
}
|
|
|
|
/*-------------------------------interrupt functions------------------------*/
|
|
#ifndef CUSTOM_KERNEL_SENSORHUB
|
|
static int ltr303_check_intr(struct i2c_client *client)
|
|
{
|
|
int res, intl;
|
|
u8 buffer[2];
|
|
|
|
APS_FUN();
|
|
buffer[0] = LTR303_ALS_STATUS;
|
|
res = i2c_master_send(client, buffer, 0x1);
|
|
if (res <= 0)
|
|
goto EXIT_ERR;
|
|
|
|
|
|
res = i2c_master_recv(client, buffer, 0x1);
|
|
if (res <= 0)
|
|
goto EXIT_ERR;
|
|
|
|
|
|
res = 0;
|
|
intl = 0;
|
|
if ((buffer[0] & 0x08) != 0) {
|
|
res = 2;
|
|
intl = 1;
|
|
}
|
|
|
|
return res;
|
|
|
|
EXIT_ERR:
|
|
APS_ERR("%s fail\n", __func__);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static void ltr303_eint_work(struct work_struct *work)
|
|
{
|
|
struct ltr303_priv *obj = (struct ltr303_priv *)container_of(work,
|
|
struct ltr303_priv, eint_work);
|
|
int err;
|
|
|
|
err = ltr303_check_intr(obj->client);
|
|
if (err < 0)
|
|
goto EXIT_INTR;
|
|
|
|
EXIT_INTR:
|
|
#ifdef CONFIG_OF
|
|
enable_irq(obj->irq);
|
|
#endif
|
|
}
|
|
|
|
static void ltr303_eint_func(void)
|
|
{
|
|
struct ltr303_priv *obj = ltr303_obj;
|
|
|
|
if (!obj)
|
|
return;
|
|
|
|
int_top_time = sched_clock();
|
|
schedule_work(&obj->eint_work);
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static irqreturn_t ltr303_eint_handler(int irq, void *desc)
|
|
{
|
|
if (irq_enabled == 2) {
|
|
disable_irq_nosync(ltr303_obj->irq);
|
|
ltr303_eint_func();
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
#endif
|
|
|
|
int ltr303_setup_eint(struct i2c_client *client)
|
|
{
|
|
int ret;
|
|
struct pinctrl *pinctrl;
|
|
struct pinctrl_state *pins_cfg;
|
|
u32 ints[2] = { 0, 0};
|
|
|
|
APS_FUN();
|
|
/* gpio setting */
|
|
pinctrl = devm_pinctrl_get(&client->dev);
|
|
if (IS_ERR(pinctrl)) {
|
|
ret = PTR_ERR(pinctrl);
|
|
APS_ERR("Cannot find alsps pinctrl!\n");
|
|
return ret;
|
|
}
|
|
pins_cfg = pinctrl_lookup_state(pinctrl, "pin_cfg");
|
|
if (IS_ERR(pins_cfg)) {
|
|
ret = PTR_ERR(pins_cfg);
|
|
APS_ERR("Cannot find alsps pinctrl pin_cfg!\n");
|
|
}
|
|
/* eint request */
|
|
if (ltr303_obj->irq_node) {
|
|
of_property_read_u32_array(ltr303_obj->irq_node, "debounce",
|
|
ints, ARRAY_SIZE(ints));
|
|
gpio_request(ints[0], "p-sensor");
|
|
gpio_set_debounce(ints[0], ints[1]);
|
|
pinctrl_select_state(pinctrl, pins_cfg);
|
|
APS_LOG("ints[0] = %d, ints[1] = %d!!\n", ints[0], ints[1]);
|
|
ltr303_obj->irq = irq_of_parse_and_map(ltr303_obj->irq_node, 0);
|
|
APS_LOG("ltr303_obj->irq = %d\n", ltr303_obj->irq);
|
|
if (!ltr303_obj->irq) {
|
|
APS_ERR("irq_of_parse_and_map fail!!\n");
|
|
return -EINVAL;
|
|
}
|
|
APS_ERR("irq to gpio = %d\n", irq_to_gpio(ltr303_obj->irq));
|
|
if (request_irq(ltr303_obj->irq, ltr303_eint_handler,
|
|
IRQF_TRIGGER_NONE, "ALS-eint", NULL)) {
|
|
APS_ERR("IRQ LINE NOT AVAILABLE!!\n");
|
|
return -EINVAL;
|
|
}
|
|
enable_irq_wake(ltr303_obj->irq);
|
|
irq_enabled = 1;
|
|
} else {
|
|
APS_ERR("null irq node!!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*-------------------------MISC device related------------------------------*/
|
|
static int ltr303_open(struct inode *inode, struct file *file)
|
|
{
|
|
file->private_data = ltr303_i2c_client;
|
|
if (!file->private_data) {
|
|
APS_ERR("null pointer!!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return nonseekable_open(inode, file);
|
|
}
|
|
static int ltr303_release(struct inode *inode, struct file *file)
|
|
{
|
|
file->private_data = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static long ltr303_unlocked_ioctl(struct file *file, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
struct i2c_client *client = (struct i2c_client *)file->private_data;
|
|
struct ltr303_priv *obj = i2c_get_clientdata(client);
|
|
int err = 0;
|
|
void __user *ptr = (void __user *) arg;
|
|
int dat;
|
|
uint32_t enable;
|
|
int ps_cali;
|
|
int threshold[2];
|
|
|
|
APS_DBG("cmd= %d\n", cmd);
|
|
switch (cmd) {
|
|
case ALSPS_SET_PS_MODE:
|
|
if (copy_from_user(&enable, ptr, sizeof(enable))) {
|
|
err = -EFAULT;
|
|
goto err_out;
|
|
}
|
|
break;
|
|
case ALSPS_GET_PS_RAW_DATA:
|
|
dat = 0;
|
|
if (copy_to_user(ptr, &dat, sizeof(dat))) {
|
|
err = -EFAULT;
|
|
goto err_out;
|
|
}
|
|
break;
|
|
case ALSPS_SET_ALS_MODE:
|
|
if (copy_from_user(&enable, ptr, sizeof(enable))) {
|
|
err = -EFAULT;
|
|
goto err_out;
|
|
}
|
|
err = ltr303_als_enable(obj->client, enable);
|
|
if (err < 0) {
|
|
APS_ERR("enable als fail: %d en: %d\n", err, enable);
|
|
goto err_out;
|
|
}
|
|
if (enable)
|
|
set_bit(CMC_BIT_ALS, &obj->enable);
|
|
else
|
|
clear_bit(CMC_BIT_ALS, &obj->enable);
|
|
break;
|
|
case ALSPS_GET_ALS_RAW_DATA:
|
|
obj->als = ltr303_als_read(obj->client, &obj->als);
|
|
if (obj->als < 0)
|
|
goto err_out;
|
|
|
|
dat = obj->als;
|
|
if (copy_to_user(ptr, &dat, sizeof(dat))) {
|
|
err = -EFAULT;
|
|
goto err_out;
|
|
}
|
|
break;
|
|
/*--------------------------for factory mode test---------------------------*/
|
|
case ALSPS_GET_PS_TEST_RESULT:
|
|
dat = 0;
|
|
if (copy_to_user(ptr, &dat, sizeof(dat))) {
|
|
err = -EFAULT;
|
|
goto err_out;
|
|
}
|
|
break;
|
|
case ALSPS_IOCTL_CLR_CALI:
|
|
if (copy_from_user(&dat, ptr, sizeof(dat))) {
|
|
err = -EFAULT;
|
|
goto err_out;
|
|
}
|
|
break;
|
|
case ALSPS_IOCTL_GET_CALI:
|
|
ps_cali = 0;
|
|
if (copy_to_user(ptr, &ps_cali, sizeof(ps_cali))) {
|
|
err = -EFAULT;
|
|
goto err_out;
|
|
}
|
|
break;
|
|
case ALSPS_IOCTL_SET_CALI:
|
|
if (copy_from_user(&ps_cali, ptr, sizeof(ps_cali))) {
|
|
err = -EFAULT;
|
|
goto err_out;
|
|
}
|
|
break;
|
|
case ALSPS_SET_PS_THRESHOLD:
|
|
if (copy_from_user(threshold, ptr, sizeof(threshold))) {
|
|
err = -EFAULT;
|
|
goto err_out;
|
|
}
|
|
break;
|
|
case ALSPS_GET_PS_THRESHOLD_HIGH:
|
|
threshold[0] = 300;
|
|
if (copy_to_user(ptr, &threshold[0], sizeof(threshold[0]))) {
|
|
err = -EFAULT;
|
|
goto err_out;
|
|
}
|
|
break;
|
|
case ALSPS_GET_PS_THRESHOLD_LOW:
|
|
threshold[0] = 100;
|
|
if (copy_to_user(ptr, &threshold[0], sizeof(threshold[0]))) {
|
|
err = -EFAULT;
|
|
goto err_out;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
err = -ENOIOCTLCMD;
|
|
break;
|
|
}
|
|
err_out:
|
|
return err;
|
|
}
|
|
|
|
/*------------------misc device related operation functions-----------------*/
|
|
static const struct file_operations ltr303_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = ltr303_open,
|
|
.release = ltr303_release,
|
|
.unlocked_ioctl = ltr303_unlocked_ioctl,
|
|
};
|
|
|
|
static struct miscdevice ltr303_device = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "als_ps",
|
|
.fops = <r303_fops,
|
|
};
|
|
|
|
static int ltr303_init_client(void)
|
|
{
|
|
int res;
|
|
int init_als_gain;
|
|
struct i2c_client *client = ltr303_obj->client;
|
|
|
|
mdelay(PON_DELAY);
|
|
als_gainrange = ALS_RANGE_64K;
|
|
init_als_gain = als_gainrange;
|
|
APS_ERR("ALS sensor gainrange %d!\n", init_als_gain);
|
|
switch (init_als_gain) {
|
|
case ALS_RANGE_64K:
|
|
res = ltr303_i2c_write_reg(LTR303_ALS_CONTR, MODE_ALS_Range1);
|
|
break;
|
|
case ALS_RANGE_32K:
|
|
res = ltr303_i2c_write_reg(LTR303_ALS_CONTR, MODE_ALS_Range2);
|
|
break;
|
|
case ALS_RANGE_16K:
|
|
res = ltr303_i2c_write_reg(LTR303_ALS_CONTR, MODE_ALS_Range3);
|
|
break;
|
|
case ALS_RANGE_8K:
|
|
res = ltr303_i2c_write_reg(LTR303_ALS_CONTR, MODE_ALS_Range4);
|
|
break;
|
|
case ALS_RANGE_1300:
|
|
res = ltr303_i2c_write_reg(LTR303_ALS_CONTR, MODE_ALS_Range5);
|
|
break;
|
|
case ALS_RANGE_600:
|
|
res = ltr303_i2c_write_reg(LTR303_ALS_CONTR, MODE_ALS_Range6);
|
|
break;
|
|
default:
|
|
res = ltr303_i2c_write_reg(LTR303_ALS_CONTR, MODE_ALS_Range1);
|
|
break;
|
|
}
|
|
res = ltr303_als_enable(client, 1);
|
|
if (res < 0) {
|
|
APS_ERR("enable als fail: %d\n", res);
|
|
goto EXIT_ERR;
|
|
}
|
|
res = ltr303_setup_eint(client);
|
|
if (res != 0) {
|
|
APS_ERR("setup eint: %d\n", res);
|
|
goto EXIT_ERR;
|
|
}
|
|
return 0;
|
|
EXIT_ERR:
|
|
APS_ERR("init dev: %d\n", res);
|
|
return 1;
|
|
}
|
|
|
|
static int als_open_report_data(int open)
|
|
{
|
|
/*should queuq work to report event if is_report_input_direct=true*/
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************
|
|
* if use this typ of enable ,
|
|
* Sensor only enabled but not report inputEvent to HAL
|
|
*************************************************************/
|
|
static int als_enable_nodata(int en)
|
|
{
|
|
int res = 0;
|
|
|
|
APS_LOG("ltr303_obj als enable value = %d\n", en);
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return -1;
|
|
}
|
|
mutex_lock(<r303_mutex);
|
|
if (en)
|
|
set_bit(CMC_BIT_ALS, <r303_obj->enable);
|
|
else
|
|
clear_bit(CMC_BIT_ALS, <r303_obj->enable);
|
|
mutex_unlock(<r303_mutex);
|
|
res = ltr303_als_enable(ltr303_obj->client, en);
|
|
if (res) {
|
|
APS_ERR("%s is failed!!\n", __func__);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int als_set_delay(u64 ns)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int als_batch(int flag, int64_t samplingPeriodNs,
|
|
int64_t maxBatchReportLatencyNs)
|
|
{
|
|
return als_set_delay(samplingPeriodNs);
|
|
}
|
|
|
|
static int als_flush(void)
|
|
{
|
|
return als_flush_report();
|
|
}
|
|
|
|
static int als_get_data(int *value, int *status)
|
|
{
|
|
int err = 0;
|
|
|
|
if (!ltr303_obj) {
|
|
APS_ERR("ltr303_obj is null!!\n");
|
|
return -1;
|
|
}
|
|
ltr303_obj->als = ltr303_als_read(ltr303_obj->client, <r303_obj->als);
|
|
if (ltr303_obj->als < 0)
|
|
err = -1;
|
|
else {
|
|
*value = ltr303_get_als_value(ltr303_obj, ltr303_obj->als);
|
|
if (*value < 0)
|
|
err = -1;
|
|
*status = SENSOR_STATUS_ACCURACY_MEDIUM;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ps_open_report_data(int open)
|
|
{
|
|
/*should queuq work to report event if is_report_input_direct=true*/
|
|
return 0;
|
|
}
|
|
|
|
/* if this typ of enable , sensor only enable but not report Event to HAL*/
|
|
static int ps_enable_nodata(int en)
|
|
{
|
|
APS_LOG("ltr559_obj ps enable value = %d\n", en);
|
|
return 0;
|
|
}
|
|
|
|
static int ps_set_delay(u64 ns)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int ps_batch(int flag, int64_t samplingPeriodNs,
|
|
int64_t maxBatchReportLatencyNs)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int ps_flush(void)
|
|
{
|
|
return ps_flush_report();
|
|
}
|
|
|
|
static int ps_get_data(int *value, int *status)
|
|
{
|
|
int err = 0;
|
|
|
|
*value = 0;
|
|
*status = SENSOR_STATUS_ACCURACY_MEDIUM;
|
|
return err;
|
|
}
|
|
|
|
/*--------------------------------i2c operations--------------------------*/
|
|
static int ltr303_i2c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct ltr303_priv *obj = NULL;
|
|
struct als_control_path als_ctl = {0};
|
|
struct als_data_path als_data = {0};
|
|
struct ps_control_path ps_ctl = { 0 };
|
|
struct ps_data_path ps_data = { 0 };
|
|
int err = 0;
|
|
|
|
APS_FUN();
|
|
err = get_alsps_dts_func(client->dev.of_node, hw);
|
|
if (err < 0) {
|
|
APS_ERR("get customization info from dts failed\n");
|
|
return -EFAULT;
|
|
}
|
|
obj = kzalloc(sizeof(*obj), GFP_KERNEL);
|
|
if (!obj) {
|
|
err = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
memset(obj, 0, sizeof(*obj));
|
|
ltr303_obj = obj;
|
|
hw->i2c_addr[0] = 0x52;
|
|
client->addr = (0x52 >> 1);
|
|
obj->hw = hw;
|
|
INIT_WORK(&obj->eint_work, ltr303_eint_work);
|
|
obj->client = client;
|
|
i2c_set_clientdata(client, obj);
|
|
/*---------------value need to be confirmed--------------------*/
|
|
atomic_set(&obj->als_debounce, 300);
|
|
atomic_set(&obj->als_deb_on, 0);
|
|
atomic_set(&obj->als_deb_end, 0);
|
|
atomic_set(&obj->als_suspend, 0);
|
|
atomic_set(&obj->als_thd_val_high, obj->hw->als_threshold_high);
|
|
atomic_set(&obj->als_thd_val_low, obj->hw->als_threshold_low);
|
|
obj->irq_node = client->dev.of_node;
|
|
obj->enable = 0;
|
|
obj->pending_intr = 0;
|
|
obj->als_level_num = ARRAY_SIZE(obj->hw->als_level);
|
|
obj->als_value_num = ARRAY_SIZE(obj->hw->als_value);
|
|
obj->als_modulus = (400*100)/(16*150);
|
|
/*----------------value need to be confirmed-------------------*/
|
|
WARN_ON(sizeof(obj->als_level) != sizeof(obj->hw->als_level));
|
|
memcpy(obj->als_level, obj->hw->als_level, sizeof(obj->als_level));
|
|
WARN_ON(sizeof(obj->als_value) != sizeof(obj->hw->als_value));
|
|
memcpy(obj->als_value, obj->hw->als_value, sizeof(obj->als_value));
|
|
atomic_set(&obj->i2c_retry, 3);
|
|
set_bit(CMC_BIT_ALS, &obj->enable);
|
|
APS_LOG("ltr303_init_client() start...!\n");
|
|
ltr303_i2c_client = client;
|
|
err = ltr303_init_client();
|
|
if (err)
|
|
goto exit_init_failed;
|
|
|
|
APS_LOG("ltr303_init_client() OK!\n");
|
|
err = misc_register(<r303_device);
|
|
if (err) {
|
|
APS_ERR("ltr303_device register failed\n");
|
|
goto exit_misc_device_register_failed;
|
|
}
|
|
|
|
als_ctl.is_use_common_factory = false;
|
|
ps_ctl.is_use_common_factory = false;
|
|
err = ltr303_create_attr(&(ltr303_i2c_driver.driver));
|
|
if (err) {
|
|
APS_ERR("create attribute err = %d\n", err);
|
|
goto exit_create_attr_failed;
|
|
}
|
|
als_ctl.open_report_data = als_open_report_data;
|
|
als_ctl.enable_nodata = als_enable_nodata;
|
|
als_ctl.set_delay = als_set_delay;
|
|
als_ctl.batch = als_batch;
|
|
als_ctl.flush = als_flush;
|
|
als_ctl.is_report_input_direct = false;
|
|
als_ctl.is_support_batch = false;
|
|
err = als_register_control_path(&als_ctl);
|
|
if (err) {
|
|
APS_ERR("register fail = %d\n", err);
|
|
goto exit_sensor_obj_attach_fail;
|
|
}
|
|
als_data.get_data = als_get_data;
|
|
als_data.vender_div = 100;
|
|
err = als_register_data_path(&als_data);
|
|
if (err) {
|
|
APS_ERR("register fail = %d\n", err);
|
|
goto exit_sensor_obj_attach_fail;
|
|
}
|
|
ps_ctl.open_report_data = ps_open_report_data;
|
|
ps_ctl.enable_nodata = ps_enable_nodata;
|
|
ps_ctl.set_delay = ps_set_delay;
|
|
ps_ctl.batch = ps_batch;
|
|
ps_ctl.flush = ps_flush;
|
|
ps_ctl.is_report_input_direct = false;
|
|
ps_ctl.is_support_batch = false;
|
|
ps_ctl.is_polling_mode = hw->polling_mode_ps;
|
|
err = ps_register_control_path(&ps_ctl);
|
|
if (err) {
|
|
APS_ERR("register fail = %d\n", err);
|
|
goto exit_sensor_obj_attach_fail;
|
|
}
|
|
ps_data.get_data = ps_get_data;
|
|
ps_data.vender_div = 100;
|
|
err = ps_register_data_path(&ps_data);
|
|
if (err) {
|
|
APS_ERR("tregister fail = %d\n", err);
|
|
goto exit_sensor_obj_attach_fail;
|
|
}
|
|
|
|
ltr303_init_flag = 0;
|
|
APS_LOG("%s: OK\n", __func__);
|
|
return 0;
|
|
|
|
exit_create_attr_failed:
|
|
exit_sensor_obj_attach_fail:
|
|
exit_misc_device_register_failed:
|
|
misc_deregister(<r303_device);
|
|
exit_init_failed:
|
|
kfree(obj);
|
|
exit:
|
|
ltr303_i2c_client = NULL;
|
|
APS_ERR("%s: err = %d\n", __func__, err);
|
|
ltr303_init_flag = -1;
|
|
return err;
|
|
}
|
|
|
|
static int ltr303_i2c_remove(struct i2c_client *client)
|
|
{
|
|
int err;
|
|
|
|
err = ltr303_delete_attr(&(ltr303_i2c_driver.driver));
|
|
if (err)
|
|
APS_ERR("ltr303_delete_attr fail: %d\n", err);
|
|
|
|
misc_deregister(<r303_device);
|
|
ltr303_i2c_client = NULL;
|
|
i2c_unregister_device(client);
|
|
kfree(i2c_get_clientdata(client));
|
|
return 0;
|
|
}
|
|
|
|
static int ltr303_i2c_detect(struct i2c_client *client,
|
|
struct i2c_board_info *info)
|
|
{
|
|
strcpy(info->type, LTR303_DEV_NAME);
|
|
return 0;
|
|
}
|
|
|
|
static int ltr303_i2c_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct ltr303_priv *obj = i2c_get_clientdata(client);
|
|
int err;
|
|
|
|
APS_FUN();
|
|
if (!obj) {
|
|
APS_ERR("null pointer!!\n");
|
|
return -EINVAL;
|
|
}
|
|
atomic_set(&obj->als_suspend, 1);
|
|
err = ltr303_als_enable(obj->client, 0);
|
|
if (err < 0) {
|
|
APS_ERR("disable als: %d\n", err);
|
|
return err;
|
|
}
|
|
ltr303_power(obj->hw, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int ltr303_i2c_resume(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct ltr303_priv *obj = i2c_get_clientdata(client);
|
|
int err;
|
|
|
|
APS_FUN();
|
|
if (!obj) {
|
|
APS_ERR("null pointer!!\n");
|
|
return -EINVAL;
|
|
}
|
|
ltr303_power(obj->hw, 1);
|
|
atomic_set(&obj->als_suspend, 0);
|
|
|
|
if (test_bit(CMC_BIT_ALS, &obj->enable)) {
|
|
err = ltr303_als_enable(obj->client, 1);
|
|
if (err < 0)
|
|
APS_ERR("enable als fail: %d\n", err);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ltr303_remove(void)
|
|
{
|
|
APS_FUN();
|
|
ltr303_power(hw, 0);
|
|
i2c_del_driver(<r303_i2c_driver);
|
|
return 0;
|
|
}
|
|
|
|
static int ltr303_local_init(void)
|
|
{
|
|
APS_FUN();
|
|
ltr303_power(hw, 1);
|
|
if (i2c_add_driver(<r303_i2c_driver)) {
|
|
APS_ERR("add driver error\n");
|
|
return -1;
|
|
}
|
|
|
|
if (ltr303_init_flag == -1)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init ltr303_init(void)
|
|
{
|
|
alsps_driver_add(<r303_init_info);
|
|
return 0;
|
|
}
|
|
|
|
static void __exit ltr303_exit(void)
|
|
{
|
|
APS_FUN();
|
|
}
|
|
|
|
module_init(ltr303_init);
|
|
module_exit(ltr303_exit);
|
|
|
|
MODULE_AUTHOR("Liteon");
|
|
MODULE_DESCRIPTION("LTR-303ALS Driver");
|
|
MODULE_LICENSE("GPL");
|