/* 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 */ /* See nvs_proximity.c and nvs_proximity.h for documentation */ #include #include #include #include #include #include #include #include #include #include #include #include #define LTR_DRIVER_VERSION (3) #define LTR_VENDOR "Lite-On Technology Corp." #define LTR_NAME "ltrX5X" #define LTR_NAME_LTR558ALS "ltr558als" #define LTR_NAME_LTR659PS "ltr659ps" #define LTR_DEVID_558ALS (0x80) #define LTR_DEVID_659PS (0x90) #define LTR_HW_DELAY_MS (600) #define LTR_WAKEUP_DELAY_MS (10) #define LTR_POLL_DLY_MS_DFLT (2000) #define LTR_POLL_DLY_MS_MIN (100) #define LTR_POLL_DLY_MS_MAX (60000) #define LTR_REG_PS_CONTR_DFLT (0x00) #define LTR_REG_PS_LED_DFLT (0x04) #define LTR_REG_PS_N_PULSES_DFLT (0x7F) #define LTR_REG_PS_MEAS_RATE_DFLT (0x05) #define LTR_REG_ALS_MEAS_RATE_DFLT (0x04) #define LTR_REG_INTERRUPT_PERSIST_DFLT (0x00) /* light defines */ #define LTR_LIGHT_VERSION (1) #define LTR_LIGHT_MAX_RANGE_IVAL (14323) #define LTR_LIGHT_MAX_RANGE_MICRO (0) #define LTR_LIGHT_RESOLUTION_IVAL (0) #define LTR_LIGHT_RESOLUTION_MICRO (14000) #define LTR_LIGHT_MILLIAMP_IVAL (0) #define LTR_LIGHT_MILLIAMP_MICRO (13500) #define LTR_LIGHT_SCALE_IVAL (0) #define LTR_LIGHT_SCALE_MICRO (10000) #define LTR_LIGHT_THRESHOLD_DFLT (50) /* proximity defines */ #define LTR_PROX_THRESHOLD_LO (1000) #define LTR_PROX_THRESHOLD_HI (2000) #define LTR_PROX_VERSION (1) /* setting max_range and resolution to 1.0 = binary proximity */ #define LTR_PROX_MAX_RANGE_IVAL (1) #define LTR_PROX_MAX_RANGE_MICRO (0) #define LTR_PROX_RESOLUTION_IVAL (1) #define LTR_PROX_RESOLUTION_MICRO (0) #define LTR_PROX_MILLIAMP_IVAL (10) #define LTR_PROX_MILLIAMP_MICRO (250000) #define LTR_PROX_SCALE_IVAL (0) #define LTR_PROX_SCALE_MICRO (0) #define LTR_PROX_OFFSET_IVAL (0) #define LTR_PROX_OFFSET_MICRO (0) /* HW registers */ #define LTR_REG_ALS_CONTR (0x80) #define LTR_REG_ALS_CONTR_MODE (1) #define LTR_REG_ALS_CONTR_SW_RESET (2) #define LTR_REG_ALS_CONTR_GAIN (3) #define LTR_REG_PS_CONTR (0x81) #define LTR_REG_PS_CONTR_MODE (1) #define LTR_REG_PS_CONTR_GAIN (2) #define LTR_REG_PS_CONTR_SAT_EN (5) #define LTR_REG_PS_CONTR_POR_MASK (0x2C) #define LTR_REG_PS_LED (0x82) #define LTR_REG_PS_N_PULSES (0x83) #define LTR_REG_PS_MEAS_RATE (0x84) #define LTR_REG_ALS_MEAS_RATE (0x85) #define LTR_REG_PART_ID (0x86) #define LTR_REG_PART_ID_MASK (0xF0) #define LTR_REG_MANUFAC_ID (0x87) #define LTR_REG_MANUFAC_ID_VAL (0x05) #define LTR_REG_ALS_DATA_CH1_0 (0x88) #define LTR_REG_ALS_DATA_CH1_1 (0x89) #define LTR_REG_ALS_DATA_CH0_0 (0x8A) #define LTR_REG_ALS_DATA_CH0_1 (0x8B) #define LTR_REG_STATUS (0x8C) #define LTR_REG_STATUS_DATA_PS (0) #define LTR_REG_STATUS_IRQ_PS (1) #define LTR_REG_STATUS_DATA_ALS (2) #define LTR_REG_STATUS_IRQ_ALS (3) #define LTR_REG_STATUS_DATA_MASK (0x05) #define LTR_REG_PS_DATA_0 (0x8D) #define LTR_REG_PS_DATA_1 (0x8E) #define LTR_REG_PS_DATA_MASK (0x07FF) #define LTR_REG_PS_DATA_SAT (15) #define LTR_REG_INTERRUPT (0x8F) #define LTR_REG_INTERRUPT_PS_EN (0) #define LTR_REG_INTERRUPT_ALS_EN (1) #define LTR_REG_INTERRUPT_MODE_MASK (0x03) #define LTR_REG_INTERRUPT_POLARITY (2) #define LTR_REG_PS_THRES_UP_0 (0x90) #define LTR_REG_PS_THRES_UP_1 (0x91) #define LTR_REG_PS_THRES_LOW_0 (0x92) #define LTR_REG_PS_THRES_LOW_1 (0x93) #define LTR_REG_PS_OFFSET_1 (0x94) #define LTR_REG_PS_OFFSET_0 (0x95) #define LTR_REG_ALS_THRES_UP_0 (0x97) #define LTR_REG_ALS_THRES_UP_1 (0x98) #define LTR_REG_ALS_THRES_LOW_0 (0x99) #define LTR_REG_ALS_THRES_LOW_1 (0x9A) #define LTR_REG_INTERRUPT_PERSIST (0x9E) #define LTR_REG_INTERRUPT_PERSIST_MASK (0xF0) /* devices */ #define LTR_DEV_LIGHT (0) #define LTR_DEV_PROX (1) #define LTR_DEV_N (2) /* regulator names in order of powering on */ static char *ltr_vregs[] = { "vdd", "vled" }; #define LTR_PM_ON (1) #define LTR_PM_LED (ARRAY_SIZE(ltr_vregs)) static u8 ltr_ids[] = { LTR_DEVID_558ALS, LTR_DEVID_659PS, }; static unsigned short ltr_i2c_addrs[] = { 0x23, }; static struct nvs_light_dynamic ltr_nld_tbl[] = { {{0, 10000}, {327, 670000}, {0, 90000}, 100, 0}, {{2, 0}, {65534, 0}, {0, 90000}, 100, 0}, }; struct ltr_state { struct i2c_client *i2c; struct nvs_fn_if *nvs; void *nvs_st[LTR_DEV_N]; struct sensor_cfg cfg[LTR_DEV_N]; struct workqueue_struct *wq; struct work_struct ws; struct regulator_bulk_data vreg[ARRAY_SIZE(ltr_vregs)]; struct nvs_light light; struct nvs_proximity prox; unsigned int sts; /* status flags */ unsigned int errs; /* error count */ unsigned int enabled; /* enable status */ bool irq_set_irq_wake; /* IRQ suspend active */ u16 i2c_addr; /* I2C address */ u8 dev_id; /* device ID */ u8 ps_contr; /* PS_CONTR register default */ u8 ps_led; /* PS_LED register default */ u8 ps_n_pulses; /* PS_N_PULSES register default */ u8 ps_meas_rate; /* PS_MEAS_RATE register default */ u8 als_meas_rate; /* ALS_CONTR register default */ u8 interrupt; /* INTERRUPT register default */ u8 interrupt_persist; /* INTERRUPT_PERSIST reg default */ u8 rc_als_contr; /* cache of ALS_CONTR */ u8 rc_ps_contr; /* cache of PS_CONTR */ u8 rc_interrupt; /* cache of INTERRUPT */ }; static void ltr_err(struct ltr_state *st) { st->errs++; if (!st->errs) st->errs--; } static void ltr_mutex_lock(struct ltr_state *st) { unsigned int i; if (st->nvs) { for (i = 0; i < LTR_DEV_N; i++) { if (st->nvs_st[i]) st->nvs->nvs_mutex_lock(st->nvs_st[i]); } } } static void ltr_mutex_unlock(struct ltr_state *st) { unsigned int i; if (st->nvs) { for (i = 0; i < LTR_DEV_N; i++) { if (st->nvs_st[i]) st->nvs->nvs_mutex_unlock(st->nvs_st[i]); } } } static int ltr_i2c_read(struct ltr_state *st, u8 reg, u16 len, u8 *val) { struct i2c_msg msg[2]; msg[0].addr = st->i2c_addr; msg[0].flags = 0; msg[0].len = 1; msg[0].buf = ® msg[1].addr = st->i2c_addr; msg[1].flags = I2C_M_RD; msg[1].len = len; msg[1].buf = val; if (i2c_transfer(st->i2c->adapter, msg, 2) != 2) { ltr_err(st); return -EIO; } return 0; } static int ltr_i2c_rd(struct ltr_state *st, u8 reg, u8 *val) { return ltr_i2c_read(st, reg, 1, val); } static int ltr_i2c_write(struct ltr_state *st, u16 len, u8 *buf) { struct i2c_msg msg; if (st->i2c_addr) { msg.addr = st->i2c_addr; msg.flags = 0; msg.len = len; msg.buf = buf; if (i2c_transfer(st->i2c->adapter, &msg, 1) != 1) { ltr_err(st); return -EIO; } } return 0; } static int ltr_i2c_wr(struct ltr_state *st, u8 reg, u8 val) { u8 buf[2]; buf[0] = reg; buf[1] = val; return ltr_i2c_write(st, sizeof(buf), buf); } static int ltr_reset_sw(struct ltr_state *st) { u8 buf[5]; u8 reset; int ret; reset = 1 << LTR_REG_ALS_CONTR_SW_RESET; if (st->dev_id == LTR_DEVID_659PS) reset >>= 1; ret = ltr_i2c_wr(st, LTR_REG_ALS_CONTR, reset); if (!ret) { mdelay(LTR_HW_DELAY_MS); st->rc_als_contr = 0; st->rc_ps_contr = 0; st->rc_interrupt = 0; } buf[0] = LTR_REG_PS_LED; buf[1] = st->ps_led; buf[2] = st->ps_n_pulses; buf[3] = st->ps_meas_rate; buf[4] = st->als_meas_rate; ret |= ltr_i2c_write(st, sizeof(buf), buf); ret |= ltr_i2c_wr(st, LTR_REG_INTERRUPT_PERSIST, st->interrupt_persist); return ret; } static int ltr_pm(struct ltr_state *st, unsigned int en_msk) { unsigned int vreg_n; unsigned int vreg_n_dis; int ret = 0; if (en_msk) { if (en_msk & (1 << LTR_DEV_PROX)) vreg_n = LTR_PM_LED; else vreg_n = LTR_PM_ON; ret = nvs_vregs_enable(&st->i2c->dev, st->vreg, vreg_n); if (ret > 0) mdelay(LTR_HW_DELAY_MS); ret = ltr_reset_sw(st); vreg_n_dis = ARRAY_SIZE(ltr_vregs) - vreg_n; if (vreg_n_dis) ret |= nvs_vregs_disable(&st->i2c->dev, &st->vreg[vreg_n], vreg_n_dis); } else { ret = nvs_vregs_sts(st->vreg, LTR_PM_ON); if ((ret < 0) || (ret == LTR_PM_ON)) { ret = ltr_i2c_wr(st, LTR_REG_ALS_CONTR, 0); ret |= ltr_i2c_wr(st, LTR_REG_PS_CONTR, 0); } else if (ret > 0) { nvs_vregs_enable(&st->i2c->dev, st->vreg, LTR_PM_ON); mdelay(LTR_HW_DELAY_MS); ret = ltr_i2c_wr(st, LTR_REG_ALS_CONTR, 0); ret |= ltr_i2c_wr(st, LTR_REG_PS_CONTR, 0); } ret |= nvs_vregs_disable(&st->i2c->dev, st->vreg, ARRAY_SIZE(ltr_vregs)); } if (ret > 0) ret = 0; if (ret) { dev_err(&st->i2c->dev, "%s en_msk=%x ERR=%d\n", __func__, en_msk, ret); } else { if (st->sts & NVS_STS_SPEW_MSG) dev_info(&st->i2c->dev, "%s en_msk=%x\n", __func__, en_msk); } return ret; } static void ltr_pm_exit(struct ltr_state *st) { ltr_pm(st, 0); nvs_vregs_exit(&st->i2c->dev, st->vreg, ARRAY_SIZE(ltr_vregs)); } static int ltr_pm_init(struct ltr_state *st) { int ret; st->enabled = 0; nvs_vregs_init(&st->i2c->dev, st->vreg, ARRAY_SIZE(ltr_vregs), ltr_vregs); ret = ltr_pm(st, (1 << LTR_DEV_N)); return ret; } static int ltr_interrupt_wr(struct ltr_state *st, u8 interrupt) { int ret = 0; if (interrupt != st->interrupt) { ret = ltr_i2c_wr(st, LTR_REG_INTERRUPT, interrupt); if (st->sts & NVS_STS_SPEW_MSG) dev_info(&st->i2c->dev, "%s irq %hhx->%hhx err=%d\n", __func__, st->rc_interrupt, interrupt, ret); if (!ret) st->rc_interrupt = interrupt; } return ret; } static int ltr_cmd_wr(struct ltr_state *st, unsigned int enable, bool irq_en) { u8 interrupt; u8 als_contr; u8 ps_contr; int ret; int ret_t = 0; if (enable & (1 << LTR_DEV_LIGHT)) { als_contr = st->light.nld_i << LTR_REG_ALS_CONTR_GAIN; als_contr |= 1 << LTR_REG_ALS_CONTR_MODE; } else { als_contr = 0; } if (st->rc_als_contr != als_contr) { ret = ltr_i2c_wr(st, LTR_REG_ALS_CONTR, als_contr); if (ret) ret_t |= ret; else st->rc_als_contr = als_contr; } ps_contr = st->ps_contr; if (enable & (1 << LTR_DEV_PROX)) ps_contr |= 1 << LTR_REG_PS_CONTR_MODE; if (st->rc_ps_contr != ps_contr) { ret = ltr_i2c_wr(st, LTR_REG_PS_CONTR, ps_contr); if (ret) ret_t |= ret; else st->rc_ps_contr = ps_contr; } if (st->i2c->irq > 0) { interrupt = st->interrupt; if (irq_en) { if (enable & (1 << LTR_DEV_LIGHT)) interrupt |= (1 << LTR_REG_INTERRUPT_ALS_EN); if (enable & (1 << LTR_DEV_PROX)) interrupt |= (1 << LTR_REG_INTERRUPT_PS_EN); } ret_t |= ltr_interrupt_wr(st, interrupt); } if ((st->rc_interrupt & LTR_REG_INTERRUPT_MODE_MASK) && !ret_t) ret_t = 1; /* flag IRQ enabled */ if (st->sts & NVS_STS_SPEW_MSG) dev_info(&st->i2c->dev, "%s als=%hhx ps=%hhx ret=%d\n", __func__, st->rc_als_contr, st->rc_ps_contr, ret_t); return ret_t; } static int ltr_thr_wr(struct ltr_state *st, u8 reg, u16 thr_lo, u16 thr_hi) { u8 buf[5]; int ret; ret = ltr_interrupt_wr(st, st->interrupt); /* irq disable */ if (st->i2c->irq > 0) { buf[0] = reg; buf[1] = thr_hi & 0xFF; buf[2] = thr_hi >> 8; buf[3] = thr_lo & 0xFF; buf[4] = thr_lo >> 8; ret |= ltr_i2c_write(st, sizeof(buf), buf); if (st->sts & NVS_STS_SPEW_MSG) dev_info(&st->i2c->dev, "%s reg=%hhx lo=%hd hi=%hd ret=%d\n", __func__, reg, thr_lo, thr_hi, ret); } return ret; } static int ltr_rd_light(struct ltr_state *st, s64 ts) { u32 hw; u16 hw0; u16 hw1; unsigned int divisor; int ratio; int ch0_coeff = 1; int ch1_coeff = 1; int ret; ret = ltr_i2c_read(st, LTR_REG_ALS_DATA_CH0_0, 2, (u8 *)&hw0); if (ret) return ret; hw0 = be16_to_cpu(hw0); ret = ltr_i2c_read(st, LTR_REG_ALS_DATA_CH1_0, 2, (u8 *)&hw1); if (ret) return ret; hw1 = be16_to_cpu(hw1); /* The code from here to the next comment is from a previous driver. * It appears to do all the resolution calculations that the NVS light * module would do. A 558 part wasn't available when this was written, * so either this code is removed and use the nvs_light_dynamic table * along with interpolation calibration or clear the * nvs_light_dynamic.resolution to 1.0. * Note that scale will divide the final result by 100 in user space. */ divisor = hw0 + hw1; if (divisor) { ratio = (hw1 * 100) / divisor; if (ratio < 45) { ch0_coeff = 17743; ch1_coeff = -11059; } else if ((ratio >= 45) && (ratio < 64)) { ch0_coeff = 37725; ch1_coeff = 13363; } else if ((ratio >= 64) && (ratio < 85)) { ch0_coeff = 16900; ch1_coeff = 1690; } else if (ratio >= 85) { ch0_coeff = 0; ch1_coeff = 0; } hw = ((hw0 * ch0_coeff) - (hw1 * ch1_coeff)) / 100; } else { hw = 0; } /* next comment */ if (st->sts & NVS_STS_SPEW_DATA) dev_info(&st->i2c->dev, "poll light hw %u %lld diff=%d %lldns index=%u\n", hw, ts, hw - st->light.hw, ts - st->light.timestamp, st->light.nld_i); st->light.hw = hw; st->light.timestamp = ts; ret = nvs_light_read(&st->light); if (ret < 1) /* either poll or nothing to do */ return ret; ret = ltr_thr_wr(st, LTR_REG_ALS_THRES_UP_0, st->light.hw_thresh_lo, st->light.hw_thresh_hi); return ret; } static int ltr_rd_prox(struct ltr_state *st, s64 ts) { u16 hw; int ret; ret = ltr_i2c_read(st, LTR_REG_PS_DATA_0, 2, (u8 *)&hw); if (ret) return ret; hw = le16_to_cpu(hw); hw &= LTR_REG_PS_DATA_MASK; if (st->sts & NVS_STS_SPEW_DATA) dev_info(&st->i2c->dev, "poll proximity hw %hu %lld diff=%d %lldns\n", hw, ts, hw - st->prox.hw, ts - st->prox.timestamp); st->prox.hw = hw; st->prox.timestamp = ts; ret = nvs_proximity_read(&st->prox); if (ret < 1) /* either poll or nothing to do */ return ret; ret = ltr_thr_wr(st, LTR_REG_PS_THRES_UP_0, st->prox.hw_thresh_lo, st->prox.hw_thresh_hi); return ret; } static int ltr_en(struct ltr_state *st, unsigned int enable) { if (enable & (1 << LTR_DEV_LIGHT)) nvs_light_enable(&st->light); if (enable & (1 << LTR_DEV_PROX)) nvs_proximity_enable(&st->prox); return ltr_cmd_wr(st, enable, false); } static int ltr_rd(struct ltr_state *st) { s64 ts; u8 sts; int ret = 0; /* clear possible IRQ */ ret = ltr_i2c_rd(st, LTR_REG_STATUS, &sts); if (ret) return ret; if (sts & LTR_REG_STATUS_DATA_MASK) { ts = nvs_timestamp(); if (st->enabled & (1 << LTR_DEV_PROX)) ret |= ltr_rd_prox(st, ts); if (st->enabled & (1 << LTR_DEV_LIGHT)) ret |= ltr_rd_light(st, ts); } else { ret = RET_POLL_NEXT; } if (ret < 0) /* poll if error or more reporting */ ret = ltr_cmd_wr(st, st->enabled, false); else ret = ltr_cmd_wr(st, st->enabled, true); return ret; } static unsigned int ltr_polldelay(struct ltr_state *st) { unsigned int poll_delay_ms = LTR_POLL_DLY_MS_DFLT; if (st->enabled & (1 << LTR_DEV_LIGHT)) poll_delay_ms = st->light.poll_delay_ms; if (st->enabled & (1 << LTR_DEV_PROX)) { if (poll_delay_ms > st->prox.poll_delay_ms) poll_delay_ms = st->prox.poll_delay_ms; } return poll_delay_ms; } static int ltr_read(struct ltr_state *st) { int ret; ltr_mutex_lock(st); ret = ltr_rd(st); ltr_mutex_unlock(st); return ret; } static void ltr_work(struct work_struct *ws) { struct ltr_state *st = container_of((struct work_struct *)ws, struct ltr_state, ws); int ret; while (st->enabled) { msleep(ltr_polldelay(st)); ret = ltr_read(st); if (ret == RET_HW_UPDATE) /* switch to IRQ driven */ break; } } static irqreturn_t ltr_irq_thread(int irq, void *dev_id) { struct ltr_state *st = (struct ltr_state *)dev_id; int ret; if (st->sts & NVS_STS_SPEW_IRQ) dev_info(&st->i2c->dev, "%s\n", __func__); if (st->enabled) { ret = ltr_read(st); if (ret < RET_HW_UPDATE) { /* switch to polling */ cancel_work_sync(&st->ws); queue_work(st->wq, &st->ws); } } return IRQ_HANDLED; } static int ltr_disable(struct ltr_state *st, int snsr_id) { bool disable = true; int ret = 0; if (snsr_id >= 0) { if (st->enabled & ~(1 << snsr_id)) { st->enabled &= ~(1 << snsr_id); disable = false; if (snsr_id == LTR_DEV_LIGHT) ret = ltr_i2c_wr(st, LTR_REG_ALS_CONTR, 0); else if (snsr_id == LTR_DEV_PROX) ret = ltr_i2c_wr(st, LTR_REG_PS_CONTR, 0); ret |= ltr_pm(st, st->enabled); } } if (disable) { ret |= ltr_pm(st, 0); if (!ret) st->enabled = 0; } return ret; } static int ltr_enable(void *client, int snsr_id, int enable) { struct ltr_state *st = (struct ltr_state *)client; int ret; if (enable < 0) return st->enabled & (1 << snsr_id); if (enable) { enable = st->enabled | (1 << snsr_id); ret = ltr_pm(st, enable); if (!ret) { ret = ltr_en(st, enable); if (ret < 0) { ltr_disable(st, snsr_id); } else { st->enabled = enable; cancel_work_sync(&st->ws); queue_work(st->wq, &st->ws); } } } else { ret = ltr_disable(st, snsr_id); } return ret; } static int ltr_batch(void *client, int snsr_id, int flags, unsigned int period, unsigned int timeout) { struct ltr_state *st = (struct ltr_state *)client; if (timeout) /* timeout not supported (no HW FIFO) */ return -EINVAL; if (snsr_id == LTR_DEV_LIGHT) st->light.delay_us = period; else if (snsr_id == LTR_DEV_PROX) st->prox.delay_us = period; return 0; } static int ltr_thresh_lo(void *client, int snsr_id, int thresh_lo) { struct ltr_state *st = (struct ltr_state *)client; if (snsr_id == LTR_DEV_LIGHT) nvs_light_threshold_calibrate_lo(&st->light, thresh_lo); else if (snsr_id == LTR_DEV_PROX) nvs_proximity_threshold_calibrate_lo(&st->prox, thresh_lo); return 0; } static int ltr_thresh_hi(void *client, int snsr_id, int thresh_hi) { struct ltr_state *st = (struct ltr_state *)client; if (snsr_id == LTR_DEV_LIGHT) nvs_light_threshold_calibrate_hi(&st->light, thresh_hi); else if (snsr_id == LTR_DEV_PROX) nvs_proximity_threshold_calibrate_hi(&st->prox, thresh_hi); return 0; } static int ltr_regs(void *client, int snsr_id, char *buf) { struct ltr_state *st = (struct ltr_state *)client; ssize_t t; u8 val[2]; u8 n; u8 i; int ret; t = sprintf(buf, "registers:\n"); for (i = LTR_REG_ALS_CONTR; i <= LTR_REG_MANUFAC_ID; i++) { ret = ltr_i2c_rd(st, i, val); if (ret) t += sprintf(buf + t, "0x%hhx=ERR\n", i); else t += sprintf(buf + t, "0x%hhx=0x%hhx\n", i, val[0]); } if (st->dev_id == LTR_DEVID_558ALS) { n = LTR_REG_ALS_DATA_CH0_1; for (i = LTR_REG_ALS_DATA_CH1_0; i < n; i += 2) { ret = ltr_i2c_read(st, i, 2, val); if (ret) t += sprintf(buf + t, "0x%hhx:0x%hhx=ERR\n", i, i + 1); else t += sprintf(buf + t, "0x%hhx:0x%hhx=0x%hx\n", i, i + 1, le16_to_cpup((__le16 *)val)); } } ret = ltr_i2c_rd(st, LTR_REG_STATUS, val); if (ret) t += sprintf(buf + t, "0x%hhx=ERR\n", LTR_REG_STATUS); else t += sprintf(buf + t, "0x%hhx=0x%hhx\n", LTR_REG_STATUS, val[0]); ret = ltr_i2c_read(st, LTR_REG_PS_DATA_0, 2, val); if (ret) t += sprintf(buf + t, "0x%hhx:0x%hhx=ERR\n", LTR_REG_PS_DATA_0, LTR_REG_PS_DATA_1); else t += sprintf(buf + t, "0x%hhx:0x%hhx=0x%hx\n", LTR_REG_PS_DATA_0, LTR_REG_PS_DATA_1, le16_to_cpup((__le16 *)val)); ret = ltr_i2c_rd(st, LTR_REG_INTERRUPT, val); if (ret) t += sprintf(buf + t, "0x%hhx=ERR\n", LTR_REG_INTERRUPT); else t += sprintf(buf + t, "0x%hhx=0x%hhx\n", LTR_REG_INTERRUPT, val[0]); for (i = LTR_REG_PS_THRES_UP_0; i < LTR_REG_PS_THRES_LOW_1; i += 2) { ret = ltr_i2c_read(st, i, 2, val); if (ret) t += sprintf(buf + t, "0x%hhx:0x%hhx=ERR\n", i, i + 1); else t += sprintf(buf + t, "0x%hhx:0x%hhx=0x%hx\n", i, i + 1, le16_to_cpup((__le16 *)val)); } ret = ltr_i2c_read(st, LTR_REG_PS_OFFSET_1, 2, val); if (ret) t += sprintf(buf + t, "0x%hhx:0x%hhx=ERR\n", LTR_REG_PS_OFFSET_1, LTR_REG_PS_OFFSET_0); else t += sprintf(buf + t, "0x%hhx:0x%hhx=0x%hx\n", i, i + 1, be16_to_cpup((__be16 *)val)); if (st->dev_id == LTR_DEVID_558ALS) { n = LTR_REG_ALS_THRES_LOW_1; for (i = LTR_REG_ALS_THRES_UP_0; i < n; i += 2) { ret = ltr_i2c_read(st, i, 2, val); if (ret) t += sprintf(buf + t, "0x%hhx:0x%hhx=ERR\n", i, i + 1); else t += sprintf(buf + t, "0x%hhx:0x%hhx=0x%hx\n", i, i + 1, le16_to_cpup((__le16 *)val)); } } ret = ltr_i2c_rd(st, LTR_REG_INTERRUPT_PERSIST, val); if (ret) t += sprintf(buf + t, "0x%hhx=ERR\n", LTR_REG_INTERRUPT_PERSIST); else t += sprintf(buf + t, "0x%hhx=0x%hhx\n", LTR_REG_INTERRUPT_PERSIST, val[0]); return t; } static int ltr_nvs_read(void *client, int snsr_id, char *buf) { struct ltr_state *st = (struct ltr_state *)client; ssize_t t; t = sprintf(buf, "driver v.%u\n", LTR_DRIVER_VERSION); t += sprintf(buf + t, "irq=%d\n", st->i2c->irq); t += sprintf(buf + t, "irq_set_irq_wake=%x\n", st->irq_set_irq_wake); t += sprintf(buf + t, "reg_ps_contr=%x\n", st->ps_contr); t += sprintf(buf + t, "reg_ps_led=%x\n", st->ps_led); t += sprintf(buf + t, "reg_ps_n_pulses=%x\n", st->ps_n_pulses); t += sprintf(buf + t, "reg_ps_meas_rate=%x\n", st->ps_meas_rate); t += sprintf(buf + t, "reg_als_meas_rate=%x\n", st->als_meas_rate); t += sprintf(buf + t, "reg_interrupt=%x\n", st->interrupt); t += sprintf(buf + t, "reg_interrupt_persist=%x\n", st->interrupt_persist); if (snsr_id == LTR_DEV_LIGHT) t += nvs_light_dbg(&st->light, buf + t); else if (snsr_id == LTR_DEV_PROX) t += nvs_proximity_dbg(&st->prox, buf + t); return t; } static struct nvs_fn_dev ltr_fn_dev = { .enable = ltr_enable, .batch = ltr_batch, .thresh_lo = ltr_thresh_lo, .thresh_hi = ltr_thresh_hi, .regs = ltr_regs, .nvs_read = ltr_nvs_read, }; static int ltr_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct ltr_state *st = i2c_get_clientdata(client); unsigned int i; int ret = 0; st->sts |= NVS_STS_SUSPEND; if (st->nvs) { for (i = 0; i < LTR_DEV_N; i++) { if (st->nvs_st[i]) ret |= st->nvs->suspend(st->nvs_st[i]); } } /* determine if we'll be operational during suspend */ for (i = 0; i < LTR_DEV_N; i++) { if ((st->enabled & (1 << i)) && (st->cfg[i].flags & SENSOR_FLAG_WAKE_UP)) break; } if (i < LTR_DEV_N) { irq_set_irq_wake(st->i2c->irq, 1); st->irq_set_irq_wake = true; } if (st->sts & NVS_STS_SPEW_MSG) dev_info(&client->dev, "%s WAKE_ON=%x\n", __func__, st->irq_set_irq_wake); return ret; } static int ltr_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct ltr_state *st = i2c_get_clientdata(client); unsigned int i; int ret = 0; if (st->irq_set_irq_wake) { irq_set_irq_wake(st->i2c->irq, 0); st->irq_set_irq_wake = false; } if (st->nvs) { for (i = 0; i < LTR_DEV_N; i++) { if (st->nvs_st[i]) ret |= st->nvs->resume(st->nvs_st[i]); } } 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(ltr_pm_ops, ltr_suspend, ltr_resume); static void ltr_shutdown(struct i2c_client *client) { struct ltr_state *st = i2c_get_clientdata(client); unsigned int i; st->sts |= NVS_STS_SHUTDOWN; if (st->nvs) { for (i = 0; i < LTR_DEV_N; i++) { if (st->nvs_st[i]) st->nvs->shutdown(st->nvs_st[i]); } } if (st->sts & NVS_STS_SPEW_MSG) dev_info(&client->dev, "%s\n", __func__); } static int ltr_remove(struct i2c_client *client) { struct ltr_state *st = i2c_get_clientdata(client); unsigned int i; if (st != NULL) { ltr_shutdown(client); if (st->nvs) { for (i = 0; i < LTR_DEV_N; i++) { if (st->nvs_st[i]) st->nvs->remove(st->nvs_st[i]); } } if (st->wq) { destroy_workqueue(st->wq); st->wq = NULL; } ltr_pm_exit(st); } dev_info(&client->dev, "%s\n", __func__); return 0; } static void ltr_id_part(struct ltr_state *st, const char *part) { unsigned int i; for (i = 0; i < LTR_DEV_N; i++) st->cfg[i].part = part; } static int ltr_id_dev(struct ltr_state *st, const char *name) { u8 val; unsigned int i; int ret; if (!strcmp(name, LTR_NAME_LTR659PS)) { st->dev_id = LTR_DEVID_659PS; ltr_id_part(st, LTR_NAME_LTR659PS); } else if (!strcmp(name, LTR_NAME_LTR558ALS)) { st->dev_id = LTR_DEVID_558ALS; ltr_id_part(st, LTR_NAME_LTR558ALS); } else { ret = ltr_reset_sw(st); ret |= ltr_i2c_rd(st, LTR_REG_MANUFAC_ID, &val); if (ret) return -ENODEV; if (val != LTR_REG_MANUFAC_ID_VAL) return -ENODEV; ret = ltr_i2c_rd(st, LTR_REG_PART_ID, &val); if (ret) return -ENODEV; val &= LTR_REG_PART_ID_MASK; for (i = 0; i < ARRAY_SIZE(ltr_ids); i++) { if (val == ltr_ids[i]) { st->dev_id = val; break; } } if (i >= ARRAY_SIZE(ltr_ids)) { dev_err(&st->i2c->dev, "%s ERR: ID %x != %s\n", __func__, val, name); return -ENODEV; } switch (st->dev_id) { case LTR_DEVID_659PS: ltr_id_part(st, LTR_NAME_LTR659PS); break; case LTR_DEVID_558ALS: ltr_id_part(st, LTR_NAME_LTR558ALS); break; default: return -ENODEV; } dev_info(&st->i2c->dev, "%s found %s for %s\n", __func__, st->cfg[0].part, name); } return 0; } static int ltr_id_i2c(struct ltr_state *st, const char *name) { int i; int ret; for (i = 0; i < ARRAY_SIZE(ltr_i2c_addrs); i++) { if (st->i2c->addr == ltr_i2c_addrs[i]) break; } if (i < ARRAY_SIZE(ltr_i2c_addrs)) { st->i2c_addr = st->i2c->addr; ret = ltr_id_dev(st, name); } else { name = LTR_NAME; for (i = 0; i < ARRAY_SIZE(ltr_i2c_addrs); i++) { st->i2c_addr = ltr_i2c_addrs[i]; ret = ltr_id_dev(st, name); if (!ret) break; } } if (ret) st->i2c_addr = 0; return ret; } struct sensor_cfg ltr_cfg_dflt[] = { { .name = NVS_LIGHT_STRING, .snsr_id = LTR_DEV_LIGHT, .ch_n = 1, .ch_sz = 4, .part = LTR_NAME, .vendor = LTR_VENDOR, .version = LTR_LIGHT_VERSION, .max_range = { .ival = LTR_LIGHT_MAX_RANGE_IVAL, .fval = LTR_LIGHT_MAX_RANGE_MICRO, }, .resolution = { .ival = LTR_LIGHT_RESOLUTION_IVAL, .fval = LTR_LIGHT_RESOLUTION_MICRO, }, .milliamp = { .ival = LTR_LIGHT_MILLIAMP_IVAL, .fval = LTR_LIGHT_MILLIAMP_MICRO, }, .delay_us_min = LTR_POLL_DLY_MS_MIN * 1000, .delay_us_max = LTR_POLL_DLY_MS_MAX * 1000, .flags = SENSOR_FLAG_ON_CHANGE_MODE, .scale = { .ival = LTR_LIGHT_SCALE_IVAL, .fval = LTR_LIGHT_SCALE_MICRO, }, .thresh_lo = LTR_LIGHT_THRESHOLD_DFLT, .thresh_hi = LTR_LIGHT_THRESHOLD_DFLT, }, { .name = NVS_PROXIMITY_STRING, .snsr_id = LTR_DEV_PROX, .ch_n = 1, .ch_sz = 4, .part = LTR_NAME, .vendor = LTR_VENDOR, .version = LTR_PROX_VERSION, .max_range = { .ival = LTR_PROX_MAX_RANGE_IVAL, .fval = LTR_PROX_MAX_RANGE_MICRO, }, .resolution = { .ival = LTR_PROX_RESOLUTION_IVAL, .fval = LTR_PROX_RESOLUTION_MICRO, }, .milliamp = { .ival = LTR_PROX_MILLIAMP_IVAL, .fval = LTR_PROX_MILLIAMP_MICRO, }, .delay_us_min = LTR_POLL_DLY_MS_MIN * 1000, .delay_us_max = LTR_POLL_DLY_MS_MAX * 1000, .flags = SENSOR_FLAG_ON_CHANGE_MODE | SENSOR_FLAG_WAKE_UP, .scale = { .ival = LTR_PROX_SCALE_IVAL, .fval = LTR_PROX_SCALE_MICRO, }, .thresh_lo = LTR_PROX_THRESHOLD_LO, .thresh_hi = LTR_PROX_THRESHOLD_HI, } }; static int ltr_of_dt(struct ltr_state *st, struct device_node *dn) { unsigned int i; int ret; for (i = 0; i < LTR_DEV_N; i++) memcpy(&st->cfg[i], <r_cfg_dflt[i], sizeof(st->cfg[0])); st->light.cfg = &st->cfg[LTR_DEV_LIGHT]; st->light.hw_mask = 0xFFFF; st->light.nld_tbl = ltr_nld_tbl; st->prox.cfg = &st->cfg[LTR_DEV_PROX]; st->prox.hw_mask = 0xFFFF; /* default device specific parameters */ st->ps_contr = LTR_REG_PS_CONTR_DFLT; st->ps_led = LTR_REG_PS_LED_DFLT; st->ps_n_pulses = LTR_REG_PS_N_PULSES_DFLT; st->ps_meas_rate = LTR_REG_PS_MEAS_RATE_DFLT; st->als_meas_rate = LTR_REG_ALS_MEAS_RATE_DFLT; st->interrupt_persist = LTR_REG_INTERRUPT_PERSIST_DFLT; /* device tree parameters */ if (dn) { /* common NVS parameters */ for (i = 0; i < LTR_DEV_N; i++) { ret = nvs_of_dt(dn, &st->cfg[i], NULL); if (ret == -ENODEV) /* the entire device has been disabled */ return -ENODEV; } /* device specific parameters */ of_property_read_u8(dn, "reg_ps_contr", &st->ps_contr); st->ps_contr &= LTR_REG_PS_CONTR_POR_MASK; of_property_read_u8(dn, "reg_ps_led", &st->ps_led); of_property_read_u8(dn, "register_ps_n_pulses", &st->ps_n_pulses); of_property_read_u8(dn, "reg_ps_meas_rate", &st->ps_meas_rate); of_property_read_u8(dn, "reg_als_meas_rate", &st->als_meas_rate); of_property_read_u8(dn, "reg_interrupt", &st->interrupt); /* just interrupt polarity */ st->interrupt &= LTR_REG_INTERRUPT_POLARITY; of_property_read_u8(dn, "reg_interrupt_persist", &st->interrupt_persist); } /* this device supports these programmable parameters */ if (nvs_light_of_dt(&st->light, dn, NULL)) { st->light.nld_i_lo = 0; st->light.nld_i_hi = ARRAY_SIZE(ltr_nld_tbl) - 1; } return 0; } static int ltr_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct ltr_state *st; unsigned long irqflags; unsigned int n; unsigned int i; 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 = ltr_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 ltr_probe_exit; } ltr_pm_init(st); ret = ltr_id_i2c(st, id->name); if (ret) { dev_err(&client->dev, "%s _id_i2c ERR\n", __func__); ret = -ENODEV; goto ltr_probe_exit; } ltr_pm(st, 0); ltr_fn_dev.sts = &st->sts; ltr_fn_dev.errs = &st->errs; st->nvs = nvs_iio(); if (st->nvs == NULL) { ret = -ENODEV; goto ltr_probe_exit; } st->light.handler = st->nvs->handler; st->prox.handler = st->nvs->handler; if (client->irq < 1) { /* disable WAKE_ON ability when no interrupt */ for (i = 0; i < LTR_DEV_N; i++) st->cfg[i].flags &= ~SENSOR_FLAG_WAKE_UP; } n = 0; for (i = 0; i < LTR_DEV_N; i++) { ret = st->nvs->probe(&st->nvs_st[i], st, &client->dev, <r_fn_dev, &st->cfg[i]); if (!ret) n++; } if (!n) { dev_err(&client->dev, "%s nvs_probe ERR\n", __func__); ret = -ENODEV; goto ltr_probe_exit; } st->light.nvs_st = st->nvs_st[LTR_DEV_LIGHT]; st->prox.nvs_st = st->nvs_st[LTR_DEV_PROX]; st->wq = create_workqueue(LTR_NAME); if (!st->wq) { dev_err(&client->dev, "%s create_workqueue ERR\n", __func__); ret = -ENOMEM; goto ltr_probe_exit; } INIT_WORK(&st->ws, ltr_work); if (client->irq) { irqflags = IRQF_ONESHOT; if (st->interrupt & LTR_REG_INTERRUPT_POLARITY) irqflags |= IRQF_TRIGGER_RISING; else irqflags |= IRQF_TRIGGER_FALLING; for (i = 0; i < LTR_DEV_N; i++) { if (st->cfg[i].snsr_id >= 0) { if (st->cfg[i].flags & SENSOR_FLAG_WAKE_UP) irqflags |= IRQF_NO_SUSPEND; } } ret = request_threaded_irq(client->irq, NULL, ltr_irq_thread, irqflags, LTR_NAME, st); if (ret) { dev_err(&client->dev, "%s req_threaded_irq ERR %d\n", __func__, ret); ret = -ENOMEM; goto ltr_probe_exit; } } dev_info(&client->dev, "%s done\n", __func__); return 0; ltr_probe_exit: ltr_remove(client); return ret; } static const struct i2c_device_id ltr_i2c_device_id[] = { { LTR_NAME, 0 }, { LTR_NAME_LTR659PS, 0 }, { LTR_NAME_LTR558ALS, 0 }, {} }; MODULE_DEVICE_TABLE(i2c, ltr_i2c_device_id); static const struct of_device_id ltr_of_match[] = { { .compatible = "liteon,ltrX5X", }, { .compatible = "liteon,ltr659ps", }, { .compatible = "liteon,ltr558als", }, {}, }; MODULE_DEVICE_TABLE(of, ltr_of_match); static struct i2c_driver ltr_driver = { .class = I2C_CLASS_HWMON, .probe = ltr_probe, .remove = ltr_remove, .shutdown = ltr_shutdown, .driver = { .name = LTR_NAME, .owner = THIS_MODULE, .of_match_table = of_match_ptr(ltr_of_match), .pm = <r_pm_ops, }, .id_table = ltr_i2c_device_id, }; module_i2c_driver(ltr_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("LTR659PS driver"); MODULE_AUTHOR("NVIDIA Corporation");