media: tegra: as3648: initial driver bring up
Charlie Huang [Fri, 7 Sep 2012 20:43:16 +0000 (13:43 -0700)]
New flash controller with dual led outputs, as well as over-current
protection, low-voltage protection, flash timeout control, etc.

bug 1048411

Change-Id: I20a74229cc357a4b93ed3ed70e663ef0acc258d9
Signed-off-by: Charlie Huang <chahuang@nvidia.com>
(cherry picked from commit f84be39819cd40e2bfbc839ca7dd74c1143893be)
Reviewed on: http://git-master/r/#change,133114
Reviewed-on: http://git-master/r/145660
Reviewed-by: Sivasubramaniam Venkataraman <svenkatarama@nvidia.com>
GVS: Gerrit_Virtual_Submit
Reviewed-by: Thomas Cherry <tcherry@nvidia.com>

Rebase-Id: R3a6ca7d45d2c5e07209f2d7315ba245b0c5b6827

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

index 845d459..9bde762 100644 (file)
@@ -109,6 +109,12 @@ config MAX77665_FLASH
         ---help---
           This is a driver for the MAX77665 flash/torch camera device
 
+config TORCH_AS364X
+        tristate "AS364X flash/torch support"
+        depends on I2C && ARCH_TEGRA
+        ---help---
+          This is a driver for the AS364X flash/torch camera device
+
 config VIDEO_SH532U
         tristate "SH532U focuser support"
         depends on I2C && ARCH_TEGRA
index d32835d..957b2ed 100644 (file)
@@ -14,6 +14,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_AS364X)     += as364x.o
 obj-$(CONFIG_VIDEO_SH532U)             += sh532u.o
 obj-$(CONFIG_VIDEO_AD5820)             += ad5820.o
 obj-$(CONFIG_VIDEO_AD5816)     += ad5816.o
