/* 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 #include #include #include #include #include #include #include #include #include #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");