/* * LM3565.c - LM3565 flash/torch kernel driver * * Copyright (c) 2013-2014, NVIDIA CORPORATION. All rights reserved. * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * This program is distributed in the hope 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. * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LM3565_REG_CHIPID 0x00 #define LM3565_REG_VERSION 0x01 #define LM3565_REG_SET_CURR 0x02 #define LM3565_REG_TXMASK 0x03 #define LM3565_REG_LOWVOLTAGE 0x04 #define LM3565_REG_FLASHTIMER 0x05 #define LM3565_REG_STROBE 0x06 #define LM3565_REG_MODE 0x07 #define LM3565_REG_FAULT 0x08 #define LM3565_REG_ADC_CNTL 0x09 #define LM3565_REG_ADC_INPUT 0x0A #define LM3565_REG_ADC_LED 0x0B #define LM3565_REG_MEM_START 0x0C #define LM3565_REG_MEM_END 0x0D #define LM3565_REG_MEM_DATA 0x0E #define LM3565_REG_MEM_CMD 0x0F #define LM3565_REG_STORAGE0 0x10 #define LM3565_REG_STORAGE_SIZE 0x10 #define LM3565_REG_MAX_ADDR 0x1F #define LM3565_MODE_STDBY 0x0F #define LM3565_MODE_TORCH 0x01 #define LM3565_MODE_FLASH 0x02 #define FIELD(x, y) ((x) << (y)) #define LM3565_MAX_ASSIST_CURRENT(x) \ DIV_ROUND_UP(((x) * 0xff * 0x7f / 0xff), 1000) #define LM3565_MAX_INDICATOR_CURRENT(x) \ DIV_ROUND_UP(((x) * 0xff * 0x3f / 0xff), 1000) #define LM3565_MAX_FLASH_LEVEL 17 #define LM3565_MAX_TORCH_LEVEL 3 #define LM3565_FLASH_TIMER_NUM 256 #define LM3565_TORCH_TIMER_NUM 1 #define LM3565_LEVEL_OFF 0xFFFF #define LM3565_MAX_FLASH_TIME_MS 512 #define FLASHTIME(x) ((x) / 2 - 1) #define lm3565_max_flash_cap_size \ (sizeof(struct nvc_torch_flash_capabilities_v1) \ + sizeof(struct nvc_torch_lumi_level_v1) \ * (LM3565_MAX_FLASH_LEVEL)) #define lm3565_flash_timeout_size \ (sizeof(struct nvc_torch_timer_capabilities_v1) \ + sizeof(struct nvc_torch_timeout_v1) \ * LM3565_FLASH_TIMER_NUM) #define lm3565_max_torch_cap_size \ (sizeof(struct nvc_torch_torch_capabilities_v1) \ + sizeof(struct nvc_torch_lumi_level_v1) \ * (LM3565_MAX_TORCH_LEVEL)) #define lm3565_torch_timeout_size \ (sizeof(struct nvc_torch_timer_capabilities_v1) \ + sizeof(struct nvc_torch_timeout_v1) \ * LM3565_TORCH_TIMER_NUM) struct lm3565_caps_struct { char *name; u32 curr_base_uA; u32 curr_step_uA; u32 max_peak_curr_mA; u32 max_assist_curr_mA; }; struct lm3565_reg_cache { u8 dev_id; u8 version; u8 led_curr; u8 txmask; u8 vlow; u8 ftime; u8 strobe; u8 outmode; }; struct lm3565_info { struct i2c_client *i2c_client; struct miscdevice miscdev; struct device *dev; struct dentry *d_lm3565; struct list_head list; struct mutex mutex; struct lm3565_power_rail power; struct regmap *regmap; struct lm3565_platform_data *pdata; struct nvc_torch_capability_query query; struct nvc_torch_flash_capabilities_v1 flash_cap; struct nvc_torch_lumi_level_v1 flash_levels[LM3565_MAX_FLASH_LEVEL]; struct nvc_torch_timer_capabilities_v1 flash_timeouts; struct nvc_torch_timeout_v1 flash_timeout_ary[LM3565_FLASH_TIMER_NUM]; struct nvc_torch_torch_capabilities_v1 torch_cap; struct nvc_torch_lumi_level_v1 torch_levels[LM3565_MAX_TORCH_LEVEL]; struct nvc_torch_timer_capabilities_v1 torch_timeouts; struct nvc_torch_timeout_v1 torch_timeout_ary[LM3565_TORCH_TIMER_NUM]; struct lm3565_caps_struct caps; struct lm3565_config config; struct lm3565_reg_cache regs; atomic_t in_use; int pwr_state; u8 max_flash; u8 max_torch; u8 op_mode; u8 power_on; u8 new_timer; u8 flash_strobe; }; static const struct i2c_device_id lm3565_id[] = { { "lm3565", 0 }, { }, }; MODULE_DEVICE_TABLE(i2c, lm3565_id); static LIST_HEAD(lm3565_info_list); static DEFINE_SPINLOCK(lm3565_spinlock); static struct lm3565_platform_data lm3565_default_pdata = { .cfg = 0, .num = 0, .dev_name = "torch", .pinstate = {0x0000, 0x0000}, }; static struct nvc_torch_capability_query lm3565_query = { .version = NVC_TORCH_CAPABILITY_VER_1, .flash_num = 1, .torch_num = 1, .led_attr = 0, }; static const struct lm3565_caps_struct lm3565_caps = { "lm3565", 480000, 30000, 930, 60, }; static struct nvc_torch_lumi_level_v1 lm3565_def_flash_levels[] = { {0, 480000}, {1, 510000}, {2, 540000}, {3, 570000}, {4, 600000}, {5, 630000}, {6, 660000}, {7, 690000}, {8, 720000}, {9, 750000}, {10, 780000}, {11, 810000}, {12, 840000}, {13, 870000}, {14, 900000}, {15, 930000}, }; static const struct nvc_torch_lumi_level_v1 torch_levels[2] = { {0, 60000}, {1, 90000} }; /* translated from the default register values after power up */ static const struct lm3565_config default_cfg = { .txmask_current_mA = 0, .txmask_inductor_mA = 0, .vin_low_v_mV = 0, .vin_low_c_mA = 0, .strobe_type = 2, .max_peak_current_mA = 900, .max_sustained_current_mA = 0, .max_peak_duration_ms = 0, .min_current_mA = 0, .def_flash_time_mS = LM3565_MAX_FLASH_TIME_MS, .led_config = { .granularity = 1000, .flash_levels = ARRAY_SIZE(lm3565_def_flash_levels), .lumi_levels = lm3565_def_flash_levels, }, }; static const u16 v_in_low[] = { 0, 3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700 }; static const u16 c_in_low[] = {150, 180, 210, 240}; static const u16 tx_c_lut[] = { 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360, 390, 420, 450, 480 }; static const u16 tx_i_lut[] = {2300, 2600, 2900, 3300}; /* torch timer duration settings in uS */ #define LM3565_TORCH_TIMER_FOREVER 0xFFFFFFFF static u32 lm3565_torch_timer[LM3565_TORCH_TIMER_NUM] = { LM3565_TORCH_TIMER_FOREVER }; static int lm3565_power(struct lm3565_info *info, int pwr); static int lm3565_debugfs_init(struct lm3565_info *info); static const struct regmap_config lm3565_regmap_config = { .reg_bits = 8, .val_bits = 8, .cache_type = REGCACHE_NONE, }; static inline int lm3565_reg_rd( struct lm3565_info *info, u8 reg, unsigned int *val) { int ret = -ENODEV; mutex_lock(&info->mutex); if (info->power_on) ret = regmap_read(info->regmap, reg, val); else dev_err(info->dev, "%s: power is off.\n", __func__); mutex_unlock(&info->mutex); return ret; } static int lm3565_reg_raw_wr(struct lm3565_info *info, u8 reg, u8 *buf, u8 num) { int ret = -ENODEV; dev_dbg(info->dev, "%s %x = %x %x\n", __func__, reg, buf[0], buf[1]); mutex_lock(&info->mutex); if (info->power_on) ret = regmap_raw_write(info->regmap, reg, buf, num); else dev_err(info->dev, "%s: power is off.\n", __func__); mutex_unlock(&info->mutex); return ret; } static int lm3565_reg_wr(struct lm3565_info *info, u8 reg, u8 val) { int ret = -ENODEV; dev_dbg(info->dev, "%s\n", __func__); mutex_lock(&info->mutex); if (info->power_on) ret = regmap_write(info->regmap, reg, val); else dev_err(info->dev, "%s: power is off.\n", __func__); mutex_unlock(&info->mutex); return ret; } static int lm3565_get_current_mA(u8 curr) { if (curr >= ARRAY_SIZE(lm3565_def_flash_levels)) return -ENODEV; return lm3565_def_flash_levels[curr].luminance / 1000; } static int lm3565_get_lut_index(u16 val, const u16 *lut, int num) { int idx; for (idx = num - 1; idx >= 0; idx--) if (val >= lut[idx]) break; return idx; } static int lm3565_set_leds(struct lm3565_info *info, u8 curr) { int err = 0; u8 regs[6]; u8 om; u8 stb; if (info->op_mode == LM3565_MODE_STDBY) { err = lm3565_reg_wr(info, LM3565_REG_MODE, 0); if (!err) { info->regs.outmode = 0; } dev_dbg(info->dev, "%s led disabled\n", __func__); goto set_leds_end; } if (info->op_mode == LM3565_MODE_FLASH) { if (curr >= info->max_flash) curr = info->max_flash; om = 0x0b; stb = info->flash_strobe; } else { if (curr >= info->max_torch) curr = info->max_torch; curr <<= 4; om = 0x0a; stb = 0; } regs[0] = curr; regs[1] = info->regs.txmask; regs[2] = info->regs.vlow; regs[3] = info->new_timer; regs[4] = stb; regs[5] = om; err = lm3565_reg_raw_wr(info, LM3565_REG_SET_CURR, regs, sizeof(regs)); if (!err) { info->regs.led_curr = curr; info->regs.ftime = info->new_timer; info->regs.outmode = om; info->regs.strobe = stb; } dev_dbg(info->dev, "%s %x %x control = %x %x\n", __func__, curr, info->new_timer, info->regs.strobe, om); set_leds_end: return err; } static int lm3565_update_settings(struct lm3565_info *info) { int err = 0; u8 regs[2]; if (info->op_mode == LM3565_MODE_STDBY) { regs[0] = info->regs.txmask; regs[1] = info->regs.vlow; err = lm3565_reg_raw_wr( info, LM3565_REG_TXMASK, regs, sizeof(regs)); } err |= lm3565_set_leds(info, info->regs.led_curr); dev_dbg(info->dev, "UP: tx: %x vlow: %x\n", info->regs.txmask, info->regs.vlow); return err; } static int lm3565_set_txmask(struct lm3565_info *info, bool upd) { struct lm3565_config *p_cfg = &info->config; int err = 0; u8 tm; if (!p_cfg->txmask_current_mA && !p_cfg->txmask_inductor_mA) return 0; tm = lm3565_get_lut_index( p_cfg->txmask_current_mA, tx_c_lut, ARRAY_SIZE(tx_c_lut)); info->regs.txmask = FIELD(1, 7) | FIELD(tm, 3); tm = lm3565_get_lut_index( p_cfg->txmask_inductor_mA, tx_i_lut, ARRAY_SIZE(tx_i_lut)); info->regs.txmask |= FIELD(tm, 1); dev_dbg(info->dev, "%s 0x%02x\n", __func__, info->regs.txmask); if (upd) err = lm3565_reg_wr(info, LM3565_REG_TXMASK, info->regs.txmask); return err; } static int lm3565_set_vlow(struct lm3565_info *info, bool upd) { struct lm3565_config *pcfg = &info->config; int err = 0; u8 vl; if (!pcfg->vin_low_v_mV && !pcfg->vin_low_c_mA) return 0; vl = lm3565_get_lut_index( pcfg->vin_low_v_mV, v_in_low, ARRAY_SIZE(v_in_low)); info->regs.vlow = FIELD(1, 6) | FIELD(vl, 3); vl = lm3565_get_lut_index( pcfg->vin_low_c_mA, c_in_low, ARRAY_SIZE(c_in_low)); info->regs.vlow |= FIELD(vl, 1); if (upd) err = lm3565_reg_wr( info, LM3565_REG_LOWVOLTAGE, info->regs.vlow); return err; } static void lm3565_config_init(struct lm3565_info *info) { struct lm3565_config *pcfg = &info->config; struct lm3565_config *pcfg_cust = &info->pdata->config; dev_dbg(info->dev, "%s\n", __func__); memcpy(pcfg, &default_cfg, sizeof(info->config)); if (pcfg_cust->strobe_type) pcfg->strobe_type = pcfg_cust->strobe_type; if (pcfg_cust->def_flash_time_mS) pcfg->def_flash_time_mS = pcfg_cust->def_flash_time_mS; if (pcfg->def_flash_time_mS > 512) pcfg->def_flash_time_mS = 512; if (pcfg_cust->vin_low_v_mV) { if (pcfg_cust->vin_low_v_mV == 0xffff) pcfg->vin_low_v_mV = 0; else pcfg->vin_low_v_mV = pcfg_cust->vin_low_v_mV; } if (pcfg_cust->txmask_current_mA) pcfg->txmask_current_mA = pcfg_cust->txmask_current_mA; if (pcfg_cust->txmask_inductor_mA) pcfg->txmask_inductor_mA = pcfg_cust->txmask_inductor_mA; if (pcfg_cust->max_peak_current_mA) pcfg->max_peak_current_mA = pcfg_cust->max_peak_current_mA; if (pcfg_cust->max_peak_duration_ms) pcfg->max_peak_duration_ms = pcfg_cust->max_peak_duration_ms; if (pcfg_cust->max_torch_current_mA) pcfg->max_torch_current_mA = pcfg_cust->max_torch_current_mA; if (pcfg_cust->max_sustained_current_mA) pcfg->max_sustained_current_mA = pcfg_cust->max_sustained_current_mA; if (pcfg_cust->min_current_mA) pcfg->min_current_mA = pcfg_cust->min_current_mA; } static inline void lm3565_strobe_init(struct lm3565_info *info) { info->flash_strobe = (info->config.strobe_type == 1) ? 0xc0 : (info->config.strobe_type == 2) ? 0x80 : 0x00; } static int lm3565_configure(struct lm3565_info *info, bool update) { struct lm3565_config *pcfg = &info->config; struct lm3565_caps_struct *pcap = &info->caps; struct nvc_torch_flash_capabilities_v1 *pfcap = &info->flash_cap; struct nvc_torch_torch_capabilities_v1 *ptcap = &info->torch_cap; struct nvc_torch_timer_capabilities_v1 *ptmcap; struct nvc_torch_lumi_level_v1 *plvls; int i; dev_dbg(info->dev, "%s\n", __func__); memcpy(&info->query, &lm3565_query, sizeof(info->query)); lm3565_set_txmask(info, false); lm3565_set_vlow(info, false); lm3565_strobe_init(info); info->regs.ftime = FLASHTIME(pcfg->def_flash_time_mS); if (pcfg->max_peak_current_mA > pcap->max_peak_curr_mA || !pcfg->max_peak_current_mA) { dev_notice(info->dev, "max_peak_current_mA of %d invalid changed to %d\n", pcfg->max_peak_current_mA, pcap->max_peak_curr_mA); pcfg->max_peak_current_mA = pcap->max_peak_curr_mA; } if (pcfg->max_sustained_current_mA > pcap->max_assist_curr_mA || !pcfg->max_sustained_current_mA) { dev_notice(info->dev, "max_sustained_current_mA %d invalid changed to %d\n", pcfg->max_sustained_current_mA, pcap->max_assist_curr_mA); pcfg->max_sustained_current_mA = pcap->max_assist_curr_mA; } if ((1000 * pcfg->min_current_mA) < pcap->curr_step_uA) { pcfg->min_current_mA = pcap->curr_step_uA / 1000; dev_notice(info->dev, "min_current_mA lower than possible, increased to %d\n", pcfg->min_current_mA); } pfcap->version = NVC_TORCH_CAPABILITY_VER_1; pfcap->led_idx = 0; pfcap->attribute = 0; pfcap->granularity = pcfg->led_config.granularity; pfcap->levels[0].guidenum = LM3565_LEVEL_OFF; pfcap->levels[0].luminance = 0; plvls = pcfg->led_config.lumi_levels; for (i = 1; i < LM3565_MAX_FLASH_LEVEL; i++) { if (lm3565_get_current_mA(plvls[i - 1].guidenum) > pcfg->max_peak_current_mA) break; pfcap->levels[i].guidenum = plvls[i - 1].guidenum; pfcap->levels[i].luminance = plvls[i - 1].luminance; info->max_flash = plvls[i - 1].guidenum; dev_dbg(info->dev, "%02d - %d\n", pfcap->levels[i].guidenum, pfcap->levels[i].luminance); } pfcap->numberoflevels = i; ptmcap = &info->flash_timeouts; pfcap->timeout_off = (void *)ptmcap - (void *)pfcap; pfcap->timeout_num = LM3565_FLASH_TIMER_NUM; ptmcap->timeout_num = LM3565_FLASH_TIMER_NUM; for (i = 0; i < ptmcap->timeout_num; i++) { ptmcap->timeouts[i].timeout = 2000 * (i + 1); dev_dbg(info->dev, "t: %03d - %06d uS\n", i, ptmcap->timeouts[i].timeout); } ptcap->version = NVC_TORCH_CAPABILITY_VER_1; ptcap->led_idx = 0; ptcap->attribute = 0; ptcap->granularity = pcfg->led_config.granularity; ptcap->levels[0].guidenum = LM3565_LEVEL_OFF; ptcap->levels[0].luminance = 0; for (i = 1; i < LM3565_MAX_TORCH_LEVEL; i++) { if (torch_levels[i - 1].luminance / 1000 > pcfg->max_torch_current_mA) break; ptcap->levels[i].guidenum = torch_levels[i - 1].guidenum; ptcap->levels[i].luminance = torch_levels[i - 1].luminance; info->max_torch = torch_levels[i - 1].guidenum; } ptcap->numberoflevels = i; ptcap->timeout_num = LM3565_TORCH_TIMER_NUM; ptmcap = &info->torch_timeouts; ptcap->timeout_off = (void *)ptmcap - (void *)ptcap; ptmcap->timeout_num = ptcap->timeout_num; for (i = 0; i < ptmcap->timeout_num; i++) { ptmcap->timeouts[i].timeout = lm3565_torch_timer[i]; dev_dbg(info->dev, "t: %02d - %d uS\n", i, ptmcap->timeouts[i].timeout); } if (update && info->pwr_state == NVC_PWR_ON) return lm3565_update_settings(info); return 0; } static int lm3565_strobe(struct lm3565_info *info, int t_on) { struct nvc_gpio_pdata *sg = &info->pdata->strobe_gpio; if (!sg->gpio_type) { dev_err(info->dev, "%s strobe gpio undefined.\n", __func__); return -ENODEV; }; if (!sg->active_high) t_on ^= 1; return gpio_direction_output(sg->gpio, (t_on & 1)); } #ifdef CONFIG_PM static int lm3565_suspend(struct i2c_client *client, pm_message_t msg) { struct lm3565_info *info = i2c_get_clientdata(client); dev_info(&client->dev, "Suspending %s\n", info->caps.name); return 0; } static int lm3565_resume(struct i2c_client *client) { struct lm3565_info *info = i2c_get_clientdata(client); dev_info(&client->dev, "Resuming %s\n", info->caps.name); return 0; } static void lm3565_shutdown(struct i2c_client *client) { struct lm3565_info *info = i2c_get_clientdata(client); dev_info(&client->dev, "Shutting down %s\n", info->caps.name); /* powier off chip to turn off led */ lm3565_power(info, NVC_PWR_OFF); } #endif static int lm3565_power_on(struct lm3565_info *info) { struct lm3565_power_rail *power = &info->power; struct nvc_gpio *gp = &power->enable_gpio; int err = 0; dev_dbg(info->dev, "%s %d\n", __func__, info->power_on); if (info->power_on) return 0; mutex_lock(&info->mutex); if (gp->valid) { dev_dbg(info->dev, "gpio %d %d\n", gp->gpio, gp->active_high); err = gpio_direction_output(gp->gpio, gp->active_high ? 1 : 0); if (err) { dev_err(info->dev, "%s gpio err\n", __func__); goto power_on_end; } } if (power->v_in) { dev_dbg(info->dev, "regulator_enable power->v_in"); err = regulator_enable(power->v_in); if (err) { dev_err(info->dev, "%s v_in err\n", __func__); goto power_on_end; } } if (power->v_i2c) { dev_dbg(info->dev, "regulator_enable power->v_i2c"); err = regulator_enable(power->v_i2c); if (err) { dev_err(info->dev, "%s v_i2c err\n", __func__); if (power->v_in) regulator_disable(power->v_in); goto power_on_end; } } if (info->pdata && info->pdata->power_on_callback) err = info->pdata->power_on_callback(&info->power); if (!err) { usleep_range(1000, 1020); info->power_on = 1; } power_on_end: mutex_unlock(&info->mutex); return err; } static int lm3565_power_off(struct lm3565_info *info) { struct lm3565_power_rail *power = &info->power; struct nvc_gpio *gp = &power->enable_gpio; int err = 0; dev_dbg(info->dev, "%s %d\n", __func__, info->power_on); if (!info->power_on) return 0; mutex_lock(&info->mutex); if (info->pdata && info->pdata->power_off_callback) err = info->pdata->power_off_callback(&info->power); if (IS_ERR_VALUE(err)) goto power_off_end; if (gp->valid) { err = gpio_direction_output(gp->gpio, gp->active_high ? 0 : 1); if (err) { dev_err(info->dev, "%s gpio err\n", __func__); goto power_off_end; } } if (power->v_in) { err = regulator_disable(power->v_in); if (err) { dev_err(info->dev, "%s vi_in err\n", __func__); goto power_off_end; } } if (power->v_i2c) { err = regulator_disable(power->v_i2c); if (err) { dev_err(info->dev, "%s vi_i2c err\n", __func__); goto power_off_end; } } info->power_on = 0; power_off_end: mutex_unlock(&info->mutex); return err; } static int lm3565_power(struct lm3565_info *info, int pwr) { int err = 0; dev_dbg(info->dev, "%s %d %d\n", __func__, pwr, info->pwr_state); if (pwr == info->pwr_state) /* power state no change */ return 0; switch (pwr) { case NVC_PWR_OFF: err = lm3565_set_leds(info, 0); if ((info->pdata->cfg & NVC_CFG_OFF2STDBY) || (info->pdata->cfg & NVC_CFG_BOOT_INIT)) pwr = NVC_PWR_STDBY; else err |= lm3565_power_off(info); break; case NVC_PWR_STDBY_OFF: err = lm3565_set_leds(info, 0); if ((info->pdata->cfg & NVC_CFG_OFF2STDBY) || (info->pdata->cfg & NVC_CFG_BOOT_INIT)) pwr = NVC_PWR_STDBY; else err |= lm3565_power_on(info); break; case NVC_PWR_STDBY: err = lm3565_power_on(info); err |= lm3565_set_leds(info, 0); break; case NVC_PWR_COMM: err = lm3565_power_on(info); break; case NVC_PWR_ON: err = lm3565_power_on(info); if (!err) err = lm3565_update_settings(info); break; default: err = -EINVAL; break; } if (err < 0) { dev_err(info->dev, "%s error\n", __func__); pwr = NVC_PWR_ERR; } info->pwr_state = pwr; if (err > 0) return 0; return err; } static int lm3565_get_dev_id(struct lm3565_info *info) { int pwr = info->pwr_state; unsigned int devid; unsigned int version; int err; dev_dbg(info->dev, "%s %02x %d\n", __func__, info->regs.dev_id, pwr); /* ChipID[7:3] is a fixed identification B0 */ if ((info->regs.dev_id & 0x34) == 0x34) return 0; lm3565_power(info, NVC_PWR_COMM); err = lm3565_reg_rd(info, LM3565_REG_CHIPID, &devid); if (!err && (devid & 0x34) != 0x34) err = -ENODEV; else err |= lm3565_reg_rd( info, LM3565_REG_VERSION, &version); lm3565_power(info, pwr); dev_dbg(info->dev, "%s: %02x %02x err = %02x\n", __func__, devid, version, err); if (err) dev_err(info->dev, "%s failed.\n", __func__); else { info->regs.dev_id = (u8)devid; info->regs.version = (u8)version; } return err; } static int lm3565_user_get_param(struct lm3565_info *info, long arg) { struct nvc_param params; struct nvc_torch_pin_state pinstate; const void *data_ptr = NULL; int err = 0; u32 data_size = 0; u8 reg; if (copy_from_user(¶ms, (const void __user *)arg, sizeof(struct nvc_param))) { dev_err(info->dev, "%s %d copy_from_user err\n", __func__, __LINE__); return -EINVAL; } switch (params.param) { case NVC_PARAM_TORCH_QUERY: dev_dbg(info->dev, "%s QUERY\n", __func__); data_ptr = &info->query; data_size = sizeof(info->query); break; case NVC_PARAM_FLASH_EXT_CAPS: dev_dbg(info->dev, "%s EXT_FLASH_CAPS\n", __func__); if (params.variant >= info->query.flash_num) { dev_err(info->dev, "%s unsupported flash index %d.\n", __func__, params.variant); return -EINVAL; } data_ptr = &info->flash_cap; data_size = lm3565_max_flash_cap_size + lm3565_flash_timeout_size; break; case NVC_PARAM_TORCH_EXT_CAPS: dev_dbg(info->dev, "%s EXT_TORCH_CAPS\n", __func__); if (params.variant >= info->query.torch_num) { dev_err(info->dev, "%s unsupported torch index %d.\n", __func__, params.variant); return -EINVAL; } data_ptr = &info->torch_cap; data_size = lm3565_max_torch_cap_size + lm3565_torch_timeout_size; break; case NVC_PARAM_FLASH_LEVEL: dev_dbg(info->dev, "%s FLASH_LEVEL\n", __func__); reg = info->regs.led_curr; data_ptr = ® data_size = sizeof(reg); break; case NVC_PARAM_TORCH_LEVEL: dev_dbg(info->dev, "%s TORCH_LEVEL\n", __func__); reg = info->regs.led_curr; data_ptr = ® data_size = sizeof(reg); break; case NVC_PARAM_FLASH_PIN_STATE: dev_dbg(info->dev, "%s FLASH_CAPS\n", __func__); /* By default use Active Pin State Setting */ pinstate = info->pdata->pinstate; if ((info->op_mode != LM3565_MODE_FLASH) || (!info->regs.led_curr)) pinstate.values ^= 0xffff; /* Inactive Pin Setting */ dev_dbg(info->dev, "%s FLASH_PIN_STATE: %x&%x\n", __func__, pinstate.mask, pinstate.values); data_ptr = &pinstate; data_size = sizeof(pinstate); break; default: dev_err(info->dev, "%s unsupported parameter: %d\n", __func__, params.param); err = -EINVAL; break; } if (!err && params.sizeofvalue < data_size) { dev_err(info->dev, "%s data size mismatch %d != %d\n", __func__, params.sizeofvalue, data_size); err = -EINVAL; } if (!err && copy_to_user((void __user *)params.p_value, data_ptr, data_size)) { dev_err(info->dev, "%s copy_to_user err line %d\n", __func__, __LINE__); err = -EFAULT; } return err; } static int lm3565_get_levels(struct lm3565_info *info, struct nvc_param *params, bool is_flash, struct nvc_torch_set_level_v1 *plevels) { struct nvc_torch_timer_capabilities_v1 *p_tm; u8 op_mode; if (copy_from_user(plevels, (const void __user *)params->p_value, sizeof(*plevels))) { dev_err(info->dev, "%s %d copy_from_user err\n", __func__, __LINE__); return -EINVAL; } if (is_flash) { dev_dbg(info->dev, "%s FLASH_LEVEL: %d %d\n", __func__, plevels->ledmask, plevels->levels[0]); op_mode = LM3565_MODE_FLASH; p_tm = &info->flash_timeouts; } else { dev_dbg(info->dev, "%s TORCH_LEVEL: %d %d\n", __func__, plevels->ledmask, plevels->levels[0]); op_mode = LM3565_MODE_TORCH; p_tm = &info->torch_timeouts; } if (plevels->timeout) { u16 i; for (i = 0; i < p_tm->timeout_num; i++) { plevels->timeout = i; if (plevels->timeout == p_tm->timeouts[i].timeout) break; } } else plevels->timeout = p_tm->timeout_num - 1; if (plevels->levels[0] == LM3565_LEVEL_OFF) plevels->ledmask = 0; if (!plevels->ledmask) info->op_mode = LM3565_MODE_STDBY; else info->op_mode = op_mode; dev_dbg(info->dev, "Return: %d - %d %d %d\n", info->op_mode, plevels->ledmask, plevels->levels[0], plevels->levels[1]); return 0; } static int lm3565_user_set_param(struct lm3565_info *info, long arg) { struct nvc_param params; struct nvc_torch_set_level_v1 led_levels; int err = 0; u8 val; if (copy_from_user(¶ms, (const void __user *)arg, sizeof(struct nvc_param))) { dev_err(info->dev, "%s %d copy_from_user err\n", __func__, __LINE__); return -EINVAL; } switch (params.param) { case NVC_PARAM_FLASH_LEVEL: lm3565_get_levels(info, ¶ms, true, &led_levels); info->new_timer = led_levels.timeout; dev_dbg(info->dev, "%s FLASH_LEVEL: %d %d\n", __func__, led_levels.levels[0], info->new_timer); err = lm3565_set_leds(info, led_levels.levels[0]); break; case NVC_PARAM_TORCH_LEVEL: lm3565_get_levels(info, ¶ms, false, &led_levels); info->new_timer = led_levels.timeout; dev_dbg(info->dev, "%s TORCH_LEVEL: %d %d\n", __func__, led_levels.levels[0], info->new_timer); err = lm3565_set_leds(info, led_levels.levels[0]); break; case NVC_PARAM_FLASH_PIN_STATE: if (copy_from_user(&val, (const void __user *)params.p_value, sizeof(val))) { dev_err(info->dev, "%s %d copy_from_user err\n", __func__, __LINE__); return -EINVAL; } dev_dbg(info->dev, "%s FLASH_PIN_STATE: %d\n", __func__, val); err = lm3565_strobe(info, val); break; default: dev_err(info->dev, "%s unsupported parameter: %d\n", __func__, params.param); err = -EINVAL; break; } return err; } static long lm3565_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct lm3565_info *info = file->private_data; int pwr; int err = 0; switch (_IOC_NR(cmd)) { case _IOC_NR(NVC_IOCTL_PARAM_WR): err = lm3565_user_set_param(info, arg); break; case _IOC_NR(NVC_IOCTL_PARAM_RD): err = lm3565_user_get_param(info, arg); break; case _IOC_NR(NVC_IOCTL_PWR_WR): /* This is a Guaranteed Level of Service (GLOS) call */ pwr = (int)arg * 2; dev_dbg(info->dev, "%s PWR_WR: %d\n", __func__, pwr); if (!pwr || (pwr > NVC_PWR_ON)) { /* Invalid Power State */ dev_err(info->dev, "unsupported power state - %ld\n", arg); err = -EFAULT; break; } err = lm3565_power(info, pwr); if (info->pdata->cfg & NVC_CFG_NOERR) err = 0; break; case _IOC_NR(NVC_IOCTL_PWR_RD): pwr = info->pwr_state / 2; dev_dbg(info->dev, "%s PWR_RD: %d\n", __func__, pwr); if (copy_to_user((void __user *)arg, (const void *)&pwr, sizeof(pwr))) { dev_err(info->dev, "%s copy_to_user err line %d\n", __func__, __LINE__); err = -EFAULT; } break; default: dev_err(info->dev, "%s unsupported ioctl: %x\n", __func__, cmd); err = -EINVAL; break; } return err; } static int lm3565_open(struct inode *inode, struct file *file) { struct lm3565_info *info = NULL; struct lm3565_info *pos = NULL; rcu_read_lock(); list_for_each_entry_rcu(pos, &lm3565_info_list, list) { if (pos->miscdev.minor == iminor(inode)) { info = pos; break; } } rcu_read_unlock(); if (!info) return -ENODEV; if (atomic_xchg(&info->in_use, 1)) return -EBUSY; file->private_data = info; dev_dbg(info->dev, "%s\n", __func__); return 0; } static int lm3565_release(struct inode *inode, struct file *file) { struct lm3565_info *info = file->private_data; dev_dbg(info->dev, "%s\n", __func__); lm3565_power(info, NVC_PWR_OFF); file->private_data = NULL; WARN_ON(!atomic_xchg(&info->in_use, 0)); return 0; } static int lm3565_power_put(struct lm3565_power_rail *pw) { if (likely(pw->v_in)) regulator_put(pw->v_in); if (likely(pw->v_i2c)) regulator_put(pw->v_i2c); if (pw->enable_gpio.flag) gpio_free(pw->enable_gpio.gpio); memset(&pw->enable_gpio, 0, sizeof(pw->enable_gpio)); pw->v_in = NULL; pw->v_i2c = NULL; return 0; } static int lm3565_regulator_get(struct lm3565_info *info, struct regulator **vreg, char vreg_name[]) { struct regulator *reg = NULL; int err = 0; reg = regulator_get(info->dev, vreg_name); if (unlikely(IS_ERR(reg))) { dev_err(info->dev, "%s %s ERR: %d\n", __func__, vreg_name, (int)reg); err = PTR_ERR(reg); reg = NULL; } else dev_dbg(info->dev, "%s: %s\n", __func__, vreg_name); *vreg = reg; return err; } static int lm3565_control_init(struct lm3565_info *info) { struct nvc_gpio_pdata *gp = &info->pdata->enable_gpio; struct lm3565_power_rail *pw = &info->power; int ret = 0; dev_dbg(info->dev, "%s\n", __func__); lm3565_regulator_get(info, &pw->v_in, "vin"); /* 3.7v */ lm3565_regulator_get(info, &pw->v_i2c, "vi2c"); /* 1.8v */ info->pwr_state = NVC_PWR_OFF; if (gp->gpio_type) { dev_dbg(info->dev, "gpio: %d %d\n", gp->gpio, gp->active_high); pw->enable_gpio.gpio = gp->gpio; pw->enable_gpio.active_high = gp->active_high; if (gp->init_en) { ret = gpio_request(gp->gpio, "lm3565_enable"); if (ret < 0) { dev_err(info->dev, "cannot request enable gpio %d\n", gp->gpio); goto control_init_end; } pw->enable_gpio.flag = true; /* requested */ ret = gpio_direction_output( gp->gpio, gp->active_high ? 0 : 1); if (ret < 0) { dev_err(info->dev, "cannot init gpio %d\n", gp->gpio); goto control_init_end; } pw->enable_gpio.own = true; } pw->enable_gpio.valid = true; } gp = &info->pdata->strobe_gpio; if (gp->gpio_type) { dev_dbg(info->dev, "strobe: %d %d\n", gp->gpio, gp->active_high); if (gp->init_en) { ret = gpio_request(gp->gpio, "lm3565_strobe"); if (ret < 0) { dev_err(info->dev, "cannot request strobe gpio: %d\n", gp->gpio); goto control_init_end; } ret = gpio_direction_output( gp->gpio, gp->active_high ? 0 : 1); if (ret < 0) { dev_err(info->dev, "cannot init gpio %d\n", gp->gpio); gpio_free(gp->gpio); } } } control_init_end: if (ret < 0) lm3565_power_put(pw); return ret; } static inline void lm3565_strobe_free(struct lm3565_info *info) { struct nvc_gpio_pdata *gp = &info->pdata->strobe_gpio; if (gp->gpio_type && gp->init_en) gpio_free(gp->gpio); } static const struct file_operations lm3565_fileops = { .owner = THIS_MODULE, .open = lm3565_open, .unlocked_ioctl = lm3565_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = lm3565_ioctl, #endif .release = lm3565_release, }; static void lm3565_del(struct lm3565_info *info) { lm3565_power(info, NVC_PWR_OFF); lm3565_power_put(&info->power); lm3565_strobe_free(info); spin_lock(&lm3565_spinlock); list_del_rcu(&info->list); spin_unlock(&lm3565_spinlock); synchronize_rcu(); } static int lm3565_remove(struct i2c_client *client) { struct lm3565_info *info = i2c_get_clientdata(client); dev_dbg(info->dev, "%s\n", __func__); misc_deregister(&info->miscdev); lm3565_del(info); return 0; } static int lm3565_probe( struct i2c_client *client, const struct i2c_device_id *id) { struct lm3565_info *info; char dname[16]; int err; dev_dbg(&client->dev, "%s\n", __func__); info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); if (info == NULL) { dev_err(&client->dev, "%s: kzalloc error\n", __func__); return -ENOMEM; } info->regmap = devm_regmap_init_i2c(client, &lm3565_regmap_config); if (IS_ERR(info->regmap)) { dev_err(&client->dev, "regmap init failed: %ld\n", PTR_ERR(info->regmap)); return -ENODEV; } info->i2c_client = client; info->dev = &client->dev; if (client->dev.platform_data) info->pdata = client->dev.platform_data; else { info->pdata = &lm3565_default_pdata; dev_dbg(&client->dev, "%s No platform data. Using defaults.\n", __func__); } memcpy(&info->caps, &lm3565_caps, sizeof(info->caps)); lm3565_config_init(info); info->op_mode = LM3565_MODE_STDBY; lm3565_configure(info, false); i2c_set_clientdata(client, info); mutex_init(&info->mutex); INIT_LIST_HEAD(&info->list); spin_lock(&lm3565_spinlock); list_add_rcu(&info->list, &lm3565_info_list); spin_unlock(&lm3565_spinlock); lm3565_control_init(info); err = lm3565_get_dev_id(info); if (err < 0) { dev_err(&client->dev, "%s device not found\n", __func__); if (info->pdata->cfg & NVC_CFG_NODEV) { lm3565_del(info); return -ENODEV; } } else dev_info(&client->dev, "%s device %02x found\n", __func__, info->regs.dev_id); if (info->pdata->dev_name != 0) strcpy(dname, info->pdata->dev_name); else strcpy(dname, "lm3565"); if (info->pdata->num) snprintf(dname, sizeof(dname), "%s.%u", dname, info->pdata->num); info->miscdev.name = dname; info->miscdev.fops = &lm3565_fileops; info->miscdev.minor = MISC_DYNAMIC_MINOR; if (misc_register(&info->miscdev)) { dev_err(&client->dev, "%s unable to register misc device %s\n", __func__, dname); lm3565_del(info); return -ENODEV; } lm3565_debugfs_init(info); return 0; } static int lm3565_status_show(struct seq_file *s, void *data) { struct lm3565_info *k_info = s->private; struct lm3565_config *pcfg = &k_info->config; pr_info("%s\n", __func__); seq_printf(s, "lm3565 status:\n" " Flash type: %s, bus %d, addr: 0x%02x\n\n" " Power State = %01x\n" " Led Current = 0x%02x\n" " Flash Mode = 0x%02x\n" " Flash TimeOut = 0x%02x\n" " Flash Strobe = 0x%02x\n" " Max_Peak_Current = 0x%04dmA\n" " TxMask_Current = 0x%04dmA\n" " TxMask_Inductor = 0x%04dmA\n" " VIN_low = 0x%04dmV\n" " CIN_low = 0x%04dmA\n" " PinState Mask = 0x%04x\n" " PinState Values = 0x%04x\n" , (char *)lm3565_id[k_info->pdata->type + 1].name, k_info->i2c_client->adapter->nr, k_info->i2c_client->addr, k_info->pwr_state, k_info->regs.led_curr, k_info->op_mode, k_info->regs.ftime, pcfg->strobe_type, pcfg->max_peak_current_mA, pcfg->txmask_current_mA, pcfg->txmask_inductor_mA, pcfg->vin_low_v_mV, pcfg->vin_low_c_mA, k_info->pdata->pinstate.mask, k_info->pdata->pinstate.values ); return 0; } static ssize_t lm3565_attr_set(struct file *s, const char __user *user_buf, size_t count, loff_t *ppos) { struct lm3565_info *k_info = ((struct seq_file *)s->private_data)->private; char buf[24]; int buf_size; u32 val = 0; pr_info("%s\n", __func__); if (!user_buf || count <= 1) return -EFAULT; memset(buf, 0, sizeof(buf)); buf_size = min(count, sizeof(buf) - 1); if (copy_from_user(buf, user_buf, buf_size)) return -EFAULT; if (sscanf(buf + 1, "0x%x", &val) == 1) goto set_attr; if (sscanf(buf + 1, "0X%x", &val) == 1) goto set_attr; if (sscanf(buf + 1, "%d", &val) == 1) goto set_attr; pr_info("SYNTAX ERROR: %s\n", buf); return -EFAULT; set_attr: pr_info("new data = %x\n", val); switch (buf[0]) { /* enable/disable power */ case 'p': if (val & 0xffff) lm3565_power(k_info, NVC_PWR_ON); else lm3565_power(k_info, NVC_PWR_OFF); break; /* change led current */ case 'c': lm3565_set_leds(k_info, val & 0xff); break; /* modify flash timeout reg */ case 'f': k_info->new_timer = val & 0xff; break; /* set led work mode/trigger mode */ case 'x': if (val & 0xf) k_info->op_mode = val & 0xf; if (val & 0xf0) { k_info->config.strobe_type = (val & 0xf0) >> 4; lm3565_strobe_init(k_info); } break; /* set max_peak_current_mA */ case 'k': if (val & 0xffff) k_info->config.max_peak_current_mA = val & 0xffff; lm3565_configure(k_info, true); break; /* change txmask settings */ case 't': if (val & 0x0ffff) k_info->config.txmask_current_mA = val & 0x0ffff; if (val & 0x0ffff0000) k_info->config.txmask_inductor_mA = (val >> 16) & 0x0ffff; lm3565_set_txmask(k_info, true); break; /* change low voltage/current settings */ case 'v': if (val & 0xffff) k_info->config.vin_low_v_mV = val & 0xffff; if (val & 0x0ffff0000) k_info->config.vin_low_c_mA = (val >> 16) & 0x0ffff; lm3565_set_vlow(k_info, true); break; /* change pinstate setting */ case 'm': k_info->pdata->pinstate.mask = (val >> 16) & 0xffff; k_info->pdata->pinstate.values = val & 0xffff; break; /* trigger an external flash/torch event */ case 'g': lm3565_strobe(k_info, val); break; } return count; } static int lm3565_debugfs_open(struct inode *inode, struct file *file) { return single_open(file, lm3565_status_show, inode->i_private); } static const struct file_operations lm3565_debugfs_fops = { .open = lm3565_debugfs_open, .read = seq_read, .write = lm3565_attr_set, .llseek = seq_lseek, .release = single_release, }; static int lm3565_debugfs_init(struct lm3565_info *info) { struct dentry *d; info->d_lm3565 = debugfs_create_dir( info->miscdev.this_device->kobj.name, NULL); if (info->d_lm3565 == NULL) { pr_info("%s: debugfs create dir failed\n", __func__); return -ENOMEM; } d = debugfs_create_file("d", S_IRUGO|S_IWUSR, info->d_lm3565, (void *)info, &lm3565_debugfs_fops); if (!d) { pr_info("%s: debugfs create file failed\n", __func__); debugfs_remove_recursive(info->d_lm3565); info->d_lm3565 = NULL; } return -EFAULT; } static struct i2c_driver lm3565_driver = { .driver = { .name = "lm3565", .owner = THIS_MODULE, }, .id_table = lm3565_id, .probe = lm3565_probe, .remove = lm3565_remove, #ifdef CONFIG_PM .shutdown = lm3565_shutdown, .suspend = lm3565_suspend, .resume = lm3565_resume, #endif }; module_i2c_driver(lm3565_driver); MODULE_DESCRIPTION("LM3565 flash/torch driver"); MODULE_AUTHOR("Charlie Huang "); MODULE_LICENSE("GPL");