diff --git a/drivers/media/video/tegra/as364x.c b/drivers/media/video/tegra/as364x.c
new file mode 100644 (file)
index 0000000..cd1100d
--- /dev/null
@@ -0,0 +1,1411 @@
+/*
+ * AS364X.c - AS364X flash/torch kernel driver
+ *
+ * Copyright (c) 2012, 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/>.
+ */
+
+#include <linux/kernel.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/list.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+#include <media/nvc.h>
+#include <media/as364x.h>
+
+#define AS364X_REG_CHIPID              0x00
+#define AS364X_REG_LED1_SET_CURR       0x01
+#define AS364X_REG_LED2_SET_CURR       0x02
+#define AS364X_REG_TXMASK              0x03
+#define AS364X_REG_LOWVOLTAGE          0x04
+#define AS364X_REG_FLASHTIMER          0x05
+#define AS364X_REG_CONTROL             0x06
+#define AS364X_REG_STROBE              0x07
+#define AS364X_REG_FAULT               0x08
+#define AS364X_REG_PWM_INDICATOR       0x09
+#define AS364X_REG_LED_CURR_MIN                0x0E
+#define AS364X_REG_LED_CURR_ACT                0x0F
+#define AS364X_REG_PASSWORD            0x80
+#define AS364X_REG_CURR_BOOST          0x81
+
+#define AS364X_REG_CONTROL_MODE_EXT_TORCH      0x00
+#define AS364X_REG_CONTROL_MODE_INDICATOR      0x01
+#define AS364X_REG_CONTROL_MODE_ASSIST         0x02
+#define AS364X_REG_CONTROL_MODE_FLASH          0x03
+
+#define AS364X_MAX_ASSIST_CURRENT(x)    \
+       DIV_ROUND_UP(((x) * 0xff * 0x7f / 0xff), 1000)
+#define AS364X_MAX_INDICATOR_CURRENT(x) \
+       DIV_ROUND_UP(((x) * 0xff * 0x3f / 0xff), 1000)
+
+#define AS364X_MAX_FLASH_LEVEL 256
+#define AS364X_MAX_TORCH_LEVEL 128
+
+#define SUSTAINTIME_DEF                820
+#define RECHARGEFACTOR_DEF     197
+
+#define as364x_max_flash_cap_size      (sizeof(u32) \
+                               + (sizeof(struct nvc_torch_level_info) \
+                               * (AS364X_MAX_FLASH_LEVEL)))
+#define as364x_max_torch_cap_size      (sizeof(u32) \
+                               + (sizeof(s32) * (AS364X_MAX_TORCH_LEVEL)))
+
+struct as364x_caps_struct {
+       char *name;
+       u32 curr_step_uA;
+       u32 curr_step_boost_uA;
+       u32 txmask_step_uA;
+       u32 txmask_step_boost_uA;
+       u32 num_regs;
+       u32 max_peak_curr_mA;
+       u32 min_ilimit_mA;
+       u32 max_assist_curr_mA;
+       u32 max_indicator_curr_mA;
+       bool led2_support;
+};
+
+struct as364x_reg_cache {
+       u8 dev_id;
+       u8 led1_curr;
+       u8 led2_curr;
+       u8 txmask;
+       u8 strobe;
+       u8 ftime;
+       u8 vlow;
+       u8 pwm_ind;
+};
+
+static const struct as364x_caps_struct as364x_caps[] = {
+       {"as3643", 5098, 0, 81600, 0, 11, 1300, 1000,
+               AS364X_MAX_ASSIST_CURRENT(5098),
+               AS364X_MAX_INDICATOR_CURRENT(5098), false},
+       {"as3647", 6274, 0, 100400, 0, 11, 1600, 2000,
+               AS364X_MAX_ASSIST_CURRENT(6274),
+               AS364X_MAX_INDICATOR_CURRENT(6274), false},
+       {"as3648", 3529, 3921, 56467, 62747, 14, 1000, 2000,
+               AS364X_MAX_ASSIST_CURRENT(3529),
+               AS364X_MAX_INDICATOR_CURRENT(3529), true},
+};
+
+/* translated from the default register values after power up */
+const struct as364x_config default_cfg = {
+       .use_tx_mask = 0,
+       .I_limit_mA = 3000,
+       .txmasked_current_mA = 339,
+       .vin_low_v_run_mV = 3220,
+       .vin_low_v_mV = 3300,
+       .strobe_type = 2,
+       .freq_switch_on = 0,
+       .led_off_when_vin_low = 0,
+       .max_peak_current_mA = 900,
+       .max_sustained_current_mA = 0,
+       .max_peak_duration_ms = 0,
+       .min_current_mA = 0,
+};
+
+struct as364x_info {
+       struct i2c_client *i2c_client;
+       struct miscdevice miscdev;
+       struct dentry *d_as364x;
+       struct list_head list;
+       struct as364x_info *s_info;
+       struct mutex mutex;
+       struct regulator *v_in;
+       struct as364x_power_rail power;
+       struct as364x_platform_data *pdata;
+       struct nvc_torch_flash_capabilities *flash_cap;
+       struct nvc_torch_torch_capabilities *torch_cap;
+       struct as364x_caps_struct caps;
+       struct as364x_config config;
+       struct as364x_reg_cache regs;
+       atomic_t in_use;
+       int flash_cap_size;
+       int torch_cap_size;
+       int pwr_state;
+       u8 s_mode;
+       u8 flash_mode;
+       u8 led_num;
+       u8 led_mask;
+};
+
+static struct as364x_platform_data as364x_default_pdata = {
+       .cfg            = 0,
+       .num            = 0,
+       .sync           = 0,
+       .dev_name       = "torch",
+       .pinstate       = {0x0000, 0x0000},
+       .led_mask       = 3,
+};
+
+static const struct i2c_device_id as364x_id[] = {
+       { "as364x", 0 },
+       { "as3643", 0 },
+       { "as3647", 0 },
+       { "as3648", 0 },
+       { },
+};
+
+MODULE_DEVICE_TABLE(i2c, as364x_id);
+
+static LIST_HEAD(as364x_info_list);
+static DEFINE_SPINLOCK(as364x_spinlock);
+
+static const u16 v_in_low[] = {0, 3000, 3070, 3140, 3220, 3300, 3338, 3470};
+
+static int as364x_debugfs_init(struct as364x_info *info);
+
+static int as364x_reg_rd(struct as364x_info *info, u8 reg, u8 *val)
+{
+       struct i2c_msg msg[2];
+
+       *val = 0;
+       msg[0].addr = info->i2c_client->addr;
+       msg[0].flags = 0;
+       msg[0].len = 1;
+       msg[0].buf = &reg;
+       msg[1].addr = info->i2c_client->addr;
+       msg[1].flags = I2C_M_RD;
+       msg[1].len = 1;
+       msg[1].buf = val;
+       if (i2c_transfer(info->i2c_client->adapter, msg, 2) != 2)
+               return -EIO;
+
+       return 0;
+}
+
+static int as364x_reg_wr(struct as364x_info *info, u8 reg, u8 val)
+{
+       struct i2c_msg msg;
+       u8 buf[2];
+
+       buf[0] = reg;
+       buf[1] = val;
+       msg.addr = info->i2c_client->addr;
+       msg.flags = 0;
+       msg.len = 2;
+       msg.buf = &buf[0];
+       if (i2c_transfer(info->i2c_client->adapter, &msg, 1) != 1)
+               return -EIO;
+
+       return 0;
+}
+
+static int as364x_set_leds(struct as364x_info *info,
+                       u8 mask, u8 curr1, u8 curr2)
+{
+       int err = 0;
+       u8 val = 0;
+
+       if (mask & 1) {
+               if (info->flash_mode == AS364X_REG_CONTROL_MODE_FLASH) {
+                       if (curr1 >= info->flash_cap->numberoflevels)
+                               curr1 = info->flash_cap->numberoflevels - 1;
+               } else {
+                       if (curr1 >= info->torch_cap->numberoflevels)
+                               curr1 = info->torch_cap->numberoflevels - 1;
+               }
+
+               err = as364x_reg_wr(info, AS364X_REG_LED1_SET_CURR, curr1);
+               if (!err)
+                       info->regs.led1_curr = curr1;
+               else
+                       goto set_led_end;
+       }
+
+       if (mask & 2 && info->caps.led2_support) {
+               if (info->flash_mode == AS364X_REG_CONTROL_MODE_FLASH) {
+                       if (curr2 >= info->flash_cap->numberoflevels)
+                               curr2 = info->flash_cap->numberoflevels - 1;
+               } else {
+                       if (curr2 >= info->torch_cap->numberoflevels)
+                               curr2 = info->torch_cap->numberoflevels - 1;
+               }
+
+               err = as364x_reg_wr(info, AS364X_REG_LED2_SET_CURR, curr2);
+               if (!err)
+                       info->regs.led2_curr = curr2;
+               else
+                       goto set_led_end;
+       }
+
+       err = as364x_reg_wr(info, AS364X_REG_FLASHTIMER, info->regs.ftime);
+       val = info->flash_mode;
+       if (mask == 0 || (curr1 == 0 && curr2 == 0))
+               val &= ~0x08;
+       else
+               val |= 0x08;
+       pr_info("%s %x %x %x val = %x\n", __func__, mask, curr1, curr2, val);
+       err |= as364x_reg_wr(info, AS364X_REG_CONTROL, val);
+
+set_led_end:
+       return err;
+}
+
+static int as364x_set_txmask(struct as364x_info *info)
+{
+       struct as364x_caps_struct *p_cap = &info->caps;
+       struct as364x_config *p_cfg = &info->config;
+       int err;
+       u8 tm;
+       u32 limit = 0, txmask;
+
+       tm = p_cfg->use_tx_mask ? 1 : 0;
+
+       if (p_cfg->I_limit_mA > p_cap->min_ilimit_mA)
+               limit = (p_cfg->I_limit_mA - p_cap->min_ilimit_mA) / 500;
+
+       if (limit > 3)
+               limit = 3;
+       tm |= limit<<2;
+
+       txmask = p_cfg->txmasked_current_mA * 1000;
+
+       if (p_cfg->boost_mode)
+               txmask /= p_cap->txmask_step_boost_uA;
+       else
+               txmask /= p_cap->txmask_step_uA;
+
+       if (txmask > 0xf)
+               txmask = 0xf;
+
+       tm |= txmask<<4;
+
+       err = as364x_reg_wr(info, AS364X_REG_TXMASK, tm);
+       if (!err)
+               info->regs.txmask = tm;
+
+       return err;
+}
+
+static int as364x_get_vin_index(u16 mV)
+{
+       int vin;
+
+       for (vin = ARRAY_SIZE(v_in_low) - 1; vin >= 0; vin--) {
+               if (mV >= v_in_low[vin])
+                       break;
+       }
+
+       return vin;
+}
+
+static void as364x_update_config(struct as364x_info *info)
+{
+       struct as364x_config *pcfg = &info->config;
+       struct as364x_config *pcfg_cust = &info->pdata->config;
+
+       memcpy(pcfg, &default_cfg, sizeof(info->config));
+
+       pcfg->use_tx_mask = pcfg_cust->use_tx_mask;
+       pcfg->freq_switch_on = pcfg_cust->freq_switch_on;
+       pcfg->inct_pwm = pcfg_cust->inct_pwm;
+       pcfg->load_balance_on = pcfg_cust->load_balance_on;
+       pcfg->led_off_when_vin_low = pcfg_cust->led_off_when_vin_low;
+       pcfg->boost_mode = pcfg_cust->boost_mode;
+
+       if (pcfg_cust->strobe_type)
+               pcfg->strobe_type = pcfg_cust->strobe_type;
+
+       if (pcfg_cust->vin_low_v_run_mV) {
+               if (pcfg_cust->vin_low_v_run_mV == 0xffff)
+                       pcfg->vin_low_v_run_mV = 0;
+               else
+                       pcfg->vin_low_v_run_mV = pcfg_cust->vin_low_v_run_mV;
+       }
+
+       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->I_limit_mA)
+               pcfg->I_limit_mA = pcfg_cust->I_limit_mA;
+
+       if (pcfg_cust->txmasked_current_mA)
+               pcfg->txmasked_current_mA = pcfg_cust->txmasked_current_mA;
+
+       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_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 int as364x_update_settings(struct as364x_info *info)
+{
+       int err;
+
+       err = as364x_set_txmask(info);
+
+       err |= as364x_reg_wr(info, AS364X_REG_LOWVOLTAGE, info->regs.vlow);
+
+       err |= as364x_reg_wr(info, AS364X_REG_PWM_INDICATOR,
+                       info->regs.pwm_ind);
+
+       err |= as364x_reg_wr(info, AS364X_REG_STROBE, info->regs.strobe);
+
+       if (info->caps.led2_support) {
+               err |= as364x_reg_wr(info, AS364X_REG_PASSWORD, 0xa1);
+               if (info->config.boost_mode)
+                       err |= as364x_reg_wr(info, AS364X_REG_CURR_BOOST, 1);
+               else
+                       err |= as364x_reg_wr(info, AS364X_REG_CURR_BOOST, 0);
+       }
+
+       err |= as364x_set_leds(info,
+               info->led_mask, info->regs.led1_curr, info->regs.led2_curr);
+
+       return err;
+}
+
+static int as364x_configure(struct as364x_info *info, bool update)
+{
+       struct as364x_config *pcfg = &info->config;
+       struct as364x_caps_struct *pcap = &info->caps;
+       struct nvc_torch_flash_capabilities *pfcap = info->flash_cap;
+       struct nvc_torch_torch_capabilities *ptcap = info->torch_cap;
+       int val;
+       int i;
+
+       if (!pcap->led2_support)
+               pcfg->boost_mode = false;
+
+       val = as364x_get_vin_index(pcfg->vin_low_v_run_mV);
+       info->regs.vlow = val<<0;
+
+       val = as364x_get_vin_index(pcfg->vin_low_v_mV);
+       info->regs.vlow |= val<<3;
+
+       if (pcfg->led_off_when_vin_low)
+               info->regs.vlow |= 0x40;
+
+       info->regs.pwm_ind = pcfg->inct_pwm & 0x03;
+       if (pcfg->freq_switch_on)
+               info->regs.pwm_ind |= 0x04;
+       if (pcfg->load_balance_on)
+               info->regs.pwm_ind |= 0x20;
+
+       info->regs.strobe = pcfg->strobe_type ? 0xc0 : 0x80;
+       info->led_mask = info->pdata->led_mask;
+
+       if (pcfg->max_peak_current_mA > pcap->max_peak_curr_mA ||
+               !pcfg->max_peak_current_mA) {
+               dev_warn(&info->i2c_client->dev,
+                               "max_peak_current_mA of %d invalid"
+                               "changing to %d\n",
+                               pcfg->max_peak_current_mA,
+                               pcap->max_peak_curr_mA);
+               pcfg->max_peak_current_mA = pcap->max_peak_curr_mA;
+       }
+
+       info->led_num = 1;
+       if (pcap->led2_support && (info->led_mask & 3) == 3)
+               info->led_num = 2;
+       val = pcfg->max_peak_current_mA * info->led_num;
+
+       if (!pcfg->max_total_current_mA || pcfg->max_total_current_mA > val)
+               pcfg->max_total_current_mA = val;
+       pcfg->max_peak_current_mA =
+               info->config.max_total_current_mA / info->led_num;
+
+       if (pcfg->max_sustained_current_mA > pcap->max_assist_curr_mA ||
+               !pcfg->max_sustained_current_mA) {
+               dev_warn(&info->i2c_client->dev,
+                               "max_sustained_current_mA of %d invalid"
+                               "changing 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_warn(&info->i2c_client->dev,
+                               "min_current_mA lower than possible, icreasing"
+                               " to %d\n",
+                               pcfg->min_current_mA);
+       }
+       if (pcfg->min_current_mA > pcap->max_indicator_curr_mA) {
+               dev_warn(&info->i2c_client->dev,
+                               "min_current_mA of %d higher than possible,"
+                               " reducing to %d",
+                               pcfg->min_current_mA,
+                               pcap->max_indicator_curr_mA);
+               pcfg->min_current_mA =
+                       pcap->max_indicator_curr_mA;
+       }
+
+       if (pcfg->boost_mode)
+               val = pcap->curr_step_uA;
+       else
+               val = pcap->curr_step_boost_uA;
+       for (i = 0; i < AS364X_MAX_FLASH_LEVEL; i++) {
+               pfcap->levels[i].guidenum = val * i / 1000;
+               if (pfcap->levels[i].guidenum >
+                       pcfg->max_peak_current_mA) {
+                       pfcap->levels[i].guidenum = 0;
+                       break;
+               }
+               pfcap->levels[i].sustaintime = SUSTAINTIME_DEF;
+               pfcap->levels[i].rechargefactor = RECHARGEFACTOR_DEF;
+       }
+       info->flash_cap_size = (sizeof(u32) +
+                       (sizeof(struct nvc_torch_level_info) * i));
+       pfcap->numberoflevels = i;
+
+       for (i = 0; i < AS364X_MAX_TORCH_LEVEL; i++) {
+               ptcap->guidenum[i] = pfcap->levels[i].guidenum;
+               if (ptcap->guidenum[i] > pcfg->max_peak_current_mA) {
+                       ptcap->guidenum[i] = 0;
+                       break;
+               }
+       }
+       info->torch_cap_size = (sizeof(u32) + (sizeof(s32) * i));
+       ptcap->numberoflevels = i;
+
+       if (update && (info->pwr_state == NVC_PWR_COMM ||
+                       info->pwr_state == NVC_PWR_ON))
+               return as364x_update_settings(info);
+
+       return 0;
+}
+
+static int as364x_strobe(struct as364x_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));
+}
+
+#ifdef CONFIG_PM
+static int as364x_suspend(struct i2c_client *client, pm_message_t msg)
+{
+       struct as364x_info *info = i2c_get_clientdata(client);
+
+       dev_info(&client->dev, "Suspending %s\n", info->caps.name);
+
+       return 0;
+}
+
+static int as364x_resume(struct i2c_client *client)
+{
+       struct as364x_info *info = i2c_get_clientdata(client);
+
+       dev_info(&client->dev, "Resuming %s\n", info->caps.name);
+
+       return 0;
+}
+
+static void as364x_shutdown(struct i2c_client *client)
+{
+       struct as364x_info *info = i2c_get_clientdata(client);
+
+       dev_info(&client->dev, "Shutting down %s\n", info->caps.name);
+
+       mutex_lock(&info->mutex);
+       as364x_set_leds(info, 3, 0, 0);
+       mutex_unlock(&info->mutex);
+}
+#endif
+
+static int as364x_power_on(struct as364x_info *info)
+{
+       struct as364x_power_rail *power = &info->power;
+       int err = 0;
+
+       if (power->v_in) {
+               err = regulator_enable(power->v_in);
+               if (err) {
+                       dev_err(&info->i2c_client->dev, "%s v_in err\n",
+                               __func__);
+                       return err;
+               }
+       }
+
+       if (power->v_i2c) {
+               err = regulator_enable(power->v_i2c);
+               if (err) {
+                       dev_err(&info->i2c_client->dev, "%s v_i2c err\n",
+                               __func__);
+                       regulator_disable(power->v_in);
+                       return err;
+               }
+       }
+
+       if (info->pdata && info->pdata->power_on_callback)
+               err = info->pdata->power_on_callback(&info->power);
+
+       if (!err) {
+               usleep_range(100, 120);
+               err = as364x_update_settings(info);
+       }
+       return err;
+}
+
+static int as364x_power_off(struct as364x_info *info)
+{
+       struct as364x_power_rail *power = &info->power;
+       int err = 0;
+
+       if (info->pdata && info->pdata->power_off_callback)
+               err = info->pdata->power_off_callback(&info->power);
+       if (IS_ERR_VALUE(err))
+               return err;
+
+       if (power->v_in) {
+               err = regulator_disable(power->v_in);
+               if (err) {
+                       dev_err(&info->i2c_client->dev, "%s vi_in err\n",
+                               __func__);
+                       return err;
+               }
+       }
+
+       if (power->v_i2c) {
+               err = regulator_disable(power->v_i2c);
+               if (err) {
+                       dev_err(&info->i2c_client->dev, "%s vi_i2c err\n",
+                               __func__);
+                       return err;
+               }
+       }
+
+       return 0;
+}
+
+static int as364x_power(struct as364x_info *info, int pwr)
+{
+       int err = 0;
+
+       if (pwr == info->pwr_state) /* power state no change */
+               return 0;
+
+       switch (pwr) {
+       case NVC_PWR_OFF:
+               if ((info->pdata->cfg & NVC_CFG_OFF2STDBY) ||
+                            (info->pdata->cfg & NVC_CFG_BOOT_INIT))
+                       pwr = NVC_PWR_STDBY;
+               else
+                       err = as364x_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 = as364x_power_on(info);
+               break;
+       case NVC_PWR_STDBY:
+               err = as364x_power_on(info);
+               err |= as364x_set_leds(info, 3, 0, 0);
+               break;
+       case NVC_PWR_COMM:
+       case NVC_PWR_ON:
+               err = as364x_power_on(info);
+               break;
+       default:
+               err = -EINVAL;
+               break;
+       }
+
+       if (err < 0) {
+               dev_err(&info->i2c_client->dev, "%s error\n", __func__);
+               pwr = NVC_PWR_ERR;
+       }
+       info->pwr_state = pwr;
+       if (err > 0)
+               return 0;
+
+       return err;
+}
+
+static int as364x_power_sync(struct as364x_info *info, int pwr)
+{
+       int err1 = 0;
+       int err2 = 0;
+
+       if ((info->s_mode == NVC_SYNC_OFF) ||
+               (info->s_mode == NVC_SYNC_MASTER) ||
+               (info->s_mode == NVC_SYNC_STEREO))
+               err1 = as364x_power(info, pwr);
+       if ((info->s_mode == NVC_SYNC_SLAVE) ||
+               (info->s_mode == NVC_SYNC_STEREO))
+               err2 = as364x_power(info->s_info, pwr);
+       return err1 | err2;
+}
+
+static int as364x_get_dev_id(struct as364x_info *info)
+{
+       int err;
+
+       /* ChipID[7:3] is a fixed identification B0 */
+       if ((info->regs.dev_id & 0xb0) == 0xb0)
+               return 0;
+
+       if (NVC_PWR_OFF == info->pwr_state)
+               as364x_power_on(info);
+       err = as364x_reg_rd(info, AS364X_REG_CHIPID, &info->regs.dev_id);
+       if (err)
+               goto read_devid_exit;
+
+       if ((info->regs.dev_id & 0xb0) != 0xb0)
+               err = -ENODEV;
+
+read_devid_exit:
+       if (NVC_PWR_OFF == info->pwr_state)
+               as364x_power_off(info);
+       return err;
+}
+
+static int as364x_user_get_param(struct as364x_info *info, long arg)
+{
+       struct nvc_param params;
+       struct nvc_torch_pin_state pinstate;
+       const void *data_ptr = NULL;
+       u32 data_size = 0;
+       u8 reg;
+
+       if (copy_from_user(&params,
+                       (const void __user *)arg,
+                       sizeof(struct nvc_param))) {
+               dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n",
+                               __func__, __LINE__);
+               return -EINVAL;
+       }
+
+       if (info->s_mode == NVC_SYNC_SLAVE)
+               info = info->s_info;
+       switch (params.param) {
+       case NVC_PARAM_FLASH_CAPS:
+               dev_dbg(&info->i2c_client->dev, "%s FLASH_CAPS\n", __func__);
+               data_ptr = info->flash_cap;
+               data_size = info->flash_cap_size;
+               break;
+       case NVC_PARAM_FLASH_LEVEL:
+               reg = info->regs.led1_curr;
+               data_ptr = &info->flash_cap->levels[reg].guidenum;
+               data_size = sizeof(info->flash_cap->levels[reg].guidenum);
+               break;
+       case NVC_PARAM_TORCH_CAPS:
+               dev_dbg(&info->i2c_client->dev, "%s TORCH_CAPS\n", __func__);
+               data_ptr = info->torch_cap;
+               data_size = info->torch_cap_size;
+               break;
+       case NVC_PARAM_TORCH_LEVEL:
+               reg = info->regs.led1_curr;
+               data_ptr = &info->torch_cap->guidenum[reg];
+               data_size = sizeof(info->torch_cap->guidenum[reg]);
+               break;
+       case NVC_PARAM_FLASH_PIN_STATE:
+               /* By default use Active Pin State Setting */
+               pinstate = info->pdata->pinstate;
+               if ((info->flash_mode != AS364X_REG_CONTROL_MODE_FLASH) ||
+                   (!info->regs.led1_curr && !info->regs.led2_curr))
+                       pinstate.values ^= 0xffff; /* Inactive Pin Setting */
+
+               dev_dbg(&info->i2c_client->dev, "%s FLASH_PIN_STATE: %x&%x\n",
+                               __func__, pinstate.mask, pinstate.values);
+               data_ptr = &pinstate;
+               data_size = sizeof(pinstate);
+               break;
+       case NVC_PARAM_STEREO:
+               dev_dbg(&info->i2c_client->dev, "%s STEREO: %d\n",
+                               __func__, info->s_mode);
+               data_ptr = &info->s_mode;
+               data_size = sizeof(info->s_mode);
+               break;
+       default:
+               dev_err(&info->i2c_client->dev,
+                               "%s unsupported parameter: %d\n",
+                               __func__, params.param);
+               return -EINVAL;
+       }
+
+       if (params.sizeofvalue < data_size) {
+               dev_err(&info->i2c_client->dev,
+                               "%s data size mismatch %d != %d\n",
+                               __func__, params.sizeofvalue, data_size);
+               return -EINVAL;
+       }
+
+       if (copy_to_user((void __user *)params.p_value,
+                        data_ptr,
+                        data_size)) {
+               dev_err(&info->i2c_client->dev,
+                               "%s copy_to_user err line %d\n",
+                               __func__, __LINE__);
+               return -EFAULT;
+       }
+
+       return 0;
+}
+
+static int as364x_set_param(struct as364x_info *info,
+                              struct nvc_param *params,
+                              u8 val)
+{
+       int err;
+
+       switch (params->param) {
+       case NVC_PARAM_FLASH_LEVEL:
+               dev_info(&info->i2c_client->dev, "%s FLASH_LEVEL: %d\n",
+                               __func__, val);
+               info->regs.ftime =
+                       info->flash_cap->levels[val].rechargefactor;
+
+               info->flash_mode = AS364X_REG_CONTROL_MODE_FLASH;
+               err = as364x_set_leds(info, info->led_mask, val, val);
+               if (!val)
+                       info->flash_mode = AS364X_REG_CONTROL_MODE_ASSIST;
+               return err;
+       case NVC_PARAM_TORCH_LEVEL:
+               dev_info(&info->i2c_client->dev, "%s TORCH_LEVEL: %d\n",
+                               __func__, val);
+               info->flash_mode = AS364X_REG_CONTROL_MODE_ASSIST;
+               err = as364x_set_leds(info, info->led_mask, val, val);
+               return err;
+       case NVC_PARAM_FLASH_PIN_STATE:
+               dev_info(&info->i2c_client->dev, "%s FLASH_PIN_STATE: %d\n",
+                               __func__, val);
+               return as364x_strobe(info, val);
+       default:
+               dev_err(&info->i2c_client->dev,
+                               "%s unsupported parameter: %d\n",
+                               __func__, params->param);
+               return -EINVAL;
+       }
+}
+
+static int as364x_user_set_param(struct as364x_info *info, long arg)
+{
+       struct nvc_param params;
+       u8 val;
+       int err = 0;
+
+       if (copy_from_user(&params,
+                               (const void __user *)arg,
+                               sizeof(struct nvc_param))) {
+               dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n",
+                               __func__, __LINE__);
+               return -EINVAL;
+       }
+
+       if (copy_from_user(&val, (const void __user *)params.p_value,
+                          sizeof(val))) {
+               dev_err(&info->i2c_client->dev, "%s %d copy_from_user err\n",
+                               __func__, __LINE__);
+               return -EINVAL;
+       }
+
+       /* parameters independent of sync mode */
+       switch (params.param) {
+       case NVC_PARAM_STEREO:
+               dev_dbg(&info->i2c_client->dev, "%s STEREO: %d\n",
+                               __func__, (int)val);
+               if (val == info->s_mode)
+                       return 0;
+
+               switch (val) {
+               case NVC_SYNC_OFF:
+                       info->s_mode = val;
+                       if (info->s_info != NULL) {
+                               info->s_info->s_mode = val;
+                               as364x_power(info->s_info, NVC_PWR_OFF);
+                       }
+                       break;
+               case NVC_SYNC_MASTER:
+                       info->s_mode = val;
+                       if (info->s_info != NULL)
+                               info->s_info->s_mode = val;
+                       break;
+               case NVC_SYNC_SLAVE:
+               case NVC_SYNC_STEREO:
+                       if (info->s_info != NULL) {
+                               /* sync power */
+                               info->s_info->pwr_state = info->pwr_state;
+                               err = as364x_power(info->s_info,
+                                                    info->pwr_state);
+                               if (!err) {
+                                       info->s_mode = val;
+                                       info->s_info->s_mode = val;
+                               } else {
+                                       as364x_power(info->s_info,
+                                                      NVC_PWR_OFF);
+                                       err = -EIO;
+                               }
+                       } else {
+                               err = -EINVAL;
+                       }
+                       break;
+               default:
+                       err = -EINVAL;
+               }
+               if (info->pdata->cfg & NVC_CFG_NOERR)
+                       return 0;
+               return err;
+       default:
+       /* parameters dependent on sync mode */
+               switch (info->s_mode) {
+               case NVC_SYNC_OFF:
+               case NVC_SYNC_MASTER:
+                       return as364x_set_param(info, &params, val);
+               case NVC_SYNC_SLAVE:
+                       return as364x_set_param(info->s_info, &params, val);
+               case NVC_SYNC_STEREO:
+                       err = as364x_set_param(info, &params, val);
+                       if (!(info->pdata->cfg & NVC_CFG_SYNC_I2C_MUX))
+                               err |= as364x_set_param(info->s_info,
+                                               &params, val);
+                       return err;
+               default:
+                       dev_err(&info->i2c_client->dev, "%s %d internal err\n",
+                                       __func__, __LINE__);
+                       return -EINVAL;
+               }
+       }
+}
+
+static long as364x_ioctl(struct file *file,
+                          unsigned int cmd,
+                          unsigned long arg)
+{
+       struct as364x_info *info = file->private_data;
+       int pwr;
+       int err;
+
+       switch (cmd) {
+       case NVC_IOCTL_PARAM_WR:
+               return as364x_user_set_param(info, arg);
+       case NVC_IOCTL_PARAM_RD:
+               return as364x_user_get_param(info, arg);
+       case NVC_IOCTL_PWR_WR:
+               /* This is a Guaranteed Level of Service (GLOS) call */
+               pwr = (int)arg * 2;
+               dev_dbg(&info->i2c_client->dev, "%s PWR_WR: %d\n",
+                       __func__, pwr);
+               if (!pwr || (pwr > NVC_PWR_ON)) /* Invalid Power State */
+                       return 0;
+
+               err = as364x_power_sync(info, pwr);
+
+               if (info->pdata->cfg & NVC_CFG_NOERR)
+                       return 0;
+               return err;
+       case NVC_IOCTL_PWR_RD:
+               if (info->s_mode == NVC_SYNC_SLAVE)
+                       pwr = info->s_info->pwr_state / 2;
+               else
+                       pwr = info->pwr_state / 2;
+               dev_dbg(&info->i2c_client->dev, "%s PWR_RD: %d\n",
+                               __func__, pwr);
+               if (copy_to_user((void __user *)arg, (const void *)&pwr,
+                                sizeof(pwr))) {
+                       dev_err(&info->i2c_client->dev,
+                                       "%s copy_to_user err line %d\n",
+                                       __func__, __LINE__);
+                       return -EFAULT;
+               }
+
+               return 0;
+       default:
+               dev_err(&info->i2c_client->dev, "%s unsupported ioctl: %x\n",
+                               __func__, cmd);
+               return -EINVAL;
+       }
+}
+
+static int as364x_sync_en(int dev1, int dev2)
+{
+       struct as364x_info *sync1 = NULL;
+       struct as364x_info *sync2 = NULL;
+       struct as364x_info *pos = NULL;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(pos, &as364x_info_list, list) {
+               if (pos->pdata->num == dev1) {
+                       sync1 = pos;
+                       break;
+               }
+       }
+       pos = NULL;
+       list_for_each_entry_rcu(pos, &as364x_info_list, list) {
+               if (pos->pdata->num == dev2) {
+                       sync2 = pos;
+                       break;
+               }
+       }
+       rcu_read_unlock();
+       if (sync1 != NULL)
+               sync1->s_info = NULL;
+       if (sync2 != NULL)
+               sync2->s_info = NULL;
+       if (!dev1 && !dev2)
+               return 0; /* no err if default instance 0's used */
+
+       if (dev1 == dev2)
+               return -EINVAL; /* err if sync instance is itself */
+
+       if ((sync1 != NULL) && (sync2 != NULL)) {
+               sync1->s_info = sync2;
+               sync2->s_info = sync1;
+       }
+
+       return 0;
+}
+
+static int as364x_sync_dis(struct as364x_info *info)
+{
+       if (info->s_info != NULL) {
+               info->s_info->s_mode = 0;
+               info->s_info->s_info = NULL;
+               info->s_mode = 0;
+               info->s_info = NULL;
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static int as364x_open(struct inode *inode, struct file *file)
+{
+       struct as364x_info *info = NULL;
+       struct as364x_info *pos = NULL;
+       int err;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(pos, &as364x_info_list, list) {
+               if (pos->miscdev.minor == iminor(inode)) {
+                       info = pos;
+                       break;
+               }
+       }
+       rcu_read_unlock();
+       if (!info)
+               return -ENODEV;
+
+       err = as364x_sync_en(info->pdata->num, info->pdata->sync);
+       if (err == -EINVAL)
+               dev_err(&info->i2c_client->dev,
+                        "%s err: invalid num (%u) and sync (%u) instance\n",
+                        __func__, info->pdata->num, info->pdata->sync);
+       if (atomic_xchg(&info->in_use, 1))
+               return -EBUSY;
+
+       if (info->s_info != NULL) {
+               if (atomic_xchg(&info->s_info->in_use, 1))
+                       return -EBUSY;
+       }
+
+       file->private_data = info;
+       dev_dbg(&info->i2c_client->dev, "%s\n", __func__);
+       return 0;
+}
+
+static int as364x_release(struct inode *inode, struct file *file)
+{
+       struct as364x_info *info = file->private_data;
+
+       dev_dbg(&info->i2c_client->dev, "%s\n", __func__);
+       as364x_power_sync(info, NVC_PWR_OFF);
+       file->private_data = NULL;
+       WARN_ON(!atomic_xchg(&info->in_use, 0));
+       if (info->s_info != NULL)
+               WARN_ON(!atomic_xchg(&info->s_info->in_use, 0));
+       as364x_sync_dis(info);
+       return 0;
+}
+
+static int as364x_power_put(struct as364x_power_rail *pw)
+{
+       if (likely(pw->v_in))
+               regulator_put(pw->v_in);
+
+       if (likely(pw->v_i2c))
+               regulator_put(pw->v_i2c);
+
+       pw->v_in = NULL;
+       pw->v_i2c = NULL;
+
+       return 0;
+}
+
+static int as364x_regulator_get(struct as364x_info *info,
+       struct regulator **vreg, char vreg_name[])
+{
+       struct regulator *reg = NULL;
+       int err = 0;
+
+       reg = regulator_get(&info->i2c_client->dev, vreg_name);
+       if (unlikely(IS_ERR_OR_NULL(reg))) {
+               dev_err(&info->i2c_client->dev, "%s %s ERR: %d\n",
+                       __func__, vreg_name, (int)reg);
+               err = PTR_ERR(reg);
+               reg = NULL;
+       } else
+               dev_dbg(&info->i2c_client->dev, "%s: %s\n",
+                       __func__, vreg_name);
+
+       *vreg = reg;
+       return err;
+}
+
+static int as364x_power_get(struct as364x_info *info)
+{
+       struct as364x_power_rail *pw = &info->power;
+
+       as364x_regulator_get(info, &pw->v_in, "vin"); /* 3.7v */
+       as364x_regulator_get(info, &pw->v_i2c, "vi2c"); /* 1.8v */
+
+       return 0;
+}
+
+static const struct file_operations as364x_fileops = {
+       .owner = THIS_MODULE,
+       .open = as364x_open,
+       .unlocked_ioctl = as364x_ioctl,
+       .release = as364x_release,
+};
+
+static void as364x_del(struct as364x_info *info)
+{
+       as364x_power_sync(info, NVC_PWR_OFF);
+       as364x_power_put(&info->power);
+
+       as364x_sync_dis(info);
+       spin_lock(&as364x_spinlock);
+       list_del_rcu(&info->list);
+       spin_unlock(&as364x_spinlock);
+       synchronize_rcu();
+}
+
+static int as364x_remove(struct i2c_client *client)
+{
+       struct as364x_info *info = i2c_get_clientdata(client);
+
+       dev_dbg(&info->i2c_client->dev, "%s\n", __func__);
+       misc_deregister(&info->miscdev);
+       as364x_del(info);
+       return 0;
+}
+
+static int as364x_probe(
+       struct i2c_client *client,
+       const struct i2c_device_id *id)
+{
+       struct as364x_info *info;
+       char dname[16];
+       int err;
+
+       dev_dbg(&client->dev, "%s\n", __func__);
+       info = devm_kzalloc(&client->dev, sizeof(*info) +
+                       as364x_max_flash_cap_size + as364x_max_torch_cap_size,
+                       GFP_KERNEL);
+       if (info == NULL) {
+               dev_err(&client->dev, "%s: kzalloc error\n", __func__);
+               return -ENOMEM;
+       }
+
+       info->i2c_client = client;
+       if (client->dev.platform_data)
+               info->pdata = client->dev.platform_data;
+       else {
+               info->pdata = &as364x_default_pdata;
+               dev_dbg(&client->dev,
+                               "%s No platform data.  Using defaults.\n",
+                               __func__);
+       }
+
+       info->flash_cap = (void *)info + sizeof(*info);
+       info->torch_cap = (void *)info->flash_cap + as364x_max_flash_cap_size;
+       memcpy(&info->caps, &as364x_caps[info->pdata->type],
+               sizeof(info->caps));
+
+       as364x_update_config(info);
+
+       info->flash_mode = AS364X_REG_CONTROL_MODE_ASSIST; /* torch mode */
+
+       as364x_configure(info, false);
+
+       i2c_set_clientdata(client, info);
+       mutex_init(&info->mutex);
+       INIT_LIST_HEAD(&info->list);
+       spin_lock(&as364x_spinlock);
+       list_add_rcu(&info->list, &as364x_info_list);
+       spin_unlock(&as364x_spinlock);
+
+       as364x_power_get(info);
+
+       err = as364x_get_dev_id(info);
+       if (err < 0) {
+               dev_err(&client->dev, "%s device not found\n", __func__);
+               if (info->pdata->cfg & NVC_CFG_NODEV) {
+                       as364x_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, "as364x");
+       if (info->pdata->num)
+               snprintf(dname, sizeof(dname), "%s.%u",
+                        dname, info->pdata->num);
+
+       info->miscdev.name = dname;
+       info->miscdev.fops = &as364x_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);
+               as364x_del(info);
+               return -ENODEV;
+       }
+
+       as364x_debugfs_init(info);
+       return 0;
+}
+
+static int as364x_status_show(struct seq_file *s, void *data)
+{
+       struct as364x_info *k_info = s->private;
+
+       pr_info("%s\n", __func__);
+
+       seq_printf(s, "as364x status:\n"
+               "    Flash type: %s, bus %d, addr: 0x%02x\n\n"
+               "    Led Mask         = %01x\n"
+               "    Led1 Current     = 0x%02x\n"
+               "    Led2 Current     = 0x%02x\n"
+               "    Flash Mode       = 0x%02x\n"
+               "    Flash TimeOut    = 0x%02x\n"
+               "    Max_Peak_Current = 0x%04dmA\n"
+               "    Use_TxMask       = 0x%02x\n"
+               "    TxMask_Current   = 0x%04dmA\n"
+               "    Freq_Switch_on   = %s\n"
+               "    VIN_low_run      = 0x%04dmV\n"
+               "    VIN_low          = 0x%04dmV\n"
+               "    LedOff_On_VIN_low = %s\n"
+               "    PinState Mask    = 0x%04x\n"
+               "    PinState Values  = 0x%04x\n"
+               ,
+               (char *)as364x_id[k_info->pdata->type + 1].name,
+               k_info->i2c_client->adapter->nr,
+               k_info->i2c_client->addr,
+               k_info->led_mask,
+               k_info->regs.led1_curr,
+               k_info->regs.led2_curr,
+               k_info->flash_mode, k_info->regs.ftime,
+               k_info->config.max_peak_current_mA,
+               k_info->config.use_tx_mask,
+               k_info->config.txmasked_current_mA,
+               k_info->config.freq_switch_on ? "TRUE" : "FALSE",
+               k_info->config.vin_low_v_run_mV,
+               k_info->config.vin_low_v_mV,
+               k_info->config.led_off_when_vin_low ? "TRUE" : "FALSE",
+               k_info->pdata->pinstate.mask,
+               k_info->pdata->pinstate.values
+       );
+
+       return 0;
+}
+
+static ssize_t as364x_attr_set(struct file *s,
+               const char __user *user_buf, size_t count, loff_t *ppos)
+{
+       struct as364x_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]) {
+       case 'p':
+               if (val & 0xffff)
+                       as364x_power(k_info, NVC_PWR_ON);
+               else
+                       as364x_power(k_info, NVC_PWR_OFF);
+               break;
+       case 'c': /* change led 1/2 current settings */
+               as364x_set_leds(k_info, k_info->led_mask,
+                       val & 0xff, (val >> 8) & 0xff);
+               break;
+       case 'l': /* enable/disable led 1/2 */
+               k_info->pdata->led_mask = val;
+               as364x_configure(k_info, false);
+               break;
+       case 'm': /* change pinstate setting */
+               k_info->pdata->pinstate.mask = (val >> 16) & 0xffff;
+               k_info->pdata->pinstate.values = val & 0xffff;
+               break;
+       case 'f': /* modify flash timeout reg */
+               k_info->regs.ftime = val;
+               as364x_set_leds(k_info, k_info->led_mask,
+                       k_info->regs.led1_curr,
+                       k_info->regs.led2_curr);
+               break;
+       case 't': /* change txmask/torch settings */
+               k_info->config.use_tx_mask = (val >> 4) & 1;
+               k_info->config.txmasked_current_mA = val & 0x0f;
+               val = (val >> 8) & 0xffff;
+               if (val)
+                       k_info->config.I_limit_mA = val;
+               as364x_set_txmask(k_info);
+               break;
+       case 'v':
+               if (val & 0xffff)
+                       k_info->config.vin_low_v_run_mV = val & 0xffff;
+               val >>= 16;
+               if (val & 0xffff)
+                       k_info->config.vin_low_v_mV = val & 0xffff;
+               as364x_configure(k_info, true);
+               break;
+       case 'k':
+               if (val & 0xffff)
+                       k_info->config.max_peak_current_mA = val & 0xffff;
+               as364x_configure(k_info, true);
+               break;
+       case 'x':
+               if (val & 0xf)
+                       k_info->flash_mode = (val & 0xf);
+               if (val & 0xf0)
+                       k_info->config.strobe_type = (val & 0xf0) >> 4;
+               if (val & 0xf00)
+                       k_info->config.freq_switch_on =
+                               ((val & 0xf00) == 0x200);
+               if (val & 0xf000)
+                       k_info->config.led_off_when_vin_low =
+                               ((val & 0xf000) == 0x2000);
+               if (val & 0xf0000) {
+                       val = ((val & 0xf0000) >> 16) - 1;
+                       if (val >= AS364X_NUM) {
+                               pr_err("Invalid dev type %x\n", val);
+                               return -ENODEV;
+                       }
+                       k_info->pdata->type = val;
+                       memcpy(&k_info->caps,
+                               &as364x_caps[k_info->pdata->type],
+                               sizeof(k_info->caps));
+               }
+               as364x_configure(k_info, true);
+               break;
+       case 'g':
+               k_info->pdata->gpio_strobe = val;
+               as364x_strobe(k_info, 1);
+               break;
+       }
+
+       return count;
+}
+
+static int as364x_debugfs_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, as364x_status_show, inode->i_private);
+}
+
+static const struct file_operations as364x_debugfs_fops = {
+       .open = as364x_debugfs_open,
+       .read = seq_read,
+       .write = as364x_attr_set,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
+static int as364x_debugfs_init(struct as364x_info *info)
+{
+       struct dentry *d;
+
+       info->d_as364x = debugfs_create_dir(
+               info->miscdev.this_device->kobj.name, NULL);
+       if (info->d_as364x == NULL) {
+               pr_info("%s: debugfs create dir failed\n", __func__);
+               return -ENOMEM;
+       }
+
+       d = debugfs_create_file("d", S_IRUGO|S_IWUSR, info->d_as364x,
+               (void *)info, &as364x_debugfs_fops);
+       if (!d) {
+               pr_info("%s: debugfs create file failed\n", __func__);
+               debugfs_remove_recursive(info->d_as364x);
+               info->d_as364x = NULL;
+       }
+
+       return -EFAULT;
+}
+
+static struct i2c_driver as364x_driver = {
+       .driver = {
+               .name = "as364x",
+               .owner = THIS_MODULE,
+       },
+       .id_table = as364x_id,
+       .probe = as364x_probe,
+       .remove = as364x_remove,
+#ifdef CONFIG_PM
+       .shutdown = as364x_shutdown,
+       .suspend  = as364x_suspend,
+       .resume   = as364x_resume,
+#endif
+};
+
+module_i2c_driver(as364x_driver);
+
+MODULE_DESCRIPTION("AS364x flash/torch driver");
+MODULE_AUTHOR("Charlie Huang <chahuang@nvidia.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/media/as364x.h b/include/media/as364x.h
new file mode 100644 (file)
index 0000000..c1872b5
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2012, 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 __AS364X_H__
+#define __AS364X_H__
+
+#include <media/nvc_torch.h>
+
+enum {
+       AS3643,
+       AS3647,
+       AS3648,
+       AS364X_NUM,
+};
+
+struct as364x_config {
+       bool use_tx_mask; /* enable TXMASK */
+       u16 I_limit_mA; /* AS3647/AS3648: 2000, 2500, 3000, 3500 mA
+                        * AS3643: 1000, 1500, 2000, 2500 mA for the coil*/
+       u16 txmasked_current_mA; /* 57,113,...847, roughly 56.47 mA steps */
+       u16 vin_low_v_run_mV; /* 0xffff=off, 3000, 3070, 3140, 3220, 3300,
+                               3338, 3470 mV battery limit for dynamic flash
+                                reduction */
+       u16 vin_low_v_mV; /* 0xffff=off, 3000, 3070, 3140, 3220, 3300, 3338,
+                               3470mV battery limit for flash denial */
+       u8 strobe_type; /* 1=edge, 2=level */
+       u8 inct_pwm; /* pwm duty cycle for indicator or low current mode */
+       bool freq_switch_on;
+       /* balance the current sinks for unmatched LED forward valtages */
+       bool load_balance_on;
+       bool led_off_when_vin_low; /* if 0 txmask current is used */
+       bool boost_mode; /* all LED current are increased by 11% */
+       /* 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_peak_duration_ms; /* the maximum duration max_peak_current_mA
+                                    can be applied */
+       u16 max_sustained_current_mA; /* This leds maximum sustained current
+                                        in mA */
+       u16 min_current_mA; /* This leds minimum current in mA, desired
+                              values smaller than this will be realised
+                              using PWM. */
+};
+
+struct as364x_power_rail {
+       struct regulator *v_in;
+       struct regulator *v_i2c;
+};
+
+struct as364x_platform_data {
+       struct as364x_config config;
+       u32 type; /* flash device type, refer to as364x_type */
+       u32 led_mask; /* which led(s) enabled, 1/2/3 - left/right/both */
+       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 */
+       bool strobe_low_act; /* strobe state active low */
+
+       int (*power_on_callback)(struct as364x_power_rail *pw);
+       int (*power_off_callback)(struct as364x_power_rail *pw);
+};
+
+#endif
+/* __AS364X_H__ */