media: video: tegra: max77387 flash device bringup
Charlie Huang [Sat, 26 Jan 2013 06:11:33 +0000 (11:11 +0530)]
bug 1058706

Change-Id: Id7c91b52d595230144d67955b3fc6a9148cd3d2b
Signed-off-by: Charlie Huang <chahuang@nvidia.com>
Reviewed-on: http://git-master/r/194362
Tested-by: Sudhir Vyas <svyas@nvidia.com>
GVS: Gerrit_Virtual_Submit
Reviewed-by: Bitan Biswas <bbiswas@nvidia.com>

drivers/media/video/tegra/Kconfig
drivers/media/video/tegra/Makefile
drivers/media/video/tegra/max77387.c [new file with mode: 0644]
include/media/max77387.h [new file with mode: 0644]

index 8d59c89..1b5cc78 100644 (file)
@@ -100,6 +100,13 @@ config MAX77665_FLASH
         ---help---
           This is a driver for the MAX77665 flash/torch camera device
 
+config TORCH_MAX77387
+        tristate "MAX77387 flash/torch support"
+        depends on I2C && ARCH_TEGRA
+        ---help---
+          This is a driver for the MAX77387 flash/torch camera device
+          for use with the tegra isp.
+
 config TORCH_AS364X
         tristate "AS364X flash/torch support"
         depends on I2C && ARCH_TEGRA
index 699c095..c8cea53 100644 (file)
@@ -19,6 +19,7 @@ obj-$(CONFIG_VIDEO_SOC380)    += soc380.o
 obj-$(CONFIG_TORCH_SSL3250A)   += ssl3250a.o
 obj-$(CONFIG_TORCH_TPS61050)   += tps61050.o
 obj-$(CONFIG_MAX77665_FLASH)   += max77665-flash.o
+obj-$(CONFIG_TORCH_MAX77387)   += max77387.o
 obj-$(CONFIG_TORCH_AS364X)     += as364x.o
 obj-$(CONFIG_VIDEO_SH532U)     += sh532u.o
 obj-$(CONFIG_VIDEO_AD5820)     += ad5820.o
