Jetpack/kernel/nvidia/drivers/iio/light/nvs_jsa1127.c

757 lines
18 KiB
C

/* Copyright (c) 2014-2017, NVIDIA CORPORATION. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
/* The NVS = NVidia Sensor framework */
/* See nvs_iio.c and nvs.h for documentation */
/* See nvs_light.c and nvs_light.h for documentation */
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/regulator/consumer.h>
#include <linux/workqueue.h>
#include <linux/of.h>
#include <linux/nvs.h>
#include <linux/nvs_light.h>
#define JSA_VENDOR "SolteamOpto"
#define JSA_NAME "jsa1127"
#define JSA_LIGHT_VERSION (3)
#define JSA_LIGHT_MILLIAMP_IVAL (0)
#define JSA_LIGHT_MILLIAMP_FVAL (90000)
#define JSA_LIGHT_SCALE_IVAL (0)
#define JSA_LIGHT_SCALE_MICRO (10000)
#define JSA_LIGHT_THRESHOLD_LO (10)
#define JSA_LIGHT_THRESHOLD_HI (10)
#define JSA_POLL_DLY_MS_MAX (4000)
#define JSA_HW_DATA_MASK (0x7FFF)
/* HW registers */
#define JSA_CMD_SHUTDOWN (0x80)
#define JSA_CMD_EN_CONT (0x0C)
#define JSA_CMD_EN (0x04)
#define JSA_CMD_START (0x08)
#define JSA_CMD_STOP (0x30)
#define JSA_CMD_MASK (0x3F)
#define JSA_VAL_VALID (15)
#define JSA_HW_DELAY_MS (60)
/* regulator names in order of powering on */
static char *jsa_vregs[] = {
"vdd",
};
static unsigned short jsa_i2c_addrs[] = {
0x29,
0x39,
0x44,
};
static struct nvs_light_dynamic jsa_nld_tbl[] = {
{{0, 210000}, {6500, 0}, {0, 90000}, 800, 0},
{{0, 420000}, {13000, 0}, {0, 90000}, 400, 0},
{{0, 560000}, {18000, 0}, {0, 90000}, 300, 0},
{{0, 830000}, {27000, 0}, {0, 90000}, 200, 0},
{{1, 670000}, {54000, 0}, {0, 90000}, 100, 0},
{{3, 330000}, {109000, 0}, {0, 90000}, 50, 0}
};
struct jsa_state {
struct i2c_client *i2c;
struct nvs_fn_if *nvs;
void *nvs_st;
struct sensor_cfg cfg;
struct workqueue_struct *wq;
struct work_struct ws;
struct regulator_bulk_data vreg[ARRAY_SIZE(jsa_vregs)];
struct nvs_light light;
struct nvs_light_dynamic hnld[2]; /* hybrid NLD table */
unsigned int sts; /* status flags */
unsigned int errs; /* error count */
unsigned int enabled; /* enable status */
bool hw_it; /* HW defined integration time */
bool hnld_it; /* hybrid NLD table enable flag */
u16 i2c_addr; /* I2C address */
u8 rc_cmd; /* store for register dump */
};
static void jsa_err(struct jsa_state *st)
{
st->errs++;
if (!st->errs)
st->errs--;
}
static int jsa_i2c_rd(struct jsa_state *st, u16 *val)
{
struct i2c_msg msg;
int ret = 0;
msg.addr = st->i2c_addr;
msg.flags = I2C_M_RD;
msg.len = 2;
msg.buf = (__u8 *)val;
if (i2c_transfer(st->i2c->adapter, &msg, 1) == 1) {
*val = le16_to_cpup(val);
} else {
jsa_err(st);
ret = -EIO;
}
return ret;
}
static int jsa_i2c_wr(struct jsa_state *st, u8 val)
{
struct i2c_msg msg;
u8 buf[1];
int ret = 0;
if (st->i2c_addr) {
buf[0] = val;
msg.addr = st->i2c_addr;
msg.flags = 0;
msg.len = sizeof(buf);
msg.buf = buf;
if (i2c_transfer(st->i2c->adapter, &msg, 1) == 1) {
st->rc_cmd = val;
} else {
jsa_err(st);
ret = -EIO;
}
if (st->sts & NVS_STS_SPEW_MSG)
dev_info(&st->i2c->dev, "%s=%hhx err=%d\n",
__func__, val, ret);
}
return ret;
}
static int jsa_pm(struct jsa_state *st, bool enable)
{
int ret = 0;
if (enable) {
ret = nvs_vregs_enable(&st->i2c->dev, st->vreg,
ARRAY_SIZE(jsa_vregs));
if (ret)
mdelay(JSA_HW_DELAY_MS);
} else {
ret = nvs_vregs_sts(st->vreg, ARRAY_SIZE(jsa_vregs));
if ((ret < 0) || (ret == ARRAY_SIZE(jsa_vregs))) {
ret = jsa_i2c_wr(st, JSA_CMD_SHUTDOWN);
} else if (ret > 0) {
ret = nvs_vregs_enable(&st->i2c->dev, st->vreg,
ARRAY_SIZE(jsa_vregs));
mdelay(JSA_HW_DELAY_MS);
ret = jsa_i2c_wr(st, JSA_CMD_SHUTDOWN);
}
ret |= nvs_vregs_disable(&st->i2c->dev, st->vreg,
ARRAY_SIZE(jsa_vregs));
}
if (ret > 0)
ret = 0;
if (ret) {
dev_err(&st->i2c->dev, "%s pwr=%x ERR=%d\n",
__func__, enable, ret);
} else {
if (st->sts & NVS_STS_SPEW_MSG)
dev_info(&st->i2c->dev, "%s pwr=%x\n",
__func__, enable);
}
return ret;
}
static void jsa_pm_exit(struct jsa_state *st)
{
jsa_pm(st, false);
nvs_vregs_exit(&st->i2c->dev, st->vreg, ARRAY_SIZE(jsa_vregs));
}
static int jsa_pm_init(struct jsa_state *st)
{
int ret;
st->enabled = 0;
nvs_vregs_init(&st->i2c->dev,
st->vreg, ARRAY_SIZE(jsa_vregs), jsa_vregs);
ret = jsa_pm(st, true);
return ret;
}
static int jsa_start(struct jsa_state *st)
{
int ret;
if (st->hw_it) {
ret = jsa_i2c_wr(st, JSA_CMD_EN_CONT);
} else {
ret = jsa_i2c_wr(st, JSA_CMD_EN);
ret |= jsa_i2c_wr(st, JSA_CMD_START);
}
return ret;
}
static unsigned int jsa_sleep(struct jsa_state *st)
{
unsigned int ms;
if (st->hw_it)
return st->light.poll_delay_ms;
ms = st->light.delay_us / 1000;
if (ms > st->light.poll_delay_ms) {
jsa_i2c_wr(st, JSA_CMD_SHUTDOWN);
ms -= st->light.poll_delay_ms;
} else {
jsa_start(st);
ms = st->light.poll_delay_ms;
}
return ms;
}
static int jsa_rd(struct jsa_state *st)
{
s64 ts;
u16 hw;
unsigned int i;
int ret;
ret = jsa_i2c_rd(st, &hw);
if (ret)
return ret;
if (!(hw & (1 << JSA_VAL_VALID)))
/* data not ready */
return -EINVAL;
hw &= ~(1 << JSA_VAL_VALID);
ts = nvs_timestamp();
if (st->sts & NVS_STS_SPEW_DATA)
dev_info(&st->i2c->dev,
"%s hw: %hu %lld diff: %d %lldns nld_i(index)=%u hw_it=%x\n",
__func__, hw, ts, hw - st->light.hw,
ts - st->light.timestamp, st->light.nld_i, st->hw_it);
st->light.hw = hw;
st->light.timestamp = ts;
nvs_light_read(&st->light);
if (st->light.nld_i_change && st->hnld_it &&
!st->light.calibration_en) {
/* hybrid mode SW/HW IT switching */
i = st->light.nld_i;
if (st->hnld[i].driver_data) {
st->hw_it = false;
if (i == 0) {
/* SW IT is for the low range */
st->light.hw_mask = st->hnld[i].max_range.ival;
st->cfg.thresh_lo = 1;
st->cfg.thresh_hi = 1;
}
} else {
st->hw_it = true;
st->light.hw_mask = JSA_HW_DATA_MASK;
st->cfg.thresh_lo = JSA_LIGHT_THRESHOLD_LO;
st->cfg.thresh_hi = JSA_LIGHT_THRESHOLD_HI;
}
jsa_start(st);
}
return 0;
}
static void jsa_work(struct work_struct *ws)
{
struct jsa_state *st = container_of((struct work_struct *)ws,
struct jsa_state, ws);
unsigned int ms;
int ret;
ms = st->light.poll_delay_ms;
while (st->enabled) {
msleep(ms);
st->nvs->nvs_mutex_lock(st->nvs_st);
ms = st->light.poll_delay_ms;
if (st->hw_it) {
st->light.nld_i = 1;
jsa_rd(st);
} else {
st->light.nld_i = 0;
if (st->rc_cmd == JSA_CMD_START) {
ret = jsa_i2c_wr(st, JSA_CMD_STOP);
if (!ret) {
ret = jsa_rd(st);
if (ret)
jsa_start(st);
else
ms = jsa_sleep(st);
}
} else {
jsa_start(st);
}
}
st->nvs->nvs_mutex_unlock(st->nvs_st);
if (st->sts & NVS_STS_SPEW_MSG)
dev_info(&st->i2c->dev,
"%s schedule_delayed_work=%ums\n",
__func__, ms);
}
}
static int jsa_disable(struct jsa_state *st)
{
int ret;
ret = jsa_pm(st, false);
if (!ret)
st->enabled = 0;
return ret;
}
static int jsa_enable(void *client, int snsr_id, int enable)
{
struct jsa_state *st = (struct jsa_state *)client;
int ret;
dev_info(&st->i2c->dev, "%s\n", __func__);
if (enable < 0)
return st->enabled;
if (enable) {
ret = jsa_pm(st, true);
if (!ret) {
nvs_light_enable(&st->light);
ret = jsa_start(st);
if (ret) {
jsa_disable(st);
} else {
st->enabled = enable;
cancel_work_sync(&st->ws);
queue_work(st->wq, &st->ws);
}
}
} else {
ret = jsa_disable(st);
}
return ret;
}
static int jsa_batch(void *client, int snsr_id, int flags,
unsigned int period_us, unsigned int timeout_us)
{
struct jsa_state *st = (struct jsa_state *)client;
if (st->hnld_it && st->light.calibration_en) {
/* mechanism to test the hybrid SW integration time */
if (timeout_us) {
st->hw_it = false;
if (st->hnld[0].driver_data)
st->hnld[0].delay_min_ms = timeout_us;
else
st->hnld[1].delay_min_ms = timeout_us;
} else {
st->hw_it = true;
}
jsa_start(st);
} else if (timeout_us) {
/* timeout not supported (no HW FIFO) */
return -EINVAL;
}
st->light.delay_us = period_us;
return 0;
}
static int jsa_thresh_lo(void *client, int snsr_id, int thresh_lo)
{
struct jsa_state *st = (struct jsa_state *)client;
nvs_light_threshold_calibrate_lo(&st->light, thresh_lo);
return 0;
}
static int jsa_thresh_hi(void *client, int snsr_id, int thresh_hi)
{
struct jsa_state *st = (struct jsa_state *)client;
nvs_light_threshold_calibrate_hi(&st->light, thresh_hi);
return 0;
}
static int jsa_regs(void *client, int snsr_id, char *buf)
{
struct jsa_state *st = (struct jsa_state *)client;
ssize_t t;
u16 val;
int ret;
t = sprintf(buf, "registers:\n");
t += sprintf(buf + t, "CMD=%#2x\n", st->rc_cmd);
ret = jsa_i2c_rd(st, &val);
t += sprintf(buf + t, "VAL=%#4x ERR=%d\n", val, ret);
return t;
}
static int jsa_nvs_write(void *client, int snsr_id, unsigned int nvs)
{
if (nvs)
return -EINVAL;
return 0;
}
static int jsa_nvs_read(void *client, int snsr_id, char *buf)
{
struct jsa_state *st = (struct jsa_state *)client;
return nvs_light_dbg(&st->light, buf);
}
static struct nvs_fn_dev jsa_fn_dev = {
.enable = jsa_enable,
.batch = jsa_batch,
.thresh_lo = jsa_thresh_lo,
.thresh_hi = jsa_thresh_hi,
.regs = jsa_regs,
.nvs_write = jsa_nvs_write,
.nvs_read = jsa_nvs_read,
};
static int jsa_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct jsa_state *st = i2c_get_clientdata(client);
int ret = 0;
st->sts |= NVS_STS_SUSPEND;
if (st->nvs && st->nvs_st)
ret = st->nvs->suspend(st->nvs_st);
if (st->sts & NVS_STS_SPEW_MSG)
dev_info(&client->dev, "%s\n", __func__);
return ret;
}
static int jsa_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct jsa_state *st = i2c_get_clientdata(client);
int ret = 0;
if (st->nvs && st->nvs_st)
ret = st->nvs->resume(st->nvs_st);
st->sts &= ~NVS_STS_SUSPEND;
if (st->sts & NVS_STS_SPEW_MSG)
dev_info(&client->dev, "%s\n", __func__);
return ret;
}
static SIMPLE_DEV_PM_OPS(jsa_pm_ops, jsa_suspend, jsa_resume);
static void jsa_shutdown(struct i2c_client *client)
{
struct jsa_state *st = i2c_get_clientdata(client);
st->sts |= NVS_STS_SHUTDOWN;
if (st->nvs && st->nvs_st)
st->nvs->shutdown(st->nvs_st);
if (st->sts & NVS_STS_SPEW_MSG)
dev_info(&client->dev, "%s\n", __func__);
}
static int jsa_remove(struct i2c_client *client)
{
struct jsa_state *st = i2c_get_clientdata(client);
if (st != NULL) {
jsa_shutdown(client);
if (st->nvs && st->nvs_st)
st->nvs->remove(st->nvs_st);
if (st->wq) {
destroy_workqueue(st->wq);
st->wq = NULL;
}
jsa_pm_exit(st);
}
dev_info(&client->dev, "%s\n", __func__);
return 0;
}
static int jsa_id_dev(struct jsa_state *st, const char *name)
{
u16 val;
int ret = 0;
if (name == NULL) {
ret = jsa_i2c_rd(st, &val);
if (!ret)
dev_info(&st->i2c->dev, "%s %hx responded\n",
__func__, st->i2c_addr);
}
return ret;
}
static int jsa_id_i2c(struct jsa_state *st, const char *name)
{
int i;
int ret;
for (i = 0; i < ARRAY_SIZE(jsa_i2c_addrs); i++) {
if (st->i2c->addr == jsa_i2c_addrs[i])
break;
}
if (i < ARRAY_SIZE(jsa_i2c_addrs)) {
st->i2c_addr = st->i2c->addr;
ret = jsa_id_dev(st, name);
} else {
name = NULL;
for (i = 0; i < ARRAY_SIZE(jsa_i2c_addrs); i++) {
st->i2c_addr = jsa_i2c_addrs[i];
ret = jsa_id_dev(st, name);
if (!ret)
break;
}
}
if (ret)
st->i2c_addr = 0;
return ret;
}
static struct sensor_cfg jsa_cfg_dflt = {
.name = NVS_LIGHT_STRING,
.ch_n = 1,
.ch_sz = 4,
.part = JSA_NAME,
.vendor = JSA_VENDOR,
.version = JSA_LIGHT_VERSION,
.milliamp = {
.ival = JSA_LIGHT_MILLIAMP_IVAL,
.fval = JSA_LIGHT_MILLIAMP_FVAL,
},
.delay_us_max = JSA_POLL_DLY_MS_MAX * 1000,
.flags = SENSOR_FLAG_ON_CHANGE_MODE,
.scale = {
.ival = JSA_LIGHT_SCALE_IVAL,
.fval = JSA_LIGHT_SCALE_MICRO,
},
.thresh_lo = JSA_LIGHT_THRESHOLD_LO,
.thresh_hi = JSA_LIGHT_THRESHOLD_HI,
};
static int jsa_of_dt(struct jsa_state *st, struct device_node *dn)
{
unsigned int i;
int ret = 0;
/* default NVS programmable parameters */
memcpy(&st->cfg, &jsa_cfg_dflt, sizeof(st->cfg));
st->light.cfg = &st->cfg;
st->light.hw_mask = JSA_HW_DATA_MASK;
st->light.nld_tbl = jsa_nld_tbl;
/* device tree parameters */
/* common NVS parameters */
ret = nvs_of_dt(dn, &st->cfg, NULL);
if (ret == -ENODEV)
return -ENODEV;
/* this device supports these programmable parameters */
ret = nvs_light_of_dt(&st->light, dn, NULL);
if (ret) {
/* default is HW IT */
st->light.nld_i_lo = 0;
st->light.nld_i_hi = 0;
}
if (st->light.nld_i_lo == st->light.nld_i_hi) {
/* HW IT is enabled when indexes are the same.
* The values used are those in the jsa_nld_tbl pointed to by
* the common index (both nld_i_lo and nld_i_hi).
*/
st->light.nld_i = st->light.nld_i_lo;
st->hw_it = true;
/* disable dynamic resolution */
st->light.nld_tbl = NULL;
/* Initialize default dynamic parameter table */
/* Overlay device-specific dynamic parameters, where necessary*/
if (dn) {
of_property_read_u32(dn, "light_hwm_maxrange_ival",
&jsa_nld_tbl[st->light.nld_i].max_range.ival);
of_property_read_u32(dn, "light_hwm_maxrange_fval",
&jsa_nld_tbl[st->light.nld_i].max_range.fval);
of_property_read_u32(dn, "light_hwm_resolution_ival",
&jsa_nld_tbl[st->light.nld_i].resolution.ival);
of_property_read_u32(dn, "light_hwm_resolution_fval",
&jsa_nld_tbl[st->light.nld_i].resolution.fval);
of_property_read_u32(dn, "light_hwm_interval",
&jsa_nld_tbl[st->light.nld_i].delay_min_ms);
}
/* Initialize default hybrid parameter table */
/* memcpy(&st->hnld[0], &jsa_hnld_tbl, sizeof(st->hnld[0]));*/
/* Overlay device-specific hybrid parameters, where necessary*/
if (dn) {
of_property_read_u32(dn, "light_hyb_maxrange_ival",
&st->hnld[0].max_range.ival);
of_property_read_u32(dn, "light_hyb_maxrange_fval",
&st->hnld[0].max_range.fval);
of_property_read_u32(dn, "light_hyb_resolution_ival",
&st->hnld[0].resolution.ival);
of_property_read_u32(dn, "light_hyb_resolution_fval",
&st->hnld[0].resolution.fval);
of_property_read_u32(dn, "light_hyb_interval",
&st->hnld[0].delay_min_ms);
}
}
i = st->light.nld_i_lo; /* use lowest resolution */
st->cfg.resolution.ival = jsa_nld_tbl[i].resolution.ival;
st->cfg.resolution.fval = jsa_nld_tbl[i].resolution.fval;
st->cfg.delay_us_min = jsa_nld_tbl[i].delay_min_ms * 1000;
/* test if hyrbrid mode enabled */
if (st->hnld[0].max_range.ival || st->hnld[0].max_range.fval) {
/* Hybrid mode uses a both HW integration time (hw_it = true)
* and SW integration time. It's used to help obtain the low
* or high range values of the fixed HW integration time.
* HW IT uses values from the fixed indexed jsa_nld_tbl.
* SW IT uses a custom entry in the hybrid nvs_light_dynamic
* table st->hnld before or after the HW IT entry.
* Therefore, this hybrid dynamic resolution/max_range table
* has only two entries.
*/
st->hnld[0].milliamp.ival = JSA_LIGHT_MILLIAMP_IVAL;
st->hnld[0].milliamp.fval = JSA_LIGHT_MILLIAMP_FVAL;
st->hnld[0].driver_data = 1; /* flag the SW entry */
/* copy SW IT entry to other entry */
memcpy(&st->hnld[1], &st->hnld[0], sizeof(st->hnld[1]));
/* copy the HW IT to hybrid nld table in range order */
if (jsa_nld_tbl[st->light.nld_i].max_range.ival <
st->hnld[0].max_range.ival)
i = 0;
else
i = 1;
memcpy(&st->hnld[i], &jsa_nld_tbl[st->light.nld_i],
sizeof(st->hnld[i]));
st->light.nld_i = i;
st->light.nld_i_lo = 0;
st->light.nld_i_hi = 1;
st->light.nld_tbl = st->hnld;
st->hnld_it = true;
}
i = st->light.nld_i_hi; /* use highest range & delay */
st->cfg.max_range.ival = jsa_nld_tbl[i].max_range.ival;
st->cfg.max_range.fval = jsa_nld_tbl[i].max_range.fval;
return 0;
}
static int jsa_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct jsa_state *st;
int ret;
dev_info(&client->dev, "%s\n", __func__);
st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
if (st == NULL) {
dev_err(&client->dev, "%s devm_kzalloc ERR\n", __func__);
return -ENOMEM;
}
i2c_set_clientdata(client, st);
st->i2c = client;
ret = jsa_of_dt(st, client->dev.of_node);
if (ret) {
if (ret == -ENODEV) {
dev_info(&client->dev, "%s DT disabled\n", __func__);
} else {
dev_err(&client->dev, "%s _of_dt ERR\n", __func__);
ret = -ENODEV;
}
goto jsa_probe_exit;
}
jsa_pm_init(st);
ret = jsa_id_i2c(st, id->name);
if (ret) {
dev_err(&client->dev, "%s _id_i2c ERR\n", __func__);
ret = -ENODEV;
goto jsa_probe_exit;
}
jsa_fn_dev.errs = &st->errs;
jsa_fn_dev.sts = &st->sts;
st->nvs = nvs_iio();
if (st->nvs == NULL) {
dev_err(&client->dev, "%s nvs_iio ERR\n", __func__);
ret = -ENODEV;
goto jsa_probe_exit;
}
st->light.handler = st->nvs->handler;
ret = st->nvs->probe(&st->nvs_st, st, &client->dev,
&jsa_fn_dev, &st->cfg);
if (ret) {
dev_err(&client->dev, "%s nvs_probe ERR\n", __func__);
ret = -ENODEV;
goto jsa_probe_exit;
}
st->light.nvs_st = st->nvs_st;
st->wq = create_workqueue(JSA_NAME);
if (!st->wq) {
dev_err(&client->dev, "%s create_workqueue ERR\n", __func__);
ret = -ENOMEM;
goto jsa_probe_exit;
}
INIT_WORK(&st->ws, jsa_work);
dev_info(&client->dev, "%s done\n", __func__);
return 0;
jsa_probe_exit:
jsa_remove(client);
return ret;
}
static const struct i2c_device_id jsa_i2c_device_id[] = {
{ JSA_NAME, 0 },
{}
};
MODULE_DEVICE_TABLE(i2c, jsa_i2c_device_id);
static const struct of_device_id jsa_of_match[] = {
{ .compatible = "solteamopto,jsa1127", },
{},
};
MODULE_DEVICE_TABLE(of, jsa_of_match);
static struct i2c_driver jsa_driver = {
.class = I2C_CLASS_HWMON,
.probe = jsa_probe,
.remove = jsa_remove,
.shutdown = jsa_shutdown,
.driver = {
.name = JSA_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(jsa_of_match),
.pm = &jsa_pm_ops,
},
.id_table = jsa_i2c_device_id,
};
module_i2c_driver(jsa_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("JSA1127 driver");
MODULE_AUTHOR("NVIDIA Corporation");