summaryrefslogtreecommitdiff
path: root/drivers/media/video
diff options
context:
space:
mode:
authorCharlie Huang <chahuang@nvidia.com>2012-09-07 13:43:16 -0700
committerSimone Willett <swillett@nvidia.com>2012-10-22 18:38:41 -0700
commitbe9018a97a792bc1b7d8be19777932812aa33972 (patch)
treea12e7d4f8eedab1a89d4301cfe516e155bde5c35 /drivers/media/video
parentfeee59a76e4796b9365a9a639e28bea2102b75a8 (diff)
media: tegra: as3648: initial driver bring up
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>
Diffstat (limited to 'drivers/media/video')
-rw-r--r--drivers/media/video/tegra/Kconfig6
-rw-r--r--drivers/media/video/tegra/Makefile1
-rw-r--r--drivers/media/video/tegra/as364x.c1411
3 files changed, 1418 insertions, 0 deletions
diff --git a/drivers/media/video/tegra/Kconfig b/drivers/media/video/tegra/Kconfig
index 845d459c10f1..9bde7622531b 100644
--- a/drivers/media/video/tegra/Kconfig
+++ b/drivers/media/video/tegra/Kconfig
@@ -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
diff --git a/drivers/media/video/tegra/Makefile b/drivers/media/video/tegra/Makefile
index 6bb14558838e..858931e2db11 100644
--- a/drivers/media/video/tegra/Makefile
+++ b/drivers/media/video/tegra/Makefile
@@ -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_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
index 000000000000..cd1100d63191
--- /dev/null
+++ b/drivers/media/video/tegra/as364x.c
@@ -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");