diff --git a/drivers/media/video/tegra/max77387.c b/drivers/media/video/tegra/max77387.c
new file mode 100644 (file)
index 0000000..c5ce025
--- /dev/null
@@ -0,0 +1,1940 @@
+/*
+ * MAX77387.c - MAX77387 flash/torch kernel driver
+ *
+ * Copyright (c) 2013, 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 <http://www.gnu.org/licenses/>.
+ */
+
+/* Implementation
+ * --------------
+ * The board level details about the device need to be provided in the board
+ * file with the max77387_platform_data structure.
+ * Standard among NVC kernel drivers in this structure is:
+ * .cfg = Use the NVC_CFG_ defines that are in nvc_torch.h.
+ *        Descriptions of the configuration options are with the defines.
+ *        This value is typically 0.
+ * .num = The number of the instance of the device.  This should start at 1 and
+ *        and increment for each device on the board.  This number will be
+ *        appended to the MISC driver name, Example: /dev/torch.1
+ * .dev_name = The MISC driver name the device registers as.  If not used,
+ *             then the part number of the device is used for the driver name.
+ *             If using the NVC user driver then use the name found in this
+ *             driver under _default_pdata.
+ *
+ * The following is specific to NVC kernel flash/torch drivers:
+ * .pinstate = a pointer to the nvc_torch_pin_state structure.  This
+ *             structure gives the details of which VI GPIO to use to trigger
+ *             the flash.  The mask tells which pin and the values is the
+ *             level.  For example, if VI GPIO pin 6 is used, then
+ *             .mask = 0x0040
+ *             .values = 0x0040
+ *             If VI GPIO pin 0 is used, then
+ *             .mask = 0x0001
+ *             .values = 0x0001
+ *             This is typically just one pin but there is some legacy
+ *             here that insinuates more than one pin can be used.
+ *             When the flash level is set, then the driver will return the
+ *             value in values.  When the flash level is off, the driver will
+ *             return 0 for the values to deassert the signal.
+ *             If a VI GPIO is not used, then the mask and values must be set
+ *             to 0.  The flash may then be triggered via I2C instead.
+ *             However, a VI GPIO is strongly encouraged since it allows
+ *             tighter timing with the picture taken as well as reduced power
+ *             by asserting the trigger signal for only when needed.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/i2c.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+#include <linux/edp.h>
+#include <linux/regmap.h>
+#include <media/nvc.h>
+#include <media/max77387.h>
+
+#define MAX77387_RO_CHIPID1            0x00
+#define MAX77387_RO_CHIPID2            0x01
+#define MAX77387_RO_FLASH_STATUS       0x02
+#define MAX77387_RO_FLASH_STATUS2      0x03
+
+#define MAX77387_RW_FLASH_FLED1CURR    0x04
+#define MAX77387_RW_FLASH_FLED2CURR    0x05
+#define MAX77387_RW_TORCH_FLED1CURR    0x06
+#define MAX77387_RW_TORCH_FLED2CURR    0x07
+#define MAX77387_RW_FLED_MODE          0x08
+
+#define MAX77387_RW_TX1_MASK           0x09
+#define MAX77387_RW_TX2_MASK           0x0A
+#define MAX77387_RW_FLASH_RAMP         0x0B
+#define MAX77387_RW_TORCH_RAMP         0x0C
+
+#define MAX77387_RW_FLASH_TIMER                0x0D
+#define MAX77387_RW_TORCH_TIMER                0x0E
+
+#define MAX77387_RW_MAXFLASH_HYS_TH    0x10
+#define MAX77387_RW_MAXFLASH_LB_TMR    0x11
+#define MAX77387_RO_MAXFLASH_FLED1_IMIN        0x12
+#define MAX77387_RO_MAXFLASH_FLED2_IMIN        0x13
+#define MAX77387_RW_NTC                        0x14
+
+#define MAX77387_RW_DCDC_CNTL1         0X15
+#define MAX77387_RW_DCDC_CNTL2         0X16
+#define MAX77387_RW_DCDC_ILIM          0X17
+#define MAX77387_RO_DCDC_OUT           0X18
+#define MAX77387_RO_DCDC_MAX           0X19
+
+#define FIELD(x, y)                    ((x) << (y))
+#define FMASK(x)                       FIELD(7, (x))
+
+#define TORCH_TIMER_SAFETY_DIS         0x1
+
+#define TIMER_ONESHOT                  0x0
+#define TIMER_MAX                      0x1
+#define TTIMER_DIS                     0x1
+
+#define TORCH_TIMER_CTL_MASK           (FIELD(TIMER_MAX, 7) | \
+                                       FIELD(TORCH_TIMER_SAFETY_DIS, 6))
+
+#define TORCH_MODE_SHIFT               3
+#define FLASH_MODE_SHIFT               0
+
+#define TORCH_TRIG_MASK                        FMASK(TORCH_MODE_SHIFT)
+#define FLASH_TRIG_MASK                        FMASK(FLASH_MODE_SHIFT)
+
+/* TO DO: Need to confirm with maxim these trigger settings */
+#define TRIG_MODE_OFF                  0x00
+#define TRIG_MODE_FLASHEN              0x02
+#define TRIG_MODE_TORCHEN              0x01
+#define TRIG_MODE_FANDT                        0x03
+#define TRIG_MODE_FORT                 0x04
+#define TRIG_MODE_I2C                  0x05
+
+#define TORCH_TRIG_BY_I2C      FIELD(TRIG_MODE_I2C, TORCH_MODE_SHIFT)
+#define TORCH_TRIG_BY_FLASHEN  FIELD(TRIG_MODE_FLASHEN, TORCH_MODE_SHIFT)
+#define TORCH_TRIG_BY_TORCHEN  FIELD(TRIG_MODE_TORCHEN, TORCH_MODE_SHIFT)
+#define TORCH_TRIG_BY_FANDT    FIELD(TRIG_MODE_FANDT, TORCH_MODE_SHIFT)
+#define TORCH_TRIG_BY_FORT     FIELD(TRIG_MODE_FORT, TORCH_MODE_SHIFT)
+
+#define FLASH_TRIG_BY_I2C      FIELD(TRIG_MODE_I2C, FLASH_MODE_SHIFT)
+#define FLASH_TRIG_BY_FLASHEN  FIELD(TRIG_MODE_FLASHEN, FLASH_MODE_SHIFT)
+#define FLASH_TRIG_BY_TORCHEN  FIELD(TRIG_MODE_TORCHEN, FLASH_MODE_SHIFT)
+#define FLASH_TRIG_BY_FANDT    FIELD(TRIG_MODE_FANDT, FLASH_MODE_SHIFT)
+#define FLASH_TRIG_BY_FORT     FIELD(TRIG_MODE_FORT, FLASH_MODE_SHIFT)
+
+#define FLASH_ENABLE                   FIELD(1, 7)
+#define FLASH_CURRENT(x)               (FLASH_ENABLE | FIELD((x), 0))
+#define TORCH_ENABLE                   FIELD(1, 7)
+#define TORCH_CURRENT(x)               (TORCH_ENABLE | FIELD((x), 1))
+
+#define MAXFLASH_DISABLE               0
+#define MAXFLASH_ENABLE                        1
+
+#define MAXFLASH_VOLT_HYS_FLOOR                100 /* mV */
+#define MAXFLASH_VOLT_HYS_CEILING      300 /* mV */
+#define MAXFLASH_VOLT_HYS_STEP         100 /* mV */
+
+#define MAXFLASH_V_TH_FLOOR            2400 /* mV */
+#define MAXFLASH_V_TH_CEILING          3400 /* mV */
+#define MAXFLASH_V_TH_STEP             33 /* mV */
+
+#define MAXFLASH_TIMER_STEP            256 /* uS */
+
+#define MAX77387_LED_NUM               2
+#define MAX77387_FLASH_LEVELS          (1 << 6)
+#define MAX77387_MAX_FLASH_LEVEL       (MAX77387_FLASH_LEVELS + 1)
+#define MAX77387_TORCH_LEVELS          (1 << 6)
+#define MAX77387_MAX_TORCH_LEVEL       (MAX77387_TORCH_LEVELS + 1)
+
+#define MAX77387_LEVEL_OFF             0xFFFF
+
+#define MAX77387_MAX_FLASH_CURRENT(x)    \
+       DIV_ROUND_UP(((x) * MAX77387_MAX_FLASH_LEVEL), 1000)
+#define MAX77387_MAX_TORCH_CURRENT(x) \
+       DIV_ROUND_UP(((x) * MAX77387_MAX_TORCH_LEVEL), 1000)
+
+#define MAX77387_FLASH_TIMER_NUM       (1 << 7)
+#define MAX77387_TORCH_TIMER_NUM       (1 << 5)
+#define MAX77387_FTIMER_MASK           (MAX77387_FLASH_TIMER_NUM - 1)
+#define MAX77387_TTIMER_MASK           (MAX77387_TORCH_TIMER_NUM - 1)
+
+#define MAX77387_TX_MASK_ENABLE                FIELD(1, 7)
+
+#define MAXFLASH_MODE_NONE             0
+#define MAXFLASH_MODE_TORCH            1
+#define MAXFLASH_MODE_FLASH            2
+
+#define max77387_flash_cap_size \
+                       (sizeof(struct nvc_torch_flash_capabilities_v1) \
+                       + sizeof(struct nvc_torch_lumi_level_v1) \
+                       * MAX77387_MAX_FLASH_LEVEL)
+#define max77387_flash_timeout_size \
+                       (sizeof(struct nvc_torch_timer_capabilities_v1) \
+                       + sizeof(struct nvc_torch_timeout_v1) \
+                       * MAX77387_FLASH_TIMER_NUM)
+#define max77387_max_flash_cap_size (max77387_flash_cap_size * 2 \
+                       + max77387_flash_timeout_size * 2)
+
+#define max77387_torch_cap_size \
+                       (sizeof(struct nvc_torch_torch_capabilities_v1) \
+                       + sizeof(struct nvc_torch_lumi_level_v1) \
+                       * MAX77387_MAX_TORCH_LEVEL)
+#define max77387_torch_timeout_size \
+                       (sizeof(struct nvc_torch_timer_capabilities_v1) \
+                       + sizeof(struct nvc_torch_timeout_v1) \
+                       * MAX77387_TORCH_TIMER_NUM)
+#define max77387_max_torch_cap_size (max77387_torch_timeout_size * 2\
+                       + max77387_torch_timeout_size * 2)
+
+#define GET_CURRENT_BY_INDEX(c)        ((c) * 125 / 8)         /* mul 15.625 mA */
+#define GET_INDEX_BY_CURRENT(c)        ((c) * 8 / 125)         /* div by 15.625 mA */
+
+struct max77387_caps_struct {
+       u32 curr_step_uA;
+       u32 max_peak_curr_mA;
+       u32 max_torch_curr_mA;
+       u32 max_total_current_mA;
+};
+
+struct max77387_settings {
+       u8 fled_trig;
+       u8 tx1_mask;
+       u8 tx2_mask;
+       u8 flash_ramp;
+       u8 torch_ramp;
+       u8 max_flash1;
+       u8 max_flash2;
+       u8 ntc;
+       u8 dcdc_cntl1;
+       u8 dcdc_cntl2;
+       u8 dcdc_lim;
+};
+
+struct max77387_reg_cache {
+       bool regs_stale;
+       u8 led1_fcurr;
+       u8 led2_fcurr;
+       u8 led1_tcurr;
+       u8 led2_tcurr;
+       u8 leds_en;
+       u8 tmask_led1;
+       u8 tmask_led2;
+       u8 framp;
+       u8 tramp;
+       u8 f_timer;
+       u8 t_timer;
+};
+
+struct max77387_info {
+       struct i2c_client *i2c_client;
+       struct miscdevice miscdev;
+       struct device *dev;
+       struct dentry *d_max77387;
+       struct mutex mutex;
+       struct max77387_power_rail pwr_rail;
+       struct max77387_platform_data *pdata;
+       struct nvc_torch_capability_query query;
+       struct nvc_torch_flash_capabilities_v1 *flash_cap[2];
+       struct nvc_torch_timer_capabilities_v1 *flash_timeouts[2];
+       struct nvc_torch_torch_capabilities_v1 *torch_cap[2];
+       struct nvc_torch_timer_capabilities_v1 *torch_timeouts[2];
+       struct max77387_config config;
+       struct max77387_reg_cache regs;
+       struct max77387_settings settings;
+       struct regmap *regmap;
+       struct edp_client *edpc;
+       unsigned edp_state;
+       atomic_t in_use;
+       int flash_cap_size;
+       int torch_cap_size;
+       int pwr_state;
+       u16 chip_id;
+       u8 op_mode;
+       u8 power_is_on;
+       u8 ftimer_mode;
+       u8 ttimer_mode;
+       u8 new_timer;
+};
+
+static const struct max77387_caps_struct max77387_caps = {
+       .curr_step_uA = 15625,
+       .max_peak_curr_mA = MAX77387_MAX_FLASH_CURRENT(15625),
+       .max_torch_curr_mA = MAX77387_MAX_TORCH_CURRENT(3906),
+       .max_total_current_mA = 1000 * 2
+};
+
+static struct nvc_torch_lumi_level_v1
+               max77387_def_flash_levels[MAX77387_FLASH_LEVELS];
+
+static struct max77387_platform_data max77387_default_pdata = {
+       .config         = {
+                       .led_mask = 3, /* both LEDs enabled */
+                       .synchronized_led = false,
+                       .flash_trigger_mode = 3,
+                       .torch_trigger_mode = 3,
+                       .flash_mode = 2,
+                       .torch_mode = 1,
+                       .adaptive_mode = 2,
+                       .tx1_mask_mA = 0,
+                       .tx2_mask_mA = 0,
+                       .flash_rampup_uS = 0,
+                       .flash_rampdn_uS = 0,
+                       .torch_rampup_uS = 0,
+                       .torch_rampdn_uS = 0,
+                       .max_peak_current_mA = 1000,
+                       .max_torch_current_mA = 250,
+                       .max_peak_duration_ms = 0,
+                       .max_flash_threshold_mV = 0,
+                       .led_config[0] = {
+                               .flash_torch_ratio = 10000,
+                               .granularity = 1000,
+                               .flash_levels =
+                                       ARRAY_SIZE(max77387_def_flash_levels),
+                               .lumi_levels = max77387_def_flash_levels,
+                               },
+                       .led_config[1] = {
+                               .flash_torch_ratio = 10000,
+                               .granularity = 1000,
+                               .flash_levels =
+                                       ARRAY_SIZE(max77387_def_flash_levels),
+                               .lumi_levels = max77387_def_flash_levels,
+                               },
+                       },
+       .cfg            = 0,
+       .num            = 0,
+       .sync           = 0,
+       .dev_name       = "torch",
+       .pinstate       = {0x0000, 0x0000},
+};
+
+/* flash timer duration settings in uS */
+static u32 max77387_flash_timer[MAX77387_FLASH_TIMER_NUM] = {
+       128, 384, 640, 896, 1410, 1920, 2430, 2940,
+};
+
+/* torch timer duration settings in uS */
+#define MAX77387_TORCH_TIMER_FOREVER   0xFFFFFFFF
+static u32 max77387_torch_timer[MAX77387_TORCH_TIMER_NUM + 1] = {
+       122880,
+};
+
+static void max77387_throttle(unsigned int new_state, void *priv_data);
+
+static bool rd_wr_reg_chk(struct device *dev, unsigned int reg)
+{
+       if (reg > MAX77387_RO_DCDC_MAX) {
+               dev_err(dev, "%s: non-existing reg 0x%x\n", __func__, reg);
+               return false;
+       }
+       return true;
+}
+
+static const struct regmap_config max77387_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = MAX77387_RO_DCDC_MAX,
+       .writeable_reg = rd_wr_reg_chk,
+       .readable_reg = rd_wr_reg_chk,
+       .cache_type = REGCACHE_RBTREE,
+};
+
+static inline int max77387_reg_raw_rd(
+       struct max77387_info *info, u8 reg, u8 *val, u8 num)
+{
+       return regmap_raw_read(info->regmap, reg, val, num);
+}
+
+static int max77387_reg_raw_wr(
+       struct max77387_info *info, u8 reg, u8 *buf, u8 num)
+{
+       dev_dbg(info->dev, "%s %x = %x %x\n", __func__, reg, buf[0], buf[1]);
+       return regmap_raw_write(info->regmap, reg, buf, num);
+}
+
+static int max77387_reg_wr(
+       struct max77387_info *info, u8 reg, u8 val, bool refresh)
+{
+       dev_dbg(info->dev,
+               "%s: %02x - %02x, %s %s\n", __func__, reg, val,
+               info->regs.regs_stale ? "STALE" : "NONE",
+               refresh ? "REFRESH" : "NONE");
+       if (unlikely(!info->regs.regs_stale && !refresh))
+               return 0;
+       return regmap_write(info->regmap, reg, val);
+}
+
+static void max77387_edp_lowest(struct max77387_info *info)
+{
+       if (!info->edpc)
+               return;
+
+       info->edp_state = info->edpc->num_states - 1;
+       dev_dbg(info->dev, "%s %d\n", __func__, info->edp_state);
+       if (edp_update_client_request(info->edpc, info->edp_state, NULL)) {
+               dev_err(info->dev, "THIS IS NOT LIKELY HAPPEN!\n");
+               dev_err(info->dev, "UNABLE TO SET LOWEST EDP STATE!\n");
+       }
+}
+
+static void max77387_edp_register(struct max77387_info *info)
+{
+       struct edp_manager *edp_manager;
+       struct edp_client *edpc = &info->pdata->edpc_config;
+       int ret;
+
+       info->edpc = NULL;
+       if (!edpc->num_states) {
+               dev_warn(info->dev, "%s: NO edp states defined.\n", __func__);
+               return;
+       }
+
+       strncpy(edpc->name, "max77387f", EDP_NAME_LEN - 1);
+       edpc->name[EDP_NAME_LEN - 1] = 0;
+       edpc->throttle = max77387_throttle;
+       edpc->private_data = info;
+
+       dev_dbg(info->dev, "%s: %s, e0 = %d, p %d\n",
+               __func__, edpc->name, edpc->e0_index, edpc->priority);
+       for (ret = 0; ret < edpc->num_states; ret++)
+               dev_dbg(info->dev, "e%d = %d mA\n",
+                       ret - edpc->e0_index, edpc->states[ret]);
+
+       edp_manager = edp_get_manager("battery");
+       if (!edp_manager) {
+               dev_err(info->dev, "unable to get edp manager: battery\n");
+               return;
+       }
+
+       ret = edp_register_client(edp_manager, edpc);
+       if (ret) {
+               dev_err(info->dev, "unable to register edp client\n");
+               return;
+       }
+
+       info->edpc = edpc;
+       /* set to lowest state at init */
+       max77387_edp_lowest(info);
+}
+
+static int max77387_edp_req(struct max77387_info *info,
+               u8 mask, u8 *curr1, u8 *curr2)
+{
+       unsigned *estates;
+       unsigned total_curr = 0;
+       unsigned curr_mA;
+       unsigned approved;
+       unsigned new_state;
+       int ret = 0;
+
+       if (!info->edpc)
+               return 0;
+
+       dev_dbg(info->dev, "%s: %d curr1 = %02x curr2 = %02x\n",
+               __func__, mask, *curr1, *curr2);
+       estates = info->edpc->states;
+       if (mask & 1)
+               total_curr += *curr1;
+       if (mask & 2)
+               total_curr += *curr2;
+       curr_mA = GET_CURRENT_BY_INDEX(total_curr);
+
+       for (new_state = info->edpc->num_states - 1; new_state > 0; new_state--)
+               if (estates[new_state] >= curr_mA)
+                       break;
+
+       dev_dbg(info->dev, "edp req: %d curr = %d mA\n", new_state, curr_mA);
+       ret = edp_update_client_request(info->edpc, new_state, &approved);
+       if (ret) {
+               dev_err(info->dev, "E state transition failed\n");
+               return ret;
+       }
+
+       if (approved > new_state) { /* edp manager returned less current */
+               curr_mA = GET_INDEX_BY_CURRENT(estates[approved]);
+               if (mask & 1)
+                       *curr1 = curr_mA * (*curr1) / total_curr;
+               *curr2 = curr_mA - (*curr1);
+               dev_dbg(info->dev, "new state: %d curr = %d mA (%d %d)\n",
+                       approved, curr_mA, *curr1, *curr2);
+       }
+
+       info->edp_state = approved;
+
+       return 0;
+}
+
+static int max77387_set_leds(struct max77387_info *info,
+               u8 mask, u8 curr1, u8 curr2)
+{
+       struct max77387_settings *pst = &info->settings;
+       u32 f_levels1 = info->flash_cap[0]->numberoflevels - 2;
+       u32 f_levels2 = info->flash_cap[1]->numberoflevels - 2;
+       u32 t_levels1 = info->torch_cap[0]->numberoflevels - 2;
+       u32 t_levels2 = info->torch_cap[1]->numberoflevels - 2;
+       int err = 0;
+       u8 fled_en = 0;
+       u8 regs[11];
+
+       memset(regs, 0, sizeof(regs));
+
+       if (info->op_mode == MAXFLASH_MODE_NONE) {
+               err = max77387_reg_raw_wr(
+                       info, MAX77387_RW_FLASH_FLED1CURR, regs, 5);
+               if (!err) {
+                       info->regs.led1_fcurr = 0;
+                       info->regs.led2_fcurr = 0;
+                       info->regs.led1_tcurr = 0;
+                       info->regs.led2_tcurr = 0;
+                       info->regs.leds_en = 0;
+                       info->regs.regs_stale = false;
+                       max77387_edp_lowest(info);
+               }
+               goto set_leds_end;
+       }
+
+       err = max77387_edp_req(info, mask, &curr1, &curr2);
+       if (err)
+               goto set_leds_end;
+
+       if (mask & 1) {
+               if (info->op_mode == MAXFLASH_MODE_FLASH) {
+                       if (curr1 > f_levels1)
+                               curr1 = f_levels1;
+                       fled_en |= (pst->fled_trig & FLASH_TRIG_MASK);
+                       regs[0] = FLASH_CURRENT(curr1);
+               } else {
+                       if (curr1 > t_levels1)
+                               curr1 = t_levels1;
+                       fled_en |= (pst->fled_trig & TORCH_TRIG_MASK);
+                       regs[2] = TORCH_CURRENT(curr1);
+               }
+       }
+
+       if (mask & 2) {
+               if (info->op_mode == MAXFLASH_MODE_FLASH) {
+                       if (curr2 > f_levels2)
+                               curr2 = f_levels2;
+                       fled_en |= (pst->fled_trig & FLASH_TRIG_MASK);
+                       regs[1] = FLASH_CURRENT(curr2);
+               } else {
+                       if (curr2 > t_levels2)
+                               curr2 = t_levels2;
+                       fled_en |= (pst->fled_trig & TORCH_TRIG_MASK);
+                       regs[3] = TORCH_CURRENT(curr2);
+               }
+       }
+
+       /* if any led is set as flash, update the flash timer register */
+       if (info->op_mode == MAXFLASH_MODE_FLASH)
+               regs[9] = (info->ftimer_mode & FIELD(TIMER_MAX, 7)) |
+                       FIELD((info->new_timer & MAX77387_FTIMER_MASK), 0);
+       else
+               regs[10] = (info->ttimer_mode & FIELD(TTIMER_DIS, 7)) |
+                       FIELD((info->new_timer & MAX77387_TTIMER_MASK), 2);
+
+       regs[4] = fled_en;
+       regs[5] = pst->tx1_mask;
+       regs[6] = pst->tx2_mask;
+       regs[7] = pst->flash_ramp;
+       regs[8] = pst->torch_ramp;
+       if ((info->regs.led1_fcurr != regs[0]) ||
+               (info->regs.led2_fcurr != regs[1]) ||
+               (info->regs.led1_tcurr != regs[2]) ||
+               (info->regs.led2_tcurr != regs[3]) ||
+               (info->regs.t_timer != regs[10]) ||
+               (info->regs.f_timer != regs[9]) ||
+               (info->regs.leds_en != regs[4]))
+               info->regs.regs_stale = true;
+
+       if (info->regs.regs_stale) {
+               err = max77387_reg_raw_wr(
+                       info, MAX77387_RW_FLASH_FLED1CURR, regs, sizeof(regs));
+
+               if (!err) {
+                       info->regs.led1_fcurr = regs[0];
+                       info->regs.led2_fcurr = regs[1];
+                       info->regs.led1_tcurr = regs[2];
+                       info->regs.led2_tcurr = regs[3];
+                       info->regs.t_timer = regs[10];
+                       info->regs.f_timer = regs[9];
+                       info->regs.leds_en = regs[4];
+                       info->regs.regs_stale = false;
+               }
+       }
+
+set_leds_end:
+       if (err)
+               dev_err(info->dev, "%s ERROR: %d\n", __func__, err);
+       else
+               dev_dbg(info->dev,
+                       "%s led %x f: %02x %02x %02x, t: %02x %02x %02x, %x\n",
+                       __func__, mask, curr1, curr2, info->regs.f_timer,
+                       info->regs.led1_tcurr, info->regs.led2_tcurr,
+                       info->regs.t_timer, fled_en);
+       return err;
+}
+
+static void max77387_update_config(struct max77387_info *info)
+{
+       struct max77387_settings *pst = &info->settings;
+       struct max77387_config *pcfg = &info->config;
+       struct max77387_config *pcfg_cust;
+       int i;
+       int delta;
+
+       dev_dbg(info->dev, "%s +++\n", __func__);
+       dev_dbg(info->dev, "max77387_def_flash_levels:\n");
+       for (i = 0; i < ARRAY_SIZE(max77387_def_flash_levels); i++) {
+               max77387_def_flash_levels[i].guidenum = i;
+               max77387_def_flash_levels[i].luminance =
+                       (i + 1) * max77387_caps.curr_step_uA;
+               dev_dbg(info->dev, "0x%02x - %d\n",
+                       i, max77387_def_flash_levels[i].luminance);
+       }
+
+       dev_dbg(info->dev, "max77387_flash_timer:\n");
+       delta = 1024;
+       for (i = 8; i < ARRAY_SIZE(max77387_flash_timer); i++) {
+               max77387_flash_timer[i] = max77387_flash_timer[i - 1] + delta;
+               if (i >= 0x3f)
+                       delta = 8192;
+               else if (i >= 0x1f)
+                       delta = 4096;
+               else if (i >= 0x0f)
+                       delta = 2048;
+               dev_dbg(info->dev,
+                       "0x%02x - %06d\n", i, max77387_flash_timer[i]);
+       }
+
+       dev_dbg(info->dev, "max77387_torch_timer:\n");
+       delta = 131072;
+       for (i = 1; i < ARRAY_SIZE(max77387_torch_timer) - 1; i++) {
+               max77387_torch_timer[i] = max77387_torch_timer[i - 1] + delta;
+               if (i >= 0x0f)
+                       delta = 1048576;
+               else if (i >= 0x7)
+                       delta = 524288;
+               else if (i >= 3)
+                       delta = 262144;
+               dev_dbg(info->dev,
+                       "0x%02x - %08d\n", i, max77387_torch_timer[i]);
+       }
+       max77387_torch_timer[ARRAY_SIZE(max77387_torch_timer) - 1] =
+                                               MAX77387_TORCH_TIMER_FOREVER;
+
+       memcpy(pcfg, &max77387_default_pdata.config, sizeof(*pcfg));
+       if (!info->pdata) {
+               info->pdata = &max77387_default_pdata;
+               dev_dbg(info->dev, "%s No platform data.  Using defaults.\n",
+                       __func__);
+               goto update_end;
+       }
+       pcfg_cust = &info->pdata->config;
+
+       if (pcfg_cust->flash_trigger_mode)
+               pcfg->flash_trigger_mode = pcfg_cust->flash_trigger_mode;
+
+       if (pcfg_cust->torch_trigger_mode)
+               pcfg->torch_trigger_mode = pcfg_cust->torch_trigger_mode;
+
+       if (pcfg_cust->led_mask)
+               pcfg->led_mask = pcfg_cust->led_mask;
+
+       if (pcfg_cust->synchronized_led)
+               pcfg->synchronized_led = pcfg_cust->synchronized_led;
+
+       if (pcfg_cust->flash_mode)
+               pcfg->flash_mode = pcfg_cust->flash_mode;
+
+       if (pcfg_cust->torch_mode)
+               pcfg->torch_mode = pcfg_cust->torch_mode;
+
+       if (pcfg_cust->adaptive_mode)
+               pcfg->adaptive_mode = pcfg_cust->adaptive_mode;
+
+       if (pcfg_cust->max_total_current_mA)
+               pcfg->max_total_current_mA = pcfg_cust->max_total_current_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_flash_threshold_mV)
+               pcfg->max_flash_threshold_mV =
+                               pcfg_cust->max_flash_threshold_mV;
+
+       if (pcfg_cust->max_flash_hysteresis_mV)
+               pcfg->max_flash_hysteresis_mV =
+                               pcfg_cust->max_flash_hysteresis_mV;
+
+       if (pcfg_cust->max_flash_lbdly_f_uS)
+               pcfg->max_flash_lbdly_f_uS =
+                               pcfg_cust->max_flash_lbdly_f_uS;
+
+       if (pcfg_cust->max_flash_lbdly_r_uS)
+               pcfg->max_flash_lbdly_r_uS =
+                               pcfg_cust->max_flash_lbdly_r_uS;
+
+       if (pcfg_cust->tx1_mask_mA)
+               pcfg->tx1_mask_mA = pcfg_cust->tx1_mask_mA;
+
+       if (pcfg_cust->tx2_mask_mA)
+               pcfg->tx2_mask_mA = pcfg_cust->tx2_mask_mA;
+
+       if (pcfg_cust->flash_rampup_uS)
+               pcfg->flash_rampup_uS = pcfg_cust->flash_rampup_uS;
+
+       if (pcfg_cust->flash_rampdn_uS)
+               pcfg->flash_rampdn_uS = pcfg_cust->flash_rampdn_uS;
+
+       if (pcfg_cust->torch_rampup_uS)
+               pcfg->torch_rampup_uS = pcfg_cust->torch_rampup_uS;
+
+       if (pcfg_cust->torch_rampdn_uS)
+               pcfg->torch_rampdn_uS = pcfg_cust->torch_rampdn_uS;
+
+       if (pcfg_cust->def_ftimer)
+               pcfg->torch_rampdn_uS = pcfg_cust->def_ftimer;
+
+       for (i = 0; i < MAX77387_LED_NUM; i++) {
+               if (pcfg_cust->led_config[i].flash_levels &&
+                       pcfg_cust->led_config[i].flash_torch_ratio &&
+                       pcfg_cust->led_config[i].granularity &&
+                       pcfg_cust->led_config[i].lumi_levels)
+                       memcpy(&pcfg->led_config[i], &pcfg_cust->led_config[i],
+                               sizeof(pcfg_cust->led_config[0]));
+               else
+                       dev_warn(info->dev,
+                               "%s: invalid led config[%d].\n", __func__, i);
+       }
+
+update_end:
+       /* FLED enable settings */
+       /* How TORCH is triggered */
+       switch (pcfg->torch_trigger_mode) {
+       case 1: /* triggered by FLASHEN */
+               pst->fled_trig = TORCH_TRIG_BY_FLASHEN;
+               break;
+       case 2: /* triggered by TORCHEN */
+               pst->fled_trig = TORCH_TRIG_BY_TORCHEN;
+               break;
+       case 3: /* triggered by serial interface */
+               pst->fled_trig = TORCH_TRIG_BY_I2C;
+               break;
+       case 4: /* triggered by FLASHEN and TORCHEN */
+               pst->fled_trig = TORCH_TRIG_BY_FANDT;
+               break;
+       case 5: /* triggered by FLASHEN or TORCHEN */
+               pst->fled_trig = TORCH_TRIG_BY_FORT;
+               break;
+       default:
+               dev_err(info->dev, "%s: unrecognized torch trigger mode.\n",
+                       __func__);
+               dev_err(info->dev, "use default i2c mode.\n");
+               pst->fled_trig = TORCH_TRIG_BY_I2C;
+               break;
+       }
+
+       /* How FLASH is triggered */
+       switch (pcfg->flash_trigger_mode) {
+       case 1: /* triggered by FLASHEN */
+               pst->fled_trig |= FLASH_TRIG_BY_FLASHEN;
+               break;
+       case 2: /* triggered by TORCHEN */
+               pst->fled_trig |= FLASH_TRIG_BY_TORCHEN;
+               break;
+       case 3: /* triggered by serial interface */
+               pst->fled_trig |= FLASH_TRIG_BY_I2C;
+               break;
+       case 4: /* triggered by FLASHEN and TORCHEN */
+               pst->fled_trig |= FLASH_TRIG_BY_FANDT;
+               break;
+       case 5: /* triggered by FLASHEN or TORCHEN */
+               pst->fled_trig |= FLASH_TRIG_BY_FORT;
+               break;
+       default:
+               dev_err(info->dev, "%s: unrecognized flash trigger mode.\n",
+                       __func__);
+               dev_err(info->dev, "use default i2c mode.\n");
+               pst->fled_trig |= FLASH_TRIG_BY_I2C;
+               break;
+       }
+
+
+       info->ftimer_mode = (pcfg->flash_mode == 1) ?
+                       FIELD(TIMER_ONESHOT, 7) : FIELD(TIMER_MAX, 7);
+
+       switch (pcfg->torch_mode) {
+       case 1:
+               info->ttimer_mode = FIELD(TTIMER_DIS, 7) |
+                                       FIELD(TORCH_TIMER_SAFETY_DIS, 6);
+               break;
+       case 2:
+               info->ttimer_mode = FIELD(TIMER_ONESHOT, 7);
+               break;
+       case 3:
+       default:
+               info->ttimer_mode = FIELD(TTIMER_DIS, 7);
+               break;
+       }
+
+       if (pcfg->max_flash_threshold_mV) {
+               if (pcfg->max_flash_threshold_mV < MAXFLASH_V_TH_FLOOR)
+                       pcfg->max_flash_threshold_mV = MAXFLASH_V_TH_FLOOR;
+               else if (pcfg->max_flash_threshold_mV > MAXFLASH_V_TH_CEILING)
+                       pcfg->max_flash_threshold_mV = MAXFLASH_V_TH_CEILING;
+
+               /* 0 - hysteresis disabled */
+               if (pcfg->max_flash_hysteresis_mV) {
+                       if (pcfg->max_flash_hysteresis_mV <
+                               MAXFLASH_VOLT_HYS_FLOOR)
+                               pcfg->max_flash_hysteresis_mV =
+                                       MAXFLASH_VOLT_HYS_FLOOR;
+                       else if (pcfg->max_flash_hysteresis_mV >
+                                MAXFLASH_VOLT_HYS_CEILING)
+                               pcfg->max_flash_hysteresis_mV =
+                                       MAXFLASH_VOLT_HYS_CEILING;
+               }
+
+               pst->max_flash1 = FIELD(MAXFLASH_ENABLE, 7) |
+               ((pcfg->max_flash_threshold_mV - MAXFLASH_V_TH_FLOOR) /
+                               MAXFLASH_V_TH_STEP);
+               pst->max_flash1 |= (pcfg->max_flash_hysteresis_mV +
+               MAXFLASH_VOLT_HYS_STEP / 2) / MAXFLASH_VOLT_HYS_STEP;
+       }
+
+       if (pcfg->max_flash_lbdly_f_uS)
+               pst->max_flash1 =
+               FIELD(pcfg->max_flash_lbdly_f_uS / MAXFLASH_TIMER_STEP, 0);
+
+       if (pcfg->max_flash_lbdly_r_uS)
+               pst->max_flash1 |=
+               FIELD(pcfg->max_flash_lbdly_r_uS / MAXFLASH_TIMER_STEP, 3);
+}
+
+static int max77387_update_settings(struct max77387_info *info)
+{
+       struct max77387_settings *pst = &info->settings;
+       int err = 0;
+       u8 regs[8];
+
+       info->regs.regs_stale = true;
+       regs[0] = pst->tx1_mask;
+       regs[1] = pst->tx2_mask;
+       regs[2] = pst->flash_ramp;
+       regs[3] = pst->torch_ramp;
+       regs[4] = info->regs.f_timer;
+       regs[5] = info->regs.t_timer;
+       regs[6] = pst->max_flash1;
+       regs[7] = pst->max_flash2;
+       err = max77387_reg_raw_wr(
+               info, MAX77387_RW_TX1_MASK, regs, sizeof(regs));
+
+       regs[0] = pst->ntc;
+       regs[1] = pst->dcdc_cntl1;
+       regs[2] = pst->dcdc_cntl2;
+       regs[3] = pst->dcdc_lim;
+       err = max77387_reg_raw_wr(info, MAX77387_RW_NTC, regs, 5);
+
+       if (info->op_mode == MAXFLASH_MODE_FLASH)
+               err |= max77387_set_leds(info, info->config.led_mask,
+                               info->regs.led1_fcurr, info->regs.led2_fcurr);
+       else
+               err |= max77387_set_leds(info, info->config.led_mask,
+                               info->regs.led1_tcurr, info->regs.led2_tcurr);
+
+       info->regs.regs_stale = false;
+       return err;
+}
+
+static int max77387_configure(struct max77387_info *info, bool update)
+{
+       struct max77387_settings *pst = &info->settings;
+       struct max77387_config *pcfg = &info->config;
+       struct nvc_torch_capability_query *pqry = &info->query;
+       struct nvc_torch_flash_capabilities_v1  *pfcap = NULL;
+       struct nvc_torch_torch_capabilities_v1  *ptcap = NULL;
+       struct nvc_torch_timer_capabilities_v1  *ptmcap = NULL;
+       struct nvc_torch_lumi_level_v1          *plvls = NULL;
+       int val, i, j;
+
+       if (pcfg->max_peak_current_mA > max77387_caps.max_peak_curr_mA ||
+               !pcfg->max_peak_current_mA) {
+               dev_warn(info->dev, "invalid max_peak_current_mA: %d,",
+                               pcfg->max_peak_current_mA);
+               dev_info(info->dev, " changed to %d\n",
+                               max77387_caps.max_peak_curr_mA);
+               pcfg->max_peak_current_mA = max77387_caps.max_peak_curr_mA;
+       }
+
+       pst->tx1_mask = 0;
+       if (pcfg->tx1_mask_mA) {
+               if (pcfg->tx1_mask_mA > 1000) {
+                       dev_warn(info->dev, "%s: tx1_mask OUT OF RANGE. %d\n",
+                               __func__, pcfg->tx1_mask_mA);
+                       pcfg->tx1_mask_mA = 1000;
+               }
+               pst->tx1_mask = (u8)((u32)pcfg->tx1_mask_mA * 1000 / 15625)
+                                       | MAX77387_TX_MASK_ENABLE;
+       }
+
+       pst->tx1_mask = 0;
+       if (pcfg->tx2_mask_mA) {
+               if (pcfg->tx2_mask_mA > 1000) {
+                       dev_warn(info->dev, "%s: tx2_mask OUT OF RANGE. %d\n",
+                               __func__, pcfg->tx2_mask_mA);
+                       pcfg->tx2_mask_mA = 1000;
+               }
+               pst->tx2_mask = (u8)((u32)pcfg->tx2_mask_mA * 1000 / 15625)
+                                       | MAX77387_TX_MASK_ENABLE;
+       }
+
+       if (pcfg->flash_rampup_uS > 32896) {
+               dev_warn(info->dev, "%s: flash ramp up OUT OF RANGE. %d\n",
+                               __func__, pcfg->flash_rampup_uS);
+               pcfg->flash_rampup_uS = 32896;
+       }
+       if (pcfg->flash_rampdn_uS > 32896) {
+               dev_warn(info->dev, "%s: flash ramp up OUT OF RANGE. %d\n",
+                               __func__, pcfg->flash_rampdn_uS);
+               pcfg->flash_rampdn_uS = 32896;
+       }
+       pst->flash_ramp = ((pcfg->flash_rampup_uS / 384) << 4) |
+                               pcfg->flash_rampdn_uS / 384;
+
+       if (pcfg->torch_rampup_uS > 32896) {
+               dev_warn(info->dev, "%s: torch ramp up OUT OF RANGE. %d\n",
+                               __func__, pcfg->torch_rampup_uS);
+               pcfg->torch_rampup_uS = 32896;
+       }
+       if (pcfg->torch_rampdn_uS > 32896) {
+               dev_warn(info->dev, "%s: torch ramp up OUT OF RANGE. %d\n",
+                               __func__, pcfg->torch_rampdn_uS);
+               pcfg->torch_rampdn_uS = 32896;
+       }
+       pst->torch_ramp = ((pcfg->torch_rampup_uS / 384) << 4) |
+                               pcfg->torch_rampdn_uS / 384;
+
+       /* number of leds enabled */
+       i = 1;
+       /* in synchronize mode, both leds are considered as 1 */
+       if (!pcfg->synchronized_led && (info->config.led_mask & 3) == 3)
+               i = 2;
+       pqry->flash_num = i;
+       pqry->torch_num = i;
+
+       val = pcfg->max_peak_current_mA * i;
+       if (val > max77387_caps.max_total_current_mA)
+               val = max77387_caps.max_total_current_mA;
+
+       if (!pcfg->max_total_current_mA || pcfg->max_total_current_mA > val)
+               pcfg->max_total_current_mA = val;
+       pcfg->max_peak_current_mA =
+               pcfg->max_total_current_mA / i;
+
+       if (pcfg->max_torch_current_mA > max77387_caps.max_torch_curr_mA ||
+               !pcfg->max_torch_current_mA) {
+               dev_warn(info->dev, "invalid max_torch_current_mA: %d,",
+                               pcfg->max_torch_current_mA);
+               dev_info(info->dev, " changed to %d\n",
+                               max77387_caps.max_torch_curr_mA);
+               pcfg->max_torch_current_mA =
+                       max77387_caps.max_torch_curr_mA;
+       }
+
+       pqry->version = NVC_TORCH_CAPABILITY_VER_1;
+       pqry->led_attr = 0;
+       for (i = 0; i < pqry->flash_num; i++) {
+               pfcap = info->flash_cap[i];
+               pfcap->version = NVC_TORCH_CAPABILITY_VER_1;
+               pfcap->led_idx = i;
+               pfcap->attribute = 0;
+               pfcap->numberoflevels = pcfg->led_config[i].flash_levels + 1;
+               pfcap->granularity = pcfg->led_config[i].granularity;
+               pfcap->timeout_num = ARRAY_SIZE(max77387_flash_timer);
+               ptmcap = info->flash_timeouts[i];
+               pfcap->timeout_off = (void *)ptmcap - (void *)pfcap;
+               pfcap->flash_torch_ratio =
+                               pcfg->led_config[i].flash_torch_ratio;
+               dev_dbg(info->dev,
+                       "%s flash#%d, attr: %x, levels: %d, g: %d, ratio: %d\n",
+                       __func__, pfcap->led_idx, pfcap->attribute,
+                       pfcap->numberoflevels, pfcap->granularity,
+                       pfcap->flash_torch_ratio);
+
+               plvls = pcfg->led_config[i].lumi_levels;
+               pfcap->levels[0].guidenum = MAX77387_LEVEL_OFF;
+               pfcap->levels[0].luminance = 0;
+               for (j = 1; j < pfcap->numberoflevels; j++) {
+                       pfcap->levels[j].guidenum = plvls[j - 1].guidenum;
+                       pfcap->levels[j].luminance = plvls[j - 1].luminance;
+                       dev_dbg(info->dev, "%02d - %d\n",
+                               pfcap->levels[j].guidenum,
+                               pfcap->levels[j].luminance);
+               }
+
+               ptmcap->timeout_num = pfcap->timeout_num;
+               for (j = 0; j < ptmcap->timeout_num; j++) {
+                       ptmcap->timeouts[j].timeout = max77387_flash_timer[j];
+                       dev_dbg(info->dev, "t: %02d - %d uS\n", j,
+                               ptmcap->timeouts[j].timeout);
+               }
+       }
+
+       for (i = 0; i < pqry->torch_num; i++) {
+               ptcap = info->torch_cap[i];
+               ptcap->version = NVC_TORCH_CAPABILITY_VER_1;
+               ptcap->led_idx = i;
+               ptcap->attribute = 0;
+               ptcap->numberoflevels = pcfg->led_config[i].flash_levels + 1;
+               ptcap->granularity = pcfg->led_config[i].granularity;
+               ptcap->timeout_num = ARRAY_SIZE(max77387_torch_timer);
+               ptmcap = info->torch_timeouts[i];
+               ptcap->timeout_off = (void *)ptmcap - (void *)ptcap;
+               dev_dbg(info->dev, "torch#%d, attr: %x, levels: %d, g: %d\n",
+                       ptcap->led_idx, ptcap->attribute,
+                       ptcap->numberoflevels, ptcap->granularity);
+
+               plvls = pcfg->led_config[i].lumi_levels;
+               ptcap->levels[0].guidenum = MAX77387_LEVEL_OFF;
+               ptcap->levels[0].luminance = 0;
+               for (j = 1; j < ptcap->numberoflevels; j++) {
+                       ptcap->levels[j].guidenum = plvls[j - 1].guidenum;
+                       ptcap->levels[j].luminance = plvls[j - 1].luminance;
+                       dev_dbg(info->dev, "%02d - %d\n",
+                               ptcap->levels[j].guidenum,
+                               ptcap->levels[j].luminance);
+               }
+
+               ptmcap->timeout_num = ptcap->timeout_num;
+               for (j = 0; j < ptmcap->timeout_num; j++) {
+                       ptmcap->timeouts[j].timeout = max77387_torch_timer[j];
+                       dev_dbg(info->dev, "t: %02d - %d uS\n", j,
+                               ptmcap->timeouts[j].timeout);
+               }
+       }
+
+       if (update && (info->pwr_state == NVC_PWR_COMM ||
+                       info->pwr_state == NVC_PWR_ON))
+               return max77387_update_settings(info);
+
+       return 0;
+}
+
+static int max77387_strobe(struct max77387_info *info, int t_on)
+{
+       u32 gpio = info->pdata->gpio_strobe & 0xffff;
+       u32 lact = (info->pdata->gpio_strobe & 0xffff0000) ? 1 : 0;
+       return gpio_direction_output(gpio, lact ^ (t_on & 1));
+}
+
+static int max77387_enter_offmode(struct max77387_info *info, bool op_off)
+{
+       int err;
+
+       mutex_lock(&info->mutex);
+       if (op_off) {
+               info->op_mode = MAXFLASH_MODE_NONE;
+               err = max77387_set_leds(info, 3, 0, 0);
+       } else {
+               err = max77387_reg_wr(
+                       info, MAX77387_RW_FLED_MODE, 0, true);
+       }
+       mutex_unlock(&info->mutex);
+       return err;
+}
+
+static void max77387_throttle(unsigned int new_state, void *priv_data)
+{
+       struct max77387_info *info = priv_data;
+
+       if (!info)
+               return;
+
+       max77387_enter_offmode(info, true);
+}
+
+#ifdef CONFIG_PM
+static int max77387_suspend(struct i2c_client *client, pm_message_t msg)
+{
+       struct max77387_info *info = i2c_get_clientdata(client);
+
+       dev_info(info->dev, "Suspending\n");
+       info->regs.regs_stale = true;
+
+       return 0;
+}
+
+static int max77387_resume(struct i2c_client *client)
+{
+       struct max77387_info *info = i2c_get_clientdata(client);
+
+       dev_info(info->dev, "Resuming\n");
+       info->regs.regs_stale = true;
+
+       return 0;
+}
+
+static void max77387_shutdown(struct i2c_client *client)
+{
+       struct max77387_info *info = i2c_get_clientdata(client);
+
+       dev_info(info->dev, "Shutting down\n");
+
+       max77387_enter_offmode(info, true);
+       info->regs.regs_stale = true;
+}
+#endif
+
+static int max77387_power_off(struct max77387_info *info)
+{
+       struct max77387_power_rail *pw = &info->pwr_rail;
+       int err = 0;
+
+       if (!info->power_is_on)
+               return 0;
+
+       if (info->pdata && info->pdata->poweroff_callback)
+               err = info->pdata->poweroff_callback(pw);
+
+       if (IS_ERR_VALUE(err))
+               return err;
+
+       /* the call back function already handles the power off sequence */
+       if (err) {
+               err = 0;
+               goto max77387_poweroff_done;
+       }
+
+       if (pw->vin)
+               regulator_disable(pw->vin);
+
+       if (pw->vdd)
+               regulator_disable(pw->vdd);
+
+max77387_poweroff_done:
+       max77387_edp_lowest(info);
+       info->power_is_on = 0;
+       return err;
+}
+
+static int max77387_power_on(struct max77387_info *info)
+{
+       struct max77387_power_rail *pw = &info->pwr_rail;
+       int err = 0;
+
+       if (info->power_is_on)
+               return 0;
+
+       if (info->pdata && info->pdata->poweron_callback)
+               err = info->pdata->poweron_callback(pw);
+
+       if (IS_ERR_VALUE(err))
+               return err;
+
+       /* the call back function already handles the power on sequence */
+       if (err) {
+               err = 0;
+               goto max77387_poweron_sync;
+       }
+
+       if (pw->vdd) {
+               err = regulator_enable(pw->vdd);
+               if (err) {
+                       dev_err(info->dev, "%s vdd err\n", __func__);
+                       goto max77387_poweron_vdd_fail;
+               }
+       }
+
+       if (pw->vin) {
+               err = regulator_enable(pw->vin);
+               if (err) {
+                       dev_err(info->dev, "%s vin err\n", __func__);
+                       goto max77387_poweron_vin_fail;
+               }
+       }
+
+max77387_poweron_sync:
+       info->power_is_on = 1;
+       err = max77387_update_settings(info);
+       if (err) {
+               max77387_power_off(info);
+               return err;
+       }
+
+       max77387_edp_lowest(info);
+
+       return 0;
+
+max77387_poweron_vin_fail:
+       if (pw->vdd)
+               regulator_disable(pw->vdd);
+max77387_poweron_vdd_fail:
+       if (info->pdata && info->pdata->poweroff_callback)
+               info->pdata->poweroff_callback(pw);
+       return err;
+}
+
+static int max77387_power_set(struct max77387_info *info, int pwr)
+{
+       int err = 0;
+
+       if (pwr == info->pwr_state)
+               return 0;
+
+       switch (pwr) {
+       case NVC_PWR_OFF:
+               max77387_enter_offmode(info, true);
+               if ((info->pdata->cfg & NVC_CFG_OFF2STDBY) ||
+                       (info->pdata->cfg & NVC_CFG_BOOT_INIT))
+                       pwr = NVC_PWR_STDBY;
+               else
+                       err = max77387_power_off(info);
+               break;
+       case NVC_PWR_STDBY_OFF:
+               if ((info->pdata->cfg & NVC_CFG_OFF2STDBY) ||
+                       (info->pdata->cfg & NVC_CFG_BOOT_INIT))
+                       pwr = NVC_PWR_STDBY;
+               else
+                       err = max77387_power_on(info);
+               break;
+       case NVC_PWR_STDBY:
+               err = max77387_power_on(info);
+               err |= max77387_enter_offmode(info, false);
+               break;
+
+       case NVC_PWR_COMM:
+       case NVC_PWR_ON:
+               err = max77387_power_on(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 inline int max77387_power_user_set(
+       struct max77387_info *info, int pwr)
+{
+       int err = 0;
+
+       if (!pwr || (pwr > NVC_PWR_ON))
+               return 0;
+
+       err = max77387_power_set(info, pwr);
+       if (info->pdata->cfg & NVC_CFG_NOERR)
+               return 0;
+
+       return err;
+}
+
+static int max77387_dev_id(struct max77387_info *info)
+{
+       int err = 0;
+
+       if (info->chip_id)
+               return 0;
+       err = max77387_power_on(info);
+       if (err)
+               return err;
+
+       err = max77387_reg_raw_rd(info, MAX77387_RO_CHIPID1,
+                       (u8 *)&info->chip_id, sizeof(info->chip_id));
+       if (!err)
+               dev_info(info->dev, "%s: 0x%x detected.\n",
+                       __func__, info->chip_id);
+       else
+               dev_dbg(info->dev, "%s: not detected.\n", __func__);
+
+       err = max77387_power_off(info);
+       return err;
+}
+
+static int max77387_get_param(struct max77387_info *info, long arg)
+{
+       struct nvc_param params;
+       struct nvc_torch_pin_state pinstate;
+       const void *data_ptr = NULL;
+       u32 data_size = 0;
+       int err = 0;
+       u8 reg;
+
+       if (copy_from_user(&params, (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 %d\n",
+                       __func__, params.variant);
+               if (params.variant >= info->query.flash_num) {
+                       dev_err(info->dev, "%s unsupported flash index.\n",
+                               __func__);
+                       err = -EINVAL;
+                       break;
+               }
+               data_ptr = info->flash_cap[params.variant];
+               data_size = info->flash_cap_size;
+               break;
+       case NVC_PARAM_TORCH_EXT_CAPS:
+               dev_dbg(info->dev, "%s EXT_TORCH_CAPS %d\n",
+                       __func__, params.variant);
+               if (params.variant >= info->query.torch_num) {
+                       dev_err(info->dev, "%s unsupported torch index.\n",
+                               __func__);
+                       err = -EINVAL;
+                       break;
+               }
+               data_ptr = info->torch_cap[params.variant];
+               data_size = info->torch_cap_size;
+               break;
+       case NVC_PARAM_FLASH_LEVEL:
+               if (params.variant >= info->query.flash_num) {
+                       dev_err(info->dev,
+                               "%s unsupported flash index.\n", __func__);
+                       err = -EINVAL;
+                       break;
+               }
+               if (params.variant > 0)
+                       reg = info->regs.led2_fcurr;
+               else
+                       reg = info->regs.led1_fcurr;
+               data_ptr = &reg;
+               data_size = sizeof(reg);
+               dev_dbg(info->dev, "%s FLASH_LEVEL %d\n", __func__, reg);
+               break;
+       case NVC_PARAM_TORCH_LEVEL:
+               reg = info->regs.led1_tcurr;
+               if (params.variant >= info->query.torch_num) {
+                       dev_err(info->dev, "%s unsupported torch index.\n",
+                               __func__);
+                       err = -EINVAL;
+                       break;
+               }
+               if (params.variant > 0)
+                       reg >>= 4;
+               else
+                       reg &= 0x0F;
+               data_ptr = &reg;
+               data_size = sizeof(reg);
+               dev_dbg(info->dev, "%s TORCH_LEVEL %d\n", __func__, reg);
+               break;
+       case NVC_PARAM_FLASH_PIN_STATE:
+               pinstate = info->pdata->pinstate;
+               if (info->op_mode != MAXFLASH_MODE_FLASH)
+                       pinstate.values ^= 0xffff;
+               pinstate.values &= pinstate.mask;
+
+               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;
+       }
+
+       dev_dbg(info->dev, "%s data size user %d vs local %d\n",
+                       __func__, params.sizeofvalue, data_size);
+       if (!err && params.sizeofvalue < data_size) {
+               dev_err(info->dev, "%s data size mismatch\n", __func__);
+               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 max77387_get_levels(struct max77387_info *info,
+                              struct nvc_param *params,
+                              bool flash_mode,
+                              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 (flash_mode) {
+               dev_dbg(info->dev, "%s FLASH_LEVEL: %d %d %d\n",
+                       __func__, plevels->ledmask,
+                       plevels->levels[0], plevels->levels[1]);
+               p_tm = info->flash_timeouts[0];
+               op_mode = MAXFLASH_MODE_FLASH;
+       } else {
+               dev_dbg(info->dev, "%s TORCH_LEVEL: %d %d %d\n",
+                       __func__, plevels->ledmask,
+                       plevels->levels[0], plevels->levels[1]);
+               p_tm = info->torch_timeouts[0];
+               op_mode = MAXFLASH_MODE_TORCH;
+       }
+
+       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] == MAX77387_LEVEL_OFF)
+               plevels->ledmask &= ~1;
+       if (plevels->levels[1] == MAX77387_LEVEL_OFF)
+               plevels->ledmask &= ~2;
+       plevels->ledmask &= info->config.led_mask;
+
+       if (!plevels->ledmask)
+               info->op_mode = MAXFLASH_MODE_NONE;
+       else {
+               info->op_mode = op_mode;
+               if (info->config.synchronized_led) {
+                       plevels->ledmask = 3;
+                       plevels->levels[1] = plevels->levels[0];
+               }
+       }
+
+       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 max77387_set_param(struct max77387_info *info, long arg)
+{
+       struct nvc_param params;
+       struct nvc_torch_set_level_v1 led_levels;
+       u8 curr1;
+       u8 curr2;
+       u8 val;
+       int err = 0;
+
+       if (copy_from_user(
+               &params, (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:
+               max77387_get_levels(info, &params, true, &led_levels);
+               if (led_levels.timeout == 0)
+                       info->new_timer = info->config.def_ftimer;
+               else
+                       info->new_timer = led_levels.timeout;
+               curr1 = led_levels.levels[0];
+               curr2 = led_levels.levels[1];
+               err = max77387_set_leds(info,
+                       led_levels.ledmask, curr1, curr2);
+               break;
+       case NVC_PARAM_TORCH_LEVEL:
+               max77387_get_levels(info, &params, false, &led_levels);
+               info->new_timer = led_levels.timeout;
+               curr1 = led_levels.levels[0];
+               curr2 = led_levels.levels[1];
+               err = max77387_set_leds(info,
+                       led_levels.ledmask, curr1, curr2);
+               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__);
+                       err = -EINVAL;
+                       break;
+               }
+               dev_dbg(info->dev, "%s FLASH_PIN_STATE: %d\n",
+                               __func__, val);
+               err = max77387_strobe(info, val);
+               break;
+       default:
+               dev_err(info->dev, "%s unsupported parameter: %d\n",
+                               __func__, params.param);
+               err = -EINVAL;
+               break;
+       }
+
+       return err;
+}
+
+static long max77387_ioctl(
+       struct file *file, unsigned int cmd, unsigned long arg)
+{
+       struct max77387_info *info = file->private_data;
+       int pwr;
+       int err = 0;
+
+       switch (cmd) {
+       case NVC_IOCTL_PARAM_WR:
+               err = max77387_set_param(info, arg);
+               break;
+       case NVC_IOCTL_PARAM_RD:
+               err = max77387_get_param(info, arg);
+               break;
+       case 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);
+               err = max77387_power_user_set(info, pwr);
+               break;
+       case 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 max77387_open(struct inode *inode, struct file *file)
+{
+       struct miscdevice       *miscdev = file->private_data;
+       struct max77387_info *info;
+
+       info = container_of(miscdev, struct max77387_info, miscdev);
+       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 max77387_release(struct inode *inode, struct file *file)
+{
+       struct max77387_info *info = file->private_data;
+
+       dev_dbg(info->dev, "%s\n", __func__);
+       max77387_power_set(info, NVC_PWR_OFF);
+       file->private_data = NULL;
+       WARN_ON(!atomic_xchg(&info->in_use, 0));
+       return 0;
+}
+
+static int max77387_power_put(struct max77387_power_rail *pw)
+{
+       if (likely(pw->vin))
+               regulator_put(pw->vin);
+
+       if (likely(pw->vdd))
+               regulator_put(pw->vdd);
+
+
+       pw->vin = NULL;
+       pw->vdd = NULL;
+
+       return 0;
+}
+
+static int max77387_regulator_get(struct max77387_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_OR_NULL(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 max77387_power_get(struct max77387_info *info)
+{
+       struct max77387_power_rail *pw = &info->pwr_rail;
+       int err;
+
+       err = max77387_regulator_get(info, &pw->vin, "vin"); /* 3.3v */
+       err |= max77387_regulator_get(info, &pw->vdd, "vdd"); /* 1.8v */
+       info->pwr_state = NVC_PWR_OFF;
+
+       return err;
+};
+
+static const struct file_operations max77387_fileops = {
+       .owner = THIS_MODULE,
+       .open = max77387_open,
+       .unlocked_ioctl = max77387_ioctl,
+       .release = max77387_release,
+};
+
+static void max77387_del(struct max77387_info *info)
+{
+       max77387_power_set(info, NVC_PWR_OFF);
+       max77387_power_put(&info->pwr_rail);
+}
+
+static int max77387_remove(struct i2c_client *client)
+{
+       struct max77387_info *info = i2c_get_clientdata(client);
+
+       dev_dbg(info->dev, "%s\n", __func__);
+       misc_deregister(&info->miscdev);
+       max77387_del(info);
+       if (info->d_max77387)
+               debugfs_remove_recursive(info->d_max77387);
+
+       return 0;
+}
+
+static int max77387_debugfs_init(struct max77387_info *info);
+
+static void max77387_caps_layout(struct max77387_info *info)
+{
+#define MAX77387_FLASH_CAP_TIMEOUT_SIZE \
+       (max77387_flash_cap_size + max77387_flash_timeout_size)
+#define MAX77387_TORCH_CAP_TIMEOUT_SIZE \
+       (max77387_torch_cap_size + max77387_torch_timeout_size)
+       void *start_ptr = (void *)info + sizeof(*info);
+
+       info->flash_cap[0] = start_ptr;
+       info->flash_timeouts[0] = start_ptr + max77387_flash_cap_size;
+
+       start_ptr += MAX77387_FLASH_CAP_TIMEOUT_SIZE;
+       info->flash_cap[1] = start_ptr;
+       info->flash_timeouts[1] = start_ptr + max77387_flash_cap_size;
+
+       info->flash_cap_size = MAX77387_FLASH_CAP_TIMEOUT_SIZE;
+
+       start_ptr += MAX77387_FLASH_CAP_TIMEOUT_SIZE;
+       info->torch_cap[0] = start_ptr;
+       info->torch_timeouts[0] = start_ptr + max77387_torch_cap_size;
+
+       start_ptr += MAX77387_TORCH_CAP_TIMEOUT_SIZE;
+       info->torch_cap[1] = start_ptr;
+       info->torch_timeouts[1] = start_ptr + max77387_torch_cap_size;
+
+       info->torch_cap_size = MAX77387_TORCH_CAP_TIMEOUT_SIZE;
+       dev_dbg(info->dev, "%s: %d(%d + %d), %d(%d + %d)\n", __func__,
+               info->flash_cap_size, max77387_flash_cap_size,
+               max77387_flash_timeout_size, info->torch_cap_size,
+               max77387_torch_cap_size, max77387_torch_timeout_size);
+}
+
+static int max77387_probe(
+       struct i2c_client *client, const struct i2c_device_id *id)
+{
+       struct max77387_info *info;
+       char dname[16];
+
+       dev_info(&client->dev, "%s\n", __func__);
+
+       info = devm_kzalloc(&client->dev, sizeof(*info) +
+                       max77387_max_flash_cap_size +
+                       max77387_max_torch_cap_size,
+                       GFP_KERNEL);
+       if (info == NULL) {
+               dev_err(&client->dev, "%s: kzalloc error\n", __func__);
+               return -ENOMEM;
+       }
+
+       info->regmap = devm_regmap_init_i2c(client, &max77387_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;
+               dev_dbg(&client->dev, "pdata: %s\n", info->pdata->dev_name);
+       } else
+               dev_warn(&client->dev, "%s NO platform data\n", __func__);
+
+       max77387_power_get(info);
+
+       max77387_caps_layout(info);
+
+       max77387_update_config(info);
+
+       /* flash mode */
+       info->op_mode = MAXFLASH_MODE_NONE;
+
+       max77387_configure(info, false);
+
+       max77387_edp_register(info);
+
+       i2c_set_clientdata(client, info);
+       mutex_init(&info->mutex);
+
+       max77387_dev_id(info);
+       if ((info->pdata->cfg & NVC_CFG_NODEV) &&
+               ((info->chip_id & 0xff00) == 0x91)) {
+               max77387_del(info);
+               return -ENODEV;
+       }
+
+       if (info->pdata->dev_name != NULL)
+               strncpy(dname, info->pdata->dev_name, sizeof(dname));
+       else
+               strncpy(dname, "max77387", sizeof(dname));
+       if (info->pdata->num)
+               snprintf(dname, sizeof(dname), "%s.%u",
+                               dname, info->pdata->num);
+       info->miscdev.name = dname;
+       info->miscdev.fops = &max77387_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);
+               max77387_del(info);
+               return -ENODEV;
+       }
+
+       max77387_debugfs_init(info);
+       return 0;
+}
+
+static int max77387_status_show(struct seq_file *s, void *data)
+{
+       struct max77387_info *info = s->private;
+
+       dev_info(info->dev, "%s\n", __func__);
+
+       seq_printf(s, "max77387 status:\n"
+               "    Power State      = %01x\n"
+               "    Led Mask         = %01x\n"
+               "    Led1 Current     = 0x%02x\n"
+               "    Led2 Current     = 0x%02x\n"
+               "    Output Mode      = %s\n"
+               "    Led Settings     = 0x%02x\n"
+               "    Flash TimeOut    = 0x%02x\n"
+               "    Torch TimeOut    = 0x%02x\n"
+               "    PinState Mask    = 0x%04x\n"
+               "    PinState Values  = 0x%04x\n"
+               "    Max_Peak_Current = %dmA\n"
+               ,
+               info->pwr_state,
+               info->config.led_mask,
+               info->regs.led1_fcurr,
+               info->regs.led2_fcurr,
+               info->op_mode == MAXFLASH_MODE_FLASH ? "FLASH" :
+               info->op_mode == MAXFLASH_MODE_TORCH ? "TORCH" : "NONE",
+               info->settings.fled_trig,
+               info->regs.f_timer,
+               info->regs.t_timer,
+               info->pdata->pinstate.mask,
+               info->pdata->pinstate.values,
+               info->config.max_peak_current_mA
+               );
+
+       return 0;
+}
+
+static ssize_t max77387_attr_set(struct file *s,
+               const char __user *user_buf, size_t count, loff_t *ppos)
+{
+       struct max77387_info *info =
+               ((struct seq_file *)s->private_data)->private;
+       char buf[24];
+       int buf_size;
+       u32 val = 0;
+
+       dev_info(info->dev, "%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;
+
+       dev_err(info->dev, "SYNTAX ERROR: %s\n", buf);
+       return -EFAULT;
+
+set_attr:
+       dev_info(info->dev, "new data = %x\n", val);
+       switch (buf[0]) {
+       /* enable/disable power */
+       case 'p':
+               if (val)
+                       max77387_power_set(info, NVC_PWR_ON);
+               else
+                       max77387_power_set(info, NVC_PWR_OFF);
+               break;
+       /* enable/disable led 1/2 */
+       case 'l':
+               info->config.led_mask = val;
+               max77387_configure(info, false);
+               break;
+       /* change led 1/2 current settings */
+       case 'c':
+               max77387_set_leds(info, info->config.led_mask,
+                       val & 0xff, (val >> 8) & 0xff);
+               break;
+       /* modify flash timeout reg */
+       case 'f':
+               info->new_timer = val;
+               break;
+       /* set led work mode/trigger mode */
+       case 'x':
+               info->op_mode = (val & 0x300) >> 8;
+               info->settings.fled_trig = val & 0xff;
+               break;
+       /* set max_peak_current_mA */
+       case 'k':
+               if (val & 0xffff)
+                       info->config.max_peak_current_mA = val & 0xffff;
+               max77387_configure(info, true);
+               break;
+       /* change pinstate setting */
+       case 'm':
+               info->pdata->pinstate.mask = (val >> 16) & 0xffff;
+               info->pdata->pinstate.values = val & 0xffff;
+               break;
+       /* trigger an external flash/torch event */
+       case 'g':
+               info->pdata->gpio_strobe = val & 0xffff;
+               max77387_strobe(info, (val >> 16) & 1);
+               break;
+       }
+
+       return count;
+}
+
+static int max77387_debugfs_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, max77387_status_show, inode->i_private);
+}
+
+static const struct file_operations max77387_debugfs_fops = {
+       .open = max77387_debugfs_open,
+       .read = seq_read,
+       .write = max77387_attr_set,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
+static int max77387_debugfs_init(struct max77387_info *info)
+{
+       struct dentry *d;
+
+       info->d_max77387 = debugfs_create_dir(
+               info->miscdev.this_device->kobj.name, NULL);
+       if (info->d_max77387 == NULL) {
+               dev_err(info->dev, "%s: debugfs mk dir failed\n", __func__);
+               return -ENOMEM;
+       }
+
+       d = debugfs_create_file("d", S_IRUGO|S_IWUSR, info->d_max77387,
+               (void *)info, &max77387_debugfs_fops);
+       if (!d) {
+               dev_err(info->dev, "%s: debugfs mk file failed\n", __func__);
+               debugfs_remove_recursive(info->d_max77387);
+               info->d_max77387 = NULL;
+       }
+
+       return -EFAULT;
+}
+
+static const struct i2c_device_id max77387_id[] = {
+       { "max77387", 0 },
+       { },
+};
+
+static struct i2c_driver max77387_drv = {
+       .driver = {
+               .name = "max77387",
+               .owner = THIS_MODULE,
+       },
+       .id_table = max77387_id,
+       .probe = max77387_probe,
+       .remove = max77387_remove,
+#ifdef CONFIG_PM
+       .shutdown = max77387_shutdown,
+       .suspend  = max77387_suspend,
+       .resume   = max77387_resume,
+#endif
+};
+
+module_i2c_driver(max77387_drv);
+
+MODULE_DESCRIPTION("MAXIM MAX77387 flash/torch driver");
+MODULE_AUTHOR("Charlie Huang <chahuang@nvidia.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/media/max77387.h b/include/media/max77387.h
new file mode 100644 (file)
index 0000000..95a01a8
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2013, 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __MAX77387_H__
+#define __MAX77387_H__
+
+#include <media/nvc_torch.h>
+
+struct max77387_power_rail {
+       /* to enable the module power */
+       struct regulator *vin;
+       /* to enable the host interface power */
+       struct regulator *vdd;
+};
+
+struct max77387_led_config {
+       u16 color_setting;
+       u16 flash_torch_ratio;  /* max flash to max torch ratio, in 1/1000 */
+       u16 granularity;        /* 1, 10, 100, ... to carry float settings */
+       u16 flash_levels;       /* calibrated flash levels < 32 */
+       /* this table contains the calibrated flash level - luminance pair */
+       struct nvc_torch_lumi_level_v1 *lumi_levels;
+};
+
+struct max77387_config {
+       u32 led_mask;           /* led(s) enabled, 1/2/3 - left/right/both */
+       bool synchronized_led;  /* if both leds enabled, consider as one. */
+       u16 flash_trigger_mode; /* 0, 3=flash is triggered via i2c interface.
+                                  1=high level on the flash_en pin will turn
+                                  the flash on.
+                                  2=high level on the torch_en pin will turn
+                                  the flash on.
+                                  4=high level on both torch_en and flash_en
+                                  pin will turn the flash on.
+                                  5=high level on the torch_en or flash_en pin
+                                  will turn the flash on. */
+       u16 flash_mode;         /* 1=one_shot_mode, flash is triggerred on the
+                                  rising edge of FLASHEN/TORCHEN/I2C_bit, and
+                                  terminated based on the flash safety timer
+                                  value.
+                                  0, 2=run for MAX timer, flash is triggerred
+                                  on the rising edge of FLASHEN/TORCHEN/I2C,
+                                  and terminated based on the falling edge of
+                                  FLASHEN/TORCHEN/I2C_bit and flash safety
+                                  timer value, whichever comes first.*/
+       u16 torch_trigger_mode; /* 0, 3=torch is triggered via i2c interface.
+                                  1=high level on the flash_en pin will turn
+                                  the torch on.
+                                  2=high level on the torch_en pin will turn
+                                  the torch on.
+                                  4=high level on both torch_en and flash_en
+                                  pin will turn the torch on.
+                                  5=high level on the torch_en or flash_en pin
+                                  will turn the torch on. */
+       u16 torch_mode;         /* 1=torch safety timer disabled, torch is
+                                  controlled solely by the FLASHEN/TORCHEN/I2C.
+                                  2=one_shot_mode, torch is triggerred on the
+                                  rising edge of FLASHEN/TORCHEN/I2C_bit, and
+                                  terminated based on the torch safety timer
+                                  setting.
+                                  0, 3=run MAX timer, torch is triggerred on
+                                  the rising edge of FLASHEN/TORCHEN/I2C_bit,
+                                  and terminated based on the falling edge of
+                                  FLASHEN/TORCHEN/ I2C_bit and torch safety
+                                  timer setting, whichever comes first.*/
+       u16 adaptive_mode;      /* 1=fix mode, 2=adaptive mode */
+       /* TX MASK settings */
+       u16 tx1_mask_mA;
+       u16 tx2_mask_mA;
+       /* flash/torch ramp settings */
+       u16 flash_rampup_uS;
+       u16 flash_rampdn_uS;
+       u16 torch_rampup_uS;
+       u16 torch_rampdn_uS;
+       /* default flash timer */
+       u16 def_ftimer;
+       /* LED configuration, two identical leds must be connected. */
+       u16 max_total_current_mA; /* Both leds' maximum peak current in mA */
+       u16 max_peak_current_mA; /* This led's maximum peak current in mA */
+       u16 max_torch_current_mA; /* This leds maximum torch current in mA */
+       u16 max_peak_duration_ms; /* the maximum duration max_peak_current_mA
+                                    can be applied */
+       u16 max_flash_threshold_mV;  /* low battery detection threshold.
+                                       2400mV ~ 3400mV. */
+       u16 max_flash_hysteresis_mV; /* low battery detection hysteresis.
+                                       100mV ~ 300mV */
+       u16 max_flash_lbdly_f_uS; /* Low battery delay timer for falling edge
+                                       detection. Adjustable from 256uS to
+                                       2048uS in 256uS steps. */
+       u16 max_flash_lbdly_r_uS; /* Low battery delay timer for raising edge
+                                       detection. Adjustable from 256uS to
+                                       2048uS in 256uS steps. */
+       struct max77387_led_config led_config[2];
+};
+
+struct max77387_platform_data {
+       struct max77387_config config;
+       unsigned cfg; /* use the NVC_CFG_ defines */
+       unsigned num; /* see implementation notes in driver */
+       unsigned sync; /* see implementation notes in driver */
+       const char *dev_name; /* see implementation notes in driver */
+       struct nvc_torch_pin_state pinstate; /* see notes in driver */
+       unsigned gpio_strobe; /* GPIO connected to the ACT signal */
+       struct edp_client edpc_config;
+
+       int (*poweron_callback)(struct max77387_power_rail *pw);
+       int (*poweroff_callback)(struct max77387_power_rail *pw);
+};
+
+#endif
+/* __MAX77387_H__ */