/* 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 MX_DRIVER_VERSION (3) #define MX_VENDOR "Maxim" #define MX_NAME "max4400x" #define MX_NAME_MAX44005 "max44005" #define MX_NAME_MAX44006 "max44006" #define MX_NAME_MAX44008 "max44008" #define MX_DEVID_MAX44005 (0x05) #define MX_DEVID_MAX44006 (0x06) #define MX_DEVID_MAX44008 (0x08) #define MX_HW_DELAY_MS (1) #define MX_POLL_DLY_MS_DFLT (2000) #define MX_POLL_DLY_MS_MIN (100) #define MX_POLL_DLY_MS_MAX (60000) #define MX_AMB_CFG_DFLT (0x40) #define MX_PRX_CFG_DFLT (0x12) /* light defines */ #define MX_LIGHT_VERSION (1) #define MX_LIGHT_MAX_RANGE_IVAL (14323) #define MX_LIGHT_MAX_RANGE_MICRO (0) #define MX_LIGHT_RESOLUTION_IVAL (0) #define MX_LIGHT_RESOLUTION_MICRO (14000) #define MX_LIGHT_MILLIAMP_IVAL (0) #define MX_LIGHT_MILLIAMP_MICRO (13500) #define MX_LIGHT_SCALE_IVAL (0) #define MX_LIGHT_SCALE_MICRO (1000) #define MX_LIGHT_THRESHOLD_DFLT (50) /* proximity defines */ #define MX_PROX_VERSION (1) /* binary proximity when max_range and resolution are 1.0 */ #define MX_PROX_MAX_RANGE_IVAL (1) #define MX_PROX_MAX_RANGE_MICRO (0) #define MX_PROX_RESOLUTION_IVAL (1) #define MX_PROX_RESOLUTION_MICRO (0) #define MX_PROX_MILLIAMP_IVAL (10) #define MX_PROX_MILLIAMP_MICRO (19500) #define MX_PROX_SCALE_IVAL (0) #define MX_PROX_SCALE_MICRO (0) #define MX_PROX_THRESHOLD_LO_DFLT (10) #define MX_PROX_THRESHOLD_HI_DFLT (100) /* HW registers */ #define MX_REG_STS (0x00) #define MX_REG_STS_POR (0x04) #define MX_REG_STS_RESET (4) #define MX_REG_STS_SHDN (3) #define MX_REG_STS_PWRON (2) #define MX_REG_STS_PRXINTS (1) #define MX_REG_STS_AMBINTS (0) #define MX_REG_CFG_MAIN (0x01) #define MX_REG_CFG_MAIN_POR (0x00) #define MX_REG_CFG_MAIN_MODE (4) #define MX_REG_CFG_MAIN_AMBSEL (2) #define MX_REG_CFG_MAIN_PRXINTE (1) #define MX_REG_CFG_MAIN_AMBINTE (0) #define MX_REG_CFG_MAIN_INTE_MASK (0x03) #define MX_REG_CFG_AMB (0x02) #define MX_REG_CFG_AMB_POR (0x20) #define MX_REG_CFG_AMB_TRIM (7) #define MX_REG_CFG_AMB_COMPEN (6) #define MX_REG_CFG_AMB_TEMPEN (5) #define MX_REG_CFG_AMB_AMBTIM (2) #define MX_REG_CFG_AMB_AMBTIM_MASK (0x1C) #define MX_REG_CFG_AMB_AMBPGA (0) #define MX_REG_CFG_AMB_AMBPGA_MASK (0x03) #define MX_REG_CFG_PRX (0x03) #define MX_REG_CFG_PRX_DRV (4) #define MX_REG_CFG_PRX_PRXTIM (1) #define MX_REG_CFG_PRX_PRXPGA (0) #define MX_REG_DATA_AMB_CLEAR (0x04) #define MX_REG_DATA_AMB_CLEAR_H (0x04) #define MX_REG_DATA_AMB_CLEAR_L (0x05) #define MX_REG_DATA_AMB_RED_H (0x06) #define MX_REG_DATA_AMB_RED_L (0x07) #define MX_REG_DATA_AMB_GREEN_H (0x08) #define MX_REG_DATA_AMB_GREEN_L (0x09) #define MX_REG_DATA_AMB_BLUE_H (0x0A) #define MX_REG_DATA_AMB_BLUE_L (0x0B) #define MX_REG_DATA_AMB_IR_H (0x0C) #define MX_REG_DATA_AMB_IR_L (0x0D) #define MX_REG_DATA_AMB_IRCOMP_H (0x0E) #define MX_REG_DATA_AMB_IRCOMP_L (0x0F) #define MX_REG_DATA_PROX_H (0x10) #define MX_REG_DATA_PROX_L (0x11) #define MX_REG_DATA_TEMP_H (0x12) #define MX_REG_DATA_TEMP_L (0x13) #define MX_REG_AMB_UPTHR_H (0x14) #define MX_REG_AMB_UPTHR_L (0x15) #define MX_REG_AMB_LOTHR_H (0x16) #define MX_REG_AMB_LOTHR_L (0x17) #define MX_REG_CFG_THR (0x18) #define MX_REG_CFG_THR_PRXPST (2) #define MX_REG_CFG_THR_AMBPST (0) #define MX_REG_PRX_UPTHR_H (0x19) #define MX_REG_PRX_UPTHR_L (0x1A) #define MX_REG_PRX_LOTHR_H (0x1B) #define MX_REG_PRX_LOTHR_L (0x1C) #define MX_REG_TRIM_CLEAR (0x1D) #define MX_REG_TRIM_RED (0x1E) #define MX_REG_TRIM_GREEN (0x1F) #define MX_REG_TRIM_BLUE (0x20) #define MX_REG_TRIM_IR (0x21) #define MX_DEV_LIGHT (0) #define MX_DEV_PROX (1) #define MX_DEV_N (2) /* regulator names in order of powering on */ static char *mx_vregs[] = { "vdd", "vled" }; #define MX_PM_ON (1) #define MX_PM_LED (ARRAY_SIZE(mx_vregs)) static unsigned short mx_i2c_addrs[] = { 0x40, 0x41, 0x44, 0x45, }; /* enable bit mask of MX_DEV_ (MX_DEV_TEMP would be bit 2) */ static u8 mx_mode_tbl[] = { /* device enable */ 0x00, /* nothing */ 0x00, /* light */ 0x50, /* proximity */ 0x30, /* proximity + light */ 0x00, /* temp */ 0x00, /* temp + light */ 0x30, /* temp + proximity */ 0x30 /* temp + proximity + light */ }; /* 1 nW/cm^2 = 0.00683 lux */ static struct nvs_light_dynamic mx_nld_tbl_44005[] = { {{0, 20490}, {335, 687670}, {0, 13500}, 100, 0x00}, {{0, 81960}, {1342, 750680}, {0, 13500}, 100, 0x01}, {{0, 327680}, {5368, 381440}, {0, 13500}, 100, 0x02}, {{5, 245440}, {85936, 43520}, {0, 13500}, 100, 0x03} }; static struct nvs_light_dynamic mx_nld_tbl_44006[] = { {{0, 13660}, {223, 791780}, {0, 13500}, 100, 0x00}, {{0, 54640}, {895, 167120}, {0, 13500}, 100, 0x01}, {{0, 218560}, {3580, 668480}, {0, 13500}, 100, 0x02}, {{3, 496960}, {57290, 695680}, {0, 13500}, 100, 0x03} }; static unsigned int mx_ambtim_mask[] = { 0x3FFF, /* 14 bits */ 0x0FFF, /* 12 bits */ 0x03FF, /* 10 bits */ 0x00FF, /* 8 bits */ 0x3FFF, /* 14 bits */ 0x3FFF, /* N/A */ 0x3FFF, /* N/A */ 0x3FFF, /* N/A */ }; struct mx_state { struct i2c_client *i2c; struct nvs_fn_if *nvs; void *nvs_st[MX_DEV_N]; struct sensor_cfg cfg[MX_DEV_N]; struct workqueue_struct *wq; struct work_struct ws; struct regulator_bulk_data vreg[ARRAY_SIZE(mx_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 amb_cfg; /* ambient configuration register */ u8 prx_cfg; /* proximity configuration register */ u8 thr_cfg; /* threshold persist register */ u8 rc_main_cfg; /* cache of main configuration */ u8 rc_amb_cfg; /* cache of ambient configuration */ }; static void mx_err(struct mx_state *st) { st->errs++; if (!st->errs) st->errs--; } static void mx_mutex_lock(struct mx_state *st) { unsigned int i; if (st->nvs) { for (i = 0; i < MX_DEV_N; i++) { if (st->nvs_st[i]) st->nvs->nvs_mutex_lock(st->nvs_st[i]); } } } static void mx_mutex_unlock(struct mx_state *st) { unsigned int i; if (st->nvs) { for (i = 0; i < MX_DEV_N; i++) { if (st->nvs_st[i]) st->nvs->nvs_mutex_unlock(st->nvs_st[i]); } } } static int mx_i2c_read(struct mx_state *st, u8 reg, u16 len, u8 *val) { struct i2c_msg msg[2]; int ret = -ENODEV; if (st->i2c_addr) { 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) { ret = 0; } else { mx_err(st); ret = -EIO; } } return ret; } static int mx_i2c_rd(struct mx_state *st, u8 reg, u8 *val) { return mx_i2c_read(st, reg, 1, val); } static int mx_i2c_write(struct mx_state *st, u16 len, u8 *buf) { struct i2c_msg msg; int ret = -ENODEV; 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) { ret = 0; } else { mx_err(st); ret = -EIO; } } return ret; } static int mx_i2c_wr(struct mx_state *st, u8 reg, u8 val) { u8 buf[2]; buf[0] = reg; buf[1] = val; return mx_i2c_write(st, sizeof(buf), buf); } static void mx_cfg(struct mx_state *st) { unsigned int i; i = st->rc_amb_cfg & MX_REG_CFG_AMB_AMBTIM_MASK; i >>= MX_REG_CFG_AMB_AMBTIM; st->light.hw_mask = mx_ambtim_mask[i]; } static int mx_reset_sw(struct mx_state *st) { int ret; ret = mx_i2c_wr(st, MX_REG_STS, (1 << MX_REG_STS_RESET)); if (!ret) { mdelay(MX_HW_DELAY_MS); st->rc_main_cfg = MX_REG_CFG_MAIN_POR; st->rc_amb_cfg = MX_REG_CFG_AMB_POR; mx_cfg(st); } return ret; } static int mx_pm(struct mx_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 < MX_DEV_PROX)) vreg_n = MX_PM_LED; else vreg_n = MX_PM_ON; nvs_vregs_enable(&st->i2c->dev, st->vreg, vreg_n); if (ret) mdelay(MX_HW_DELAY_MS); ret = mx_reset_sw(st); vreg_n_dis = ARRAY_SIZE(mx_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, MX_PM_ON); if ((ret < 0) || (ret == MX_PM_ON)) { ret = mx_i2c_wr(st, MX_REG_CFG_PRX, 0); ret |= mx_i2c_wr(st, MX_REG_STS, 1 << MX_REG_STS_SHDN); } else if (ret > 0) { nvs_vregs_enable(&st->i2c->dev, st->vreg, MX_PM_ON); mdelay(MX_HW_DELAY_MS); ret = mx_i2c_wr(st, MX_REG_CFG_PRX, 0); ret |= mx_i2c_wr(st, MX_REG_STS, 1 << MX_REG_STS_SHDN); } ret |= nvs_vregs_disable(&st->i2c->dev, st->vreg, ARRAY_SIZE(mx_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 mx_pm_exit(struct mx_state *st) { mx_pm(st, 0); nvs_vregs_exit(&st->i2c->dev, st->vreg, ARRAY_SIZE(mx_vregs)); } static int mx_pm_init(struct mx_state *st) { int ret; st->enabled = 0; nvs_vregs_init(&st->i2c->dev, st->vreg, ARRAY_SIZE(mx_vregs), mx_vregs); ret = mx_pm(st, (1 << MX_DEV_N)); return ret; } static int mx_cmd_wr(struct mx_state *st, unsigned int enable, bool irq_en) { u8 amb_cfg = st->amb_cfg; u8 main_cfg = 0; int ret; int ret_t = 0; amb_cfg |= mx_nld_tbl_44005[st->light.nld_i].driver_data; if (amb_cfg != st->rc_amb_cfg) { ret = mx_i2c_wr(st, MX_REG_CFG_AMB, amb_cfg); if (ret) { ret_t |= ret; } else { st->rc_amb_cfg = amb_cfg; mx_cfg(st); } if (st->sts & NVS_STS_SPEW_MSG) dev_info(&st->i2c->dev, "%s amb_cfg=%hhx err=%d\n", __func__, amb_cfg, ret); } main_cfg = mx_mode_tbl[enable]; if (irq_en && (st->i2c->irq > 0)) { if (enable & (1 << MX_DEV_LIGHT)) main_cfg |= (1 << MX_REG_CFG_MAIN_AMBINTE); if (enable & (1 << MX_DEV_PROX)) main_cfg |= (1 << MX_REG_CFG_MAIN_PRXINTE); } if (main_cfg != st->rc_main_cfg) { ret = mx_i2c_wr(st, MX_REG_CFG_MAIN, main_cfg); if (ret) ret_t |= ret; else st->rc_main_cfg = main_cfg; if (st->sts & NVS_STS_SPEW_MSG) dev_info(&st->i2c->dev, "%s main_cfg=%hhx err=%d\n", __func__, main_cfg, ret); } if (st->rc_main_cfg & MX_REG_CFG_MAIN_INTE_MASK) ret_t = RET_HW_UPDATE; /* flag IRQ enabled */ return ret_t; } static int mx_thr_wr(struct mx_state *st, u8 reg, u16 thr_lo, u16 thr_hi) { u8 buf[5]; u16 thr_be; int ret = RET_POLL_NEXT; if (st->i2c->irq > 0) { buf[0] = reg; thr_be = cpu_to_be16(thr_hi); buf[1] = thr_be & 0xFF; buf[2] = thr_be >> 8; thr_be = cpu_to_be16(thr_lo); buf[3] = thr_be & 0xFF; buf[4] = thr_be >> 8; ret = mx_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 mx_rd_light(struct mx_state *st, s64 ts) { u16 hw; int ret; ret = mx_i2c_read(st, MX_REG_DATA_AMB_CLEAR, 2, (u8 *)&hw); if (ret) return ret; hw = be16_to_cpu(hw); if (st->sts & NVS_STS_SPEW_DATA) dev_info(&st->i2c->dev, "poll light hw %hu %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 < RET_HW_UPDATE) /* either poll or nothing to do */ return ret; ret = mx_thr_wr(st, MX_REG_AMB_UPTHR_H, st->light.hw_thresh_lo, st->light.hw_thresh_hi); return ret; } static int mx_rd_prox(struct mx_state *st, s64 ts) { u16 hw; int ret; ret = mx_i2c_read(st, MX_REG_DATA_PROX_H, 2, (u8 *)&hw); if (ret) return ret; hw = be16_to_cpu(hw); 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 < RET_HW_UPDATE) /* either poll or nothing to do */ return ret; ret = mx_thr_wr(st, MX_REG_PRX_UPTHR_H, st->prox.hw_thresh_lo, st->prox.hw_thresh_hi); return ret; } static int mx_en(struct mx_state *st, unsigned int enable) { int ret; ret = mx_i2c_wr(st, MX_REG_CFG_THR, st->thr_cfg); if (enable & (1 << MX_DEV_PROX)) { ret |= mx_i2c_wr(st, MX_REG_CFG_PRX, st->prx_cfg); if (st->prx_cfg & (1 << MX_REG_CFG_PRX_PRXTIM)) st->prox.hw_mask = 0x00FF; else st->prox.hw_mask = 0x03FF; nvs_proximity_enable(&st->prox); } if (enable & (1 << MX_DEV_LIGHT)) nvs_light_enable(&st->light); ret |= mx_cmd_wr(st, enable, false); if (st->sts & NVS_STS_SPEW_MSG) { if (enable & (1 << MX_DEV_PROX)) dev_info(&st->i2c->dev, "%s thr_cfg=%hhx prx_cfg=%hhx err=%d\n", __func__, st->thr_cfg, st->prx_cfg, ret); else dev_info(&st->i2c->dev, "%s thr_cfg=%hhx err=%d\n", __func__, st->thr_cfg, ret); } return ret; } static int mx_rd(struct mx_state *st) { u8 sts; s64 ts; int ret; /* clear possible IRQ */ ret = mx_i2c_rd(st, MX_REG_STS, &sts); if (ret) return ret; if (sts & (1 << MX_REG_STS_PWRON)) { /* restart */ mx_en(st, st->enabled); return RET_POLL_NEXT; } ts = nvs_timestamp(); if (st->enabled & (1 << MX_DEV_PROX)) ret |= mx_rd_prox(st, ts); if (st->enabled & (1 << MX_DEV_LIGHT)) ret |= mx_rd_light(st, ts); if (ret < 0) /* poll if error or more reporting */ ret = mx_cmd_wr(st, st->enabled, false); else ret = mx_cmd_wr(st, st->enabled, true); return ret; } static unsigned int mx_polldelay(struct mx_state *st) { unsigned int poll_delay_ms = MX_POLL_DLY_MS_DFLT; if (st->enabled & (1 << MX_DEV_LIGHT)) poll_delay_ms = st->light.poll_delay_ms; if (st->enabled & (1 << MX_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 mx_read(struct mx_state *st) { int ret; mx_mutex_lock(st); ret = mx_rd(st); mx_mutex_unlock(st); return ret; } static void mx_work(struct work_struct *ws) { struct mx_state *st = container_of((struct work_struct *)ws, struct mx_state, ws); int ret; while (st->enabled) { msleep(mx_polldelay(st)); ret = mx_read(st); if (ret == RET_HW_UPDATE) /* switch to IRQ driven */ break; } } static irqreturn_t mx_irq_thread(int irq, void *dev_id) { struct mx_state *st = (struct mx_state *)dev_id; int ret; if (st->sts & NVS_STS_SPEW_IRQ) dev_info(&st->i2c->dev, "%s\n", __func__); if (st->enabled) { ret = mx_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 mx_disable(struct mx_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 == MX_DEV_PROX) ret = mx_i2c_wr(st, MX_REG_CFG_PRX, 0); ret |= mx_pm(st, st->enabled); } } if (disable) { ret = mx_pm(st, 0); if (!ret) st->enabled = 0; } return ret; } static int mx_enable(void *client, int snsr_id, int enable) { struct mx_state *st = (struct mx_state *)client; int ret; if (enable < 0) return st->enabled & (1 << snsr_id); if (enable) { enable = st->enabled | (1 << snsr_id); ret = mx_pm(st, enable); if (!ret) { ret = mx_en(st, enable); if (ret < 0) { mx_disable(st, snsr_id); } else { st->enabled = enable; cancel_work_sync(&st->ws); queue_work(st->wq, &st->ws); } } } else { ret = mx_disable(st, snsr_id); } return ret; } static int mx_batch(void *client, int snsr_id, int flags, unsigned int period, unsigned int timeout) { struct mx_state *st = (struct mx_state *)client; if (timeout) /* timeout not supported (no HW FIFO) */ return -EINVAL; if (snsr_id == MX_DEV_LIGHT) st->light.delay_us = period; else if (snsr_id == MX_DEV_PROX) st->prox.delay_us = period; return 0; } static int mx_thresh_lo(void *client, int snsr_id, int thresh_lo) { struct mx_state *st = (struct mx_state *)client; if (snsr_id == MX_DEV_LIGHT) nvs_light_threshold_calibrate_lo(&st->light, thresh_lo); else if (snsr_id == MX_DEV_PROX) nvs_proximity_threshold_calibrate_lo(&st->prox, thresh_lo); return 0; } static int mx_thresh_hi(void *client, int snsr_id, int thresh_hi) { struct mx_state *st = (struct mx_state *)client; if (snsr_id == MX_DEV_LIGHT) nvs_light_threshold_calibrate_hi(&st->light, thresh_hi); else if (snsr_id == MX_DEV_PROX) nvs_proximity_threshold_calibrate_hi(&st->prox, thresh_hi); return 0; } static int mx_regs(void *client, int snsr_id, char *buf) { struct mx_state *st = (struct mx_state *)client; ssize_t t; u8 val[2]; u8 i; int ret; t = sprintf(buf, "registers:\n"); for (i = 0; i <= MX_REG_CFG_PRX; i++) { ret = mx_i2c_rd(st, i, val); if (!ret) t += sprintf(buf + t, "0x%hhx=0x%hhx\n", i, val[0]); } for (i = MX_REG_DATA_AMB_CLEAR; i < MX_REG_CFG_THR; i += 2) { ret = mx_i2c_read(st, i, 2, val); if (!ret) t += sprintf(buf + t, "0x%hhx:0x%hhx=0x%hx\n", i, i + 1, be16_to_cpup((__be16 *)val)); } ret = mx_i2c_rd(st, MX_REG_CFG_THR, val); if (!ret) t += sprintf(buf + t, "0x%hhx=0x%hhx\n", MX_REG_CFG_THR, val[0]); for (i = MX_REG_PRX_UPTHR_H; i < MX_REG_TRIM_CLEAR; i += 2) { ret = mx_i2c_read(st, i, 2, val); if (!ret) t += sprintf(buf + t, "0x%hhx:0x%hhx=0x%hx\n", i, i + 1, be16_to_cpup((__be16 *)val)); } for (i = MX_REG_TRIM_CLEAR; i <= MX_REG_TRIM_IR; i++) { ret = mx_i2c_rd(st, i, val); if (!ret) t += sprintf(buf + t, "0x%hhx=0x%hhx\n", i, val[0]); } return t; } static int mx_nvs_read(void *client, int snsr_id, char *buf) { struct mx_state *st = (struct mx_state *)client; ssize_t t; t = sprintf(buf, "driver v.%u\n", MX_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_ambient_cfg=%x\n", st->amb_cfg); t += sprintf(buf + t, "reg_proximity_cfg=%x\n", st->prx_cfg); t += sprintf(buf + t, "reg_threshold_persist=%x\n", st->thr_cfg); if (snsr_id == MX_DEV_LIGHT) t += nvs_light_dbg(&st->light, buf + t); else if (snsr_id == MX_DEV_PROX) t += nvs_proximity_dbg(&st->prox, buf + t); return t; } static struct nvs_fn_dev mx_fn_dev = { .enable = mx_enable, .batch = mx_batch, .thresh_lo = mx_thresh_lo, .thresh_hi = mx_thresh_hi, .regs = mx_regs, .nvs_read = mx_nvs_read, }; static int mx_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct mx_state *st = i2c_get_clientdata(client); unsigned int i; int ret = 0; st->sts |= NVS_STS_SUSPEND; if (st->nvs) { for (i = 0; i < MX_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 < MX_DEV_N; i++) { if ((st->enabled & (1 << i)) && (st->cfg[i].flags & SENSOR_FLAG_WAKE_UP)) break; } if (i < MX_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 mx_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct mx_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 < MX_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 0; } static SIMPLE_DEV_PM_OPS(mx_pm_ops, mx_suspend, mx_resume); static void mx_shutdown(struct i2c_client *client) { struct mx_state *st = i2c_get_clientdata(client); unsigned int i; st->sts |= NVS_STS_SHUTDOWN; if (st->nvs) { for (i = 0; i < MX_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 mx_remove(struct i2c_client *client) { struct mx_state *st = i2c_get_clientdata(client); unsigned int i; if (st != NULL) { mx_shutdown(client); if (st->nvs) { for (i = 0; i < MX_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; } mx_pm_exit(st); } dev_info(&client->dev, "%s\n", __func__); return 0; } static void mx_id_part(struct mx_state *st, const char *part) { unsigned int i; for (i = 0; i < MX_DEV_N; i++) st->cfg[i].part = part; } static int mx_id_dev(struct mx_state *st, const char *name) { struct nvs_light_dynamic *mx_nld_tbl; u8 val; unsigned int i; int ret = 0; if (!strcmp(name, MX_NAME_MAX44008)) { st->dev_id = MX_DEVID_MAX44008; mx_id_part(st, MX_NAME_MAX44008); } else if (!strcmp(name, MX_NAME_MAX44006)) { st->dev_id = MX_DEVID_MAX44006; mx_id_part(st, MX_NAME_MAX44006); } else if (!strcmp(name, MX_NAME_MAX44005)) { st->dev_id = MX_DEVID_MAX44005; mx_id_part(st, MX_NAME_MAX44005); } else { /* There is no way to auto-detect the device since the * MX44006/8 has actual proximity HW that works but just * doesn't have the undetectable LED driver HW. And of * course there isn't an ID register either. So we'll * just confirm that our device exists and default to the * MX44005 with proximity support if using max4400x. */ st->dev_id = MX_DEVID_MAX44005; mx_id_part(st, MX_NAME_MAX44005); ret = mx_reset_sw(st); ret |= mx_i2c_rd(st, MX_REG_STS, &val); if (ret) return -ENODEV; if (val != MX_REG_STS_POR) return -ENODEV; } if (st->dev_id == MX_DEVID_MAX44005) mx_nld_tbl = mx_nld_tbl_44005; else mx_nld_tbl = mx_nld_tbl_44006; st->light.nld_tbl = mx_nld_tbl; i = st->light.nld_i_lo; st->cfg[MX_DEV_LIGHT].resolution.ival = mx_nld_tbl[i].resolution.ival; st->cfg[MX_DEV_LIGHT].resolution.fval = mx_nld_tbl[i].resolution.fval; i = st->light.nld_i_hi; st->cfg[MX_DEV_LIGHT].max_range.ival = mx_nld_tbl[i].max_range.ival; st->cfg[MX_DEV_LIGHT].max_range.fval = mx_nld_tbl[i].max_range.fval; st->cfg[MX_DEV_LIGHT].delay_us_min = mx_nld_tbl[i].delay_min_ms * 1000; return ret; } static int mx_id_i2c(struct mx_state *st, const char *name) { int i; int ret; for (i = 0; i < ARRAY_SIZE(mx_i2c_addrs); i++) { if (st->i2c->addr == mx_i2c_addrs[i]) break; } if (i < ARRAY_SIZE(mx_i2c_addrs)) { st->i2c_addr = st->i2c->addr; ret = mx_id_dev(st, name); } else { name = MX_NAME; for (i = 0; i < ARRAY_SIZE(mx_i2c_addrs); i++) { st->i2c_addr = mx_i2c_addrs[i]; ret = mx_id_dev(st, name); if (!ret) break; } } if (ret) st->i2c_addr = 0; return ret; } struct sensor_cfg mx_cfg_dflt[] = { { .name = NVS_LIGHT_STRING, .snsr_id = MX_DEV_LIGHT, .ch_n = 1, .ch_sz = 4, .part = MX_NAME, .vendor = MX_VENDOR, .version = MX_LIGHT_VERSION, .max_range = { .ival = MX_LIGHT_MAX_RANGE_IVAL, .fval = MX_LIGHT_MAX_RANGE_MICRO, }, .resolution = { .ival = MX_LIGHT_RESOLUTION_IVAL, .fval = MX_LIGHT_RESOLUTION_MICRO, }, .milliamp = { .ival = MX_LIGHT_MILLIAMP_IVAL, .fval = MX_LIGHT_MILLIAMP_MICRO, }, .delay_us_min = MX_POLL_DLY_MS_MIN * 1000, .delay_us_max = MX_POLL_DLY_MS_MAX * 1000, .flags = SENSOR_FLAG_ON_CHANGE_MODE, .scale = { .ival = MX_LIGHT_SCALE_IVAL, .fval = MX_LIGHT_SCALE_MICRO, }, .thresh_lo = MX_LIGHT_THRESHOLD_DFLT, .thresh_hi = MX_LIGHT_THRESHOLD_DFLT, }, { .name = NVS_PROXIMITY_STRING, .ch_n = 1, .ch_sz = 4, .part = MX_NAME, .vendor = MX_VENDOR, .version = MX_PROX_VERSION, .max_range = { .ival = MX_PROX_MAX_RANGE_IVAL, .fval = MX_PROX_MAX_RANGE_MICRO, }, .resolution = { .ival = MX_PROX_RESOLUTION_IVAL, .fval = MX_PROX_RESOLUTION_MICRO, }, .milliamp = { .ival = MX_PROX_MILLIAMP_IVAL, .fval = MX_PROX_MILLIAMP_MICRO, }, .delay_us_min = MX_POLL_DLY_MS_MIN * 1000, .delay_us_max = MX_POLL_DLY_MS_MAX * 1000, .flags = SENSOR_FLAG_ON_CHANGE_MODE | SENSOR_FLAG_WAKE_UP, .scale = { .ival = MX_PROX_SCALE_IVAL, .fval = MX_PROX_SCALE_MICRO, }, .thresh_lo = MX_PROX_THRESHOLD_LO_DFLT, .thresh_hi = MX_PROX_THRESHOLD_HI_DFLT, }, }; static int mx_of_dt(struct mx_state *st, struct device_node *dn) { unsigned int i; int ret; for (i = 0; i < MX_DEV_N; i++) memcpy(&st->cfg[i], &mx_cfg_dflt[i], sizeof(st->cfg[0])); st->light.cfg = &st->cfg[MX_DEV_LIGHT]; st->prox.cfg = &st->cfg[MX_DEV_PROX]; /* default device specific parameters */ st->amb_cfg = MX_AMB_CFG_DFLT; st->prx_cfg = MX_PRX_CFG_DFLT; /* device tree parameters */ if (dn) { /* common NVS parameters */ for (i = 0; i < MX_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_ambient_cfg", &st->amb_cfg); of_property_read_u8(dn, "reg_proximity_cfg", &st->prx_cfg); of_property_read_u8(dn, "reg_threshold_persist", &st->thr_cfg); } /* 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(mx_nld_tbl_44005) - 1; } return 0; } static int mx_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct mx_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 = mx_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 mx_probe_exit; } mx_pm_init(st); ret = mx_id_i2c(st, id->name); if (ret) { dev_err(&client->dev, "%s _id_i2c ERR\n", __func__); ret = -ENODEV; goto mx_probe_exit; } mx_pm(st, 0); mx_fn_dev.sts = &st->sts; mx_fn_dev.errs = &st->errs; st->nvs = nvs_iio(); if (st->nvs == NULL) { ret = -ENODEV; goto mx_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 < MX_DEV_N; i++) st->cfg[i].flags &= ~SENSOR_FLAG_WAKE_UP; } n = 0; for (i = 0; i < MX_DEV_N; i++) { if (st->dev_id != MX_DEVID_MAX44005) { if (st->cfg[i].snsr_id == MX_DEV_PROX) { st->cfg[i].snsr_id = -1; continue; } } ret = st->nvs->probe(&st->nvs_st[i], st, &client->dev, &mx_fn_dev, &st->cfg[i]); if (!ret) n++; } if (!n) { dev_err(&client->dev, "%s nvs_probe ERR\n", __func__); ret = -ENODEV; goto mx_probe_exit; } st->light.nvs_st = st->nvs_st[MX_DEV_LIGHT]; st->prox.nvs_st = st->nvs_st[MX_DEV_PROX]; st->wq = create_workqueue(MX_NAME); if (!st->wq) { dev_err(&client->dev, "%s create_workqueue ERR\n", __func__); ret = -ENOMEM; goto mx_probe_exit; } INIT_WORK(&st->ws, mx_work); if (client->irq) { irqflags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; for (i = 0; i < MX_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, mx_irq_thread, irqflags, MX_NAME, st); if (ret) { dev_err(&client->dev, "%s req_threaded_irq ERR %d\n", __func__, ret); ret = -ENOMEM; goto mx_probe_exit; } } dev_info(&client->dev, "%s done\n", __func__); return 0; mx_probe_exit: mx_remove(client); return ret; } static const struct i2c_device_id mx_i2c_device_id[] = { { MX_NAME, 0 }, { MX_NAME_MAX44005, 0 }, { MX_NAME_MAX44006, 0 }, { MX_NAME_MAX44008, 0 }, {} }; MODULE_DEVICE_TABLE(i2c, mx_i2c_device_id); static const struct of_device_id mx_of_match[] = { { .compatible = "maxim,max4400x", }, { .compatible = "maxim,max44005", }, { .compatible = "maxim,max44006", }, { .compatible = "maxim,max44008", }, {}, }; MODULE_DEVICE_TABLE(of, mx_of_match); static struct i2c_driver mx_driver = { .class = I2C_CLASS_HWMON, .probe = mx_probe, .remove = mx_remove, .shutdown = mx_shutdown, .driver = { .name = MX_NAME, .owner = THIS_MODULE, .of_match_table = of_match_ptr(mx_of_match), .pm = &mx_pm_ops, }, .id_table = mx_i2c_device_id, }; module_i2c_driver(mx_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("MAX4400x driver"); MODULE_AUTHOR("NVIDIA Corporation");