blob: e9b76e6dcabe5f25564db7f157ac8d2de86f12c4 [file] [log] [blame]
/*
* AS364X.c - AS364X flash/torch kernel driver
*
* Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved.
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <linux/edp.h>
#include <linux/regmap.h>
#include <media/nvc.h>
#include <media/as364x.h>
/* #define DEBUG_I2C_TRAFFIC */
#define AS364X_MAX_FLASH_LEVEL 256
#define AS364X_MAX_TORCH_LEVEL 128
#define AS364X_FLASH_TIMER_NUM 256
#define AS364X_TORCH_TIMER_NUM 1
#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_NONE 0x00
#define AS364X_REG_CONTROL_MODE_IND 0x01
#define AS364X_REG_CONTROL_MODE_TORCH 0x02
#define AS364X_REG_CONTROL_MODE_FLASH 0x03
#define AS364X_LEVEL_OFF 0
#define AS364X_TORCH_TIMER_FOREVER 0xFFFFFFFF
#define SUSTAINTIME_DEF 558
#define DEFAULT_FLASHTIME ((SUSTAINTIME_DEF > 256) ? \
((SUSTAINTIME_DEF - 249) / 8 + 128) : \
((SUSTAINTIME_DEF - 1) / 2))
#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 GET_CURRENT_BY_INDEX(i, c) \
((c) * (i)->dev_cap->curr_step_uA / 1000)
#define GET_INDEX_BY_CURRENT(i, c) \
((c) * 1000 / (i)->dev_cap->curr_step_uA)
#define as364x_flash_cap_size \
(sizeof(struct nvc_torch_flash_capabilities_v1) \
+ sizeof(struct nvc_torch_lumi_level_v1) \
* AS364X_MAX_FLASH_LEVEL)
#define as364x_flash_timeout_size \
(sizeof(struct nvc_torch_timer_capabilities_v1) \
+ sizeof(struct nvc_torch_timeout_v1) \
* AS364X_FLASH_TIMER_NUM)
#define as364x_max_flash_cap_size (as364x_flash_cap_size * 2 \
+ as364x_flash_timeout_size * 2)
#define as364x_torch_cap_size \
(sizeof(struct nvc_torch_torch_capabilities_v1) \
+ sizeof(struct nvc_torch_lumi_level_v1) \
* AS364X_MAX_TORCH_LEVEL)
#define as364x_torch_timeout_size \
(sizeof(struct nvc_torch_timer_capabilities_v1) \
+ sizeof(struct nvc_torch_timeout_v1) \
* AS364X_TORCH_TIMER_NUM)
#define as364x_max_torch_cap_size (as364x_torch_timeout_size * 2\
+ as364x_torch_timeout_size * 2)
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;
};
struct as364x_info {
struct i2c_client *i2c_client;
struct miscdevice miscdev;
struct device *dev;
struct dentry *d_as364x;
struct regmap *regmap;
struct list_head list;
struct mutex mutex;
struct regulator *v_in;
struct as364x_power_rail power;
struct as364x_platform_data *pdata;
const struct as364x_caps_struct *dev_cap;
struct nvc_torch_capability_query query;
struct nvc_torch_flash_capabilities_v1 *flash_cap[2];
struct nvc_torch_timer_capabilities_v1 *flash_timeouts[2];
struct nvc_torch_torch_capabilities_v1 *torch_cap[2];
struct nvc_torch_timer_capabilities_v1 *torch_timeouts[2];
struct as364x_config config;
struct as364x_reg_cache regs;
struct edp_client *edpc;
unsigned edp_state;
atomic_t in_use;
int flash_cap_size;
int torch_cap_size;
int pwr_state;
u8 max_flash[2];
u8 max_torch[2];
u8 s_mode;
u8 op_mode;
u8 led_num;
u8 led_mask;
u8 power_on;
};
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},
};
static const u16 v_in_low[] = {0, 3000, 3070, 3140, 3220, 3300, 3338, 3470};
/* flash timer duration settings in uS */
static u32 as364x_flash_timer[AS364X_FLASH_TIMER_NUM];
static u32 as364x_torch_timer[AS364X_TORCH_TIMER_NUM] = {
AS364X_TORCH_TIMER_FOREVER
};
static struct nvc_torch_lumi_level_v1
as364x_def_flash_levels[AS364X_MAX_FLASH_LEVEL - 1];
/* translated from the default register values after power up */
static const struct as364x_config default_cfg = {
.led_mask = 3,
.use_tx_mask = 0,
.I_limit_mA = 3000,
.txmasked_current_mA = 339,
.vin_low_v_run_mV = 0,
.vin_low_v_mV = 0,
.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,
.def_ftimer = 0x23,
.led_config[0] = {
.flash_torch_ratio = 10000,
.granularity = 1000,
.flash_levels = ARRAY_SIZE(as364x_def_flash_levels),
.lumi_levels = as364x_def_flash_levels,
},
.led_config[1] = {
.flash_torch_ratio = 10000,
.granularity = 1000,
.flash_levels = ARRAY_SIZE(as364x_def_flash_levels),
.lumi_levels = as364x_def_flash_levels,
},
};
static struct as364x_platform_data as364x_default_pdata = {
.cfg = 0,
.num = 0,
.dev_name = "torch",
.pinstate = {0x0000, 0x0000},
};
static const struct regmap_config as364x_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.cache_type = REGCACHE_NONE,
};
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 int as364x_debugfs_init(struct as364x_info *info);
static inline void as364x_i2c_dump(
struct as364x_info *info, u8 reg, u8 *buf, u8 num)
{
#ifdef DEBUG_I2C_TRAFFIC
static unsigned char i2c_buf[32 + 3 * 16];
int len = sprintf(i2c_buf, "%s %02x =", __func__, reg);
int i;
for (i = 0; i < num; i++)
len += sprintf(i2c_buf + len, " %02x", buf[i]);
i2c_buf[len] = 0;
dev_info(info->dev, "%s\n", i2c_buf);
#else
if (num == 1) {
dev_dbg(info->dev, "%s %02x = %02x\n", __func__, reg, *buf);
return;
}
dev_dbg(info->dev, "%s %02x = %02x %02x ...\n",
__func__, reg, buf[0], buf[1]);
#endif
}
static inline int as364x_reg_rd(struct as364x_info *info, u8 reg, u8 *val)
{
int err = -ENODEV;
mutex_lock(&info->mutex);
if (info->power_on)
err = regmap_raw_read(info->regmap, reg, val, sizeof(*val));
else
dev_err(info->dev, "%s: power is off.\n", __func__);
mutex_unlock(&info->mutex);
return err;
}
static int as364x_reg_raw_wr(struct as364x_info *info, u8 reg, u8 *buf, u8 num)
{
int err = -ENODEV;
as364x_i2c_dump(info, reg, buf, num);
mutex_lock(&info->mutex);
if (info->power_on)
err = regmap_raw_write(info->regmap, reg, buf, num);
else
dev_err(info->dev, "%s: power is off.\n", __func__);
mutex_unlock(&info->mutex);
return err;
}
static int as364x_reg_wr(struct as364x_info *info, u8 reg, u8 val)
{
int err = -ENODEV;
as364x_i2c_dump(info, reg, &val, 1);
mutex_lock(&info->mutex);
if (info->power_on)
err = regmap_write(info->regmap, reg, val);
else
dev_err(info->dev, "%s: power is off.\n", __func__);
mutex_unlock(&info->mutex);
return err;
}
static void as364x_edp_lowest(struct as364x_info *info)
{
if (!info->edpc)
return;
info->edp_state = info->edpc->num_states - 1;
dev_dbg(&info->i2c_client->dev, "%s %d\n", __func__, info->edp_state);
if (edp_update_client_request(info->edpc, info->edp_state, NULL)) {
dev_err(&info->i2c_client->dev, "THIS IS NOT LIKELY HAPPEN!\n");
dev_err(&info->i2c_client->dev,
"UNABLE TO SET LOWEST EDP STATE!\n");
}
}
static void as364x_throttle(unsigned int new_state, void *priv_data);
static void as364x_edp_register(struct as364x_info *info)
{
struct edp_manager *edp_manager;
struct edp_client *edpc = &info->pdata->edpc_config;
int ret;
info->edpc = NULL;
if (!edpc->num_states) {
dev_notice(&info->i2c_client->dev,
"%s: NO edp states defined.\n", __func__);
return;
}
strncpy(edpc->name, "as364x", EDP_NAME_LEN - 1);
edpc->name[EDP_NAME_LEN - 1] = 0;
edpc->throttle = as364x_throttle;
edpc->private_data = info;
dev_dbg(&info->i2c_client->dev, "%s: %s, e0 = %d, p %d\n",
__func__, edpc->name, edpc->e0_index, edpc->priority);
for (ret = 0; ret < edpc->num_states; ret++)
dev_dbg(&info->i2c_client->dev, "e%d = %d mA\n",
ret - edpc->e0_index, edpc->states[ret]);
edp_manager = edp_get_manager("battery");
if (!edp_manager) {
dev_err(&info->i2c_client->dev,
"unable to get edp manager: battery\n");
return;
}
ret = edp_register_client(edp_manager, edpc);
if (ret) {
dev_err(&info->i2c_client->dev,
"unable to register edp client\n");
return;
}
info->edpc = edpc;
/* set to lowest state at init */
as364x_edp_lowest(info);
}
static int as364x_edp_req(struct as364x_info *info,
u8 mask, u8 *curr1, u8 *curr2)
{
unsigned *estates;
unsigned total_curr = 0;
unsigned curr_mA;
unsigned approved;
unsigned new_state;
int ret = 0;
if (!info->edpc)
return 0;
dev_dbg(&info->i2c_client->dev, "%s: %d curr1 = %02x curr2 = %02x\n",
__func__, mask, *curr1, *curr2);
estates = info->edpc->states;
if (mask & 1)
total_curr += *curr1;
if (mask & 2)
total_curr += *curr2;
curr_mA = GET_CURRENT_BY_INDEX(info, total_curr);
for (new_state = info->edpc->num_states - 1; new_state > 0; new_state--)
if (estates[new_state] >= curr_mA)
break;
dev_dbg(&info->i2c_client->dev,
"edp req: %d curr = %d mA\n", new_state, curr_mA);
ret = edp_update_client_request(info->edpc, new_state, &approved);
if (ret) {
dev_err(&info->i2c_client->dev, "E state transition failed\n");
return ret;
}
if (approved > new_state) { /* edp manager returned less current */
curr_mA = GET_INDEX_BY_CURRENT(info, estates[approved]);
if (mask & 1)
*curr1 = curr_mA * (*curr1) / total_curr;
*curr2 = curr_mA - (*curr1);
dev_dbg(&info->i2c_client->dev,
"new state: %d curr = %d mA (%d %d)\n",
approved, curr_mA, *curr1, *curr2);
}
info->edp_state = approved;
return 0;
}
static int as364x_set_leds(struct as364x_info *info,
u8 mask, u8 curr1, u8 curr2)
{
int err = 0;
u8 regs[6];
if (mask & 1) {
if (info->op_mode == AS364X_REG_CONTROL_MODE_FLASH) {
if (curr1 >= info->max_flash[0])
curr1 = info->max_flash[0];
} else {
if (curr1 >= info->max_torch[0])
curr1 = info->max_torch[0];
}
} else
curr1 = 0;
if (mask & 2 && info->dev_cap->led2_support) {
if (info->op_mode == AS364X_REG_CONTROL_MODE_FLASH) {
if (curr2 >= info->max_flash[1])
curr2 = info->max_flash[1];
} else {
if (curr2 >= info->max_torch[1])
curr2 = info->max_torch[1];
}
} else
curr2 = 0;
err = as364x_edp_req(info, mask, &curr1, &curr2);
if (err)
return err;
regs[0] = curr1;
regs[1] = curr2;
regs[2] = info->regs.txmask;
regs[3] = info->regs.vlow;
regs[4] = info->regs.ftime;
if (mask == 0 || (curr1 == 0 && curr2 == 0))
regs[5] = info->op_mode & (~0x08);
else
regs[5] = info->op_mode | 0x08;
err = as364x_reg_raw_wr(
info, AS364X_REG_LED1_SET_CURR, regs, sizeof(regs));
if (!err) {
info->regs.led1_curr = curr1;
info->regs.led2_curr = curr2;
if ((curr1 | curr2) == 0)
as364x_edp_lowest(info);
}
dev_dbg(info->dev, "%s %x %x %x %x control = %x\n",
__func__, mask, curr1, curr2,
info->regs.ftime, regs[5]);
return err;
}
static int as364x_set_txmask(struct as364x_info *info)
{
const struct as364x_caps_struct *p_cap = info->dev_cap;
struct as364x_config *p_cfg = &info->config;
int err;
u8 tm;
u32 limit = 0, txmask;
dev_dbg(info->dev, "%s\n", __func__);
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_config_init(struct as364x_info *info)
{
struct as364x_config *pcfg = &info->config;
struct as364x_config *pcfg_cust = &info->pdata->config;
unsigned i;
dev_dbg(info->dev, "%s +++\n", __func__);
dev_dbg(info->dev, "as364x_def_flash_levels:\n");
for (i = 0; i < ARRAY_SIZE(as364x_def_flash_levels); i++) {
as364x_def_flash_levels[i].guidenum = i + 1;
as364x_def_flash_levels[i].luminance =
(i + 1) * info->dev_cap->curr_step_uA;
dev_dbg(info->dev, "0x%02x - %d\n",
i, as364x_def_flash_levels[i].luminance);
}
dev_dbg(info->dev, "as364x_flash_timer:\n");
/* 0 ~ 0x7f, 2 ms step / 0x80 ~ 0xff, 8 ms step*/
for (i = 0; i < 0x80; i++) {
as364x_flash_timer[i] = (i + 1) * 2000;
dev_dbg(info->dev,
"0x%02x - %06d\n", i, as364x_flash_timer[i]);
}
for (; i < ARRAY_SIZE(as364x_flash_timer); i++) {
as364x_flash_timer[i] = 256000 + (i - 0x7f) * 8000;
dev_dbg(info->dev,
"0x%02x - %06d\n", i, as364x_flash_timer[i]);
}
memcpy(pcfg, &default_cfg, sizeof(*pcfg));
if (!info->pdata) {
info->pdata = &as364x_default_pdata;
dev_dbg(info->dev, "%s No platform data. Using defaults.\n",
__func__);
goto config_init_done;
}
pcfg_cust = &info->pdata->config;
pcfg->synchronized_led = pcfg_cust->synchronized_led;
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->led_mask)
pcfg->led_mask = pcfg_cust->led_mask;
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_torch_current_mA)
pcfg->max_torch_current_mA = pcfg_cust->max_torch_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;
for (i = 0; i < 2; i++) {
if (pcfg_cust->led_config[i].flash_levels &&
pcfg_cust->led_config[i].flash_torch_ratio &&
pcfg_cust->led_config[i].granularity &&
pcfg_cust->led_config[i].lumi_levels)
memcpy(&pcfg->led_config[i], &pcfg_cust->led_config[i],
sizeof(pcfg_cust->led_config[0]));
else
dev_notice(info->dev, "%s: led config[%d]."
"Using default values\n", __func__, i);
}
config_init_done:
dev_dbg(info->dev, "%s ---\n", __func__);
}
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->dev_cap->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);
dev_dbg(info->dev, "UP: strobe: %x pwm_ind: %x vlow: %x\n",
info->regs.strobe, info->regs.pwm_ind, info->regs.vlow);
return err;
}
static int as364x_configure(struct as364x_info *info, bool update)
{
struct as364x_config *pcfg = &info->config;
const struct as364x_caps_struct *pcap = info->dev_cap;
struct nvc_torch_capability_query *pqry = &info->query;
struct nvc_torch_flash_capabilities_v1 *pfcap = NULL;
struct nvc_torch_torch_capabilities_v1 *ptcap = NULL;
struct nvc_torch_timer_capabilities_v1 *ptmcap = NULL;
struct nvc_torch_lumi_level_v1 *plvls = NULL;
int val;
int i;
int j;
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;
switch (pcfg->strobe_type) {
case 1:
info->regs.strobe = 0x80;
break;
case 2:
info->regs.strobe = 0xc0;
break;
case 3:
default:
info->regs.strobe = 0x00;
break;
}
info->led_mask = pcfg->led_mask;
info->regs.ftime = DEFAULT_FLASHTIME;
if (pcfg->max_peak_current_mA > pcap->max_peak_curr_mA ||
!pcfg->max_peak_current_mA) {
dev_notice(info->dev,
"max_peak_current_mA of %d invalid 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 (!pcfg->synchronized_led && pcap->led2_support &&
(info->led_mask & 3) == 3)
info->led_num = 2;
pqry->version = NVC_TORCH_CAPABILITY_VER_1;
pqry->flash_num = info->led_num;
pqry->torch_num = info->led_num;
pqry->led_attr = 0;
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_notice(info->dev,
"max_sustained_current_mA is %d "
"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_notice(info->dev,
"min_current_mA lower than possible, increasing to %d\n",
pcfg->min_current_mA);
}
if (pcfg->min_current_mA > pcap->max_indicator_curr_mA) {
dev_notice(info->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;
}
for (i = 0; i < pqry->flash_num; i++) {
pfcap = info->flash_cap[i];
pfcap->version = NVC_TORCH_CAPABILITY_VER_1;
pfcap->led_idx = i;
pfcap->attribute = 0;
pfcap->granularity = pcfg->led_config[i].granularity;
pfcap->timeout_num = ARRAY_SIZE(as364x_flash_timer);
ptmcap = info->flash_timeouts[i];
pfcap->timeout_off = (void *)ptmcap - (void *)pfcap;
pfcap->flash_torch_ratio =
pcfg->led_config[i].flash_torch_ratio;
plvls = pcfg->led_config[i].lumi_levels;
pfcap->levels[0].guidenum = AS364X_LEVEL_OFF;
pfcap->levels[0].luminance = 0;
for (j = 1; j < pcfg->led_config[i].flash_levels + 1; j++) {
if (GET_CURRENT_BY_INDEX(info, plvls[j - 1].guidenum) >
pcfg->max_peak_current_mA)
break;
pfcap->levels[j].guidenum = plvls[j - 1].guidenum;
pfcap->levels[j].luminance = plvls[j - 1].luminance;
info->max_flash[i] = plvls[j - 1].guidenum;
dev_dbg(info->dev, "%03d - %d\n",
pfcap->levels[j].guidenum,
pfcap->levels[j].luminance);
}
pfcap->numberoflevels = j;
dev_dbg(info->dev,
"%s flash#%d, attr: %x, levels: %d, g: %d, ratio: %d\n",
__func__, pfcap->led_idx, pfcap->attribute,
pfcap->numberoflevels, pfcap->granularity,
pfcap->flash_torch_ratio);
ptmcap->timeout_num = pfcap->timeout_num;
for (j = 0; j < ptmcap->timeout_num; j++) {
ptmcap->timeouts[j].timeout = as364x_flash_timer[j];
dev_dbg(info->dev, "t: %03d - %d uS\n", j,
ptmcap->timeouts[j].timeout);
}
}
for (i = 0; i < pqry->torch_num; i++) {
ptcap = info->torch_cap[i];
ptcap->version = NVC_TORCH_CAPABILITY_VER_1;
ptcap->led_idx = i;
ptcap->attribute = 0;
ptcap->granularity = pcfg->led_config[i].granularity;
ptcap->timeout_num = ARRAY_SIZE(as364x_torch_timer);
ptmcap = info->torch_timeouts[i];
ptcap->timeout_off = (void *)ptmcap - (void *)ptcap;
plvls = pcfg->led_config[i].lumi_levels;
ptcap->levels[0].guidenum = AS364X_LEVEL_OFF;
ptcap->levels[0].luminance = 0;
for (j = 1; j < pcfg->led_config[i].flash_levels + 1; j++) {
if (GET_CURRENT_BY_INDEX(info, plvls[j - 1].guidenum) >
pcfg->max_torch_current_mA)
break;
ptcap->levels[j].guidenum = plvls[j - 1].guidenum;
ptcap->levels[j].luminance = plvls[j - 1].luminance;
info->max_torch[i] = plvls[j - 1].guidenum;
dev_dbg(info->dev, "%03d - %d\n",
ptcap->levels[j].guidenum,
ptcap->levels[j].luminance);
}
ptcap->numberoflevels = j;
if (ptcap->numberoflevels > AS364X_MAX_TORCH_LEVEL)
ptcap->numberoflevels = AS364X_MAX_TORCH_LEVEL;
dev_dbg(info->dev, "torch#%d, attr: %x, levels: %d, g: %d\n",
ptcap->led_idx, ptcap->attribute,
ptcap->numberoflevels, ptcap->granularity);
ptmcap->timeout_num = ptcap->timeout_num;
for (j = 0; j < ptmcap->timeout_num; j++) {
ptmcap->timeouts[j].timeout = as364x_torch_timer[j];
dev_dbg(info->dev, "t: %03d - %d uS\n", j,
ptmcap->timeouts[j].timeout);
}
}
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->dev_cap->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->dev_cap->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->dev_cap->name);
as364x_set_leds(info, 3, 0, 0);
}
#endif
static int as364x_power_on(struct as364x_info *info)
{
struct as364x_power_rail *power = &info->power;
int err = 0;
if (info->power_on)
return 0;
mutex_lock(&info->mutex);
if (power->v_in) {
err = regulator_enable(power->v_in);
if (err) {
dev_err(info->dev, "%s v_in err\n", __func__);
goto power_on_end;
}
}
if (power->v_i2c) {
err = regulator_enable(power->v_i2c);
if (err) {
dev_err(info->dev, "%s v_i2c err\n", __func__);
if (power->v_in)
regulator_disable(power->v_in);
goto power_on_end;
}
}
if (info->pdata && info->pdata->power_on_callback)
err = info->pdata->power_on_callback(&info->power);
if (!err)
info->power_on = 1;
as364x_edp_lowest(info);
power_on_end:
mutex_unlock(&info->mutex);
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->power_on)
return 0;
mutex_lock(&info->mutex);
if (info->pdata && info->pdata->power_off_callback)
err = info->pdata->power_off_callback(&info->power);
if (IS_ERR_VALUE(err))
goto power_off_end;
if (power->v_in) {
err = regulator_disable(power->v_in);
if (err) {
dev_err(info->dev, "%s vi_in err\n", __func__);
goto power_off_end;
}
}
if (power->v_i2c) {
err = regulator_disable(power->v_i2c);
if (err)
dev_err(info->dev, "%s vi_i2c err\n", __func__);
}
if (!err)
info->power_on = 0;
as364x_edp_lowest(info);
power_off_end:
mutex_unlock(&info->mutex);
return err;
}
static int as364x_power(struct as364x_info *info, int pwr)
{
int err = 0;
dev_dbg(info->dev, "%s %d %d\n", __func__, pwr, info->pwr_state);
if (pwr == info->pwr_state) /* power state no change */
return 0;
switch (pwr) {
case NVC_PWR_OFF:
err = as364x_set_leds(info, 3, 0, 0);
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:
err = as364x_set_leds(info, 3, 0, 0);
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->dev, "%s error\n", __func__);
pwr = NVC_PWR_ERR;
}
info->pwr_state = pwr;
if (err > 0)
return 0;
return err;
}
static void as364x_throttle(unsigned int new_state, void *priv_data)
{
struct as364x_info *info = priv_data;
if (!info)
return;
as364x_power(info, NVC_PWR_OFF);
}
static int as364x_get_dev_id(struct as364x_info *info)
{
int err;
dev_dbg(info->dev, "%s %02x\n", __func__, info->regs.dev_id);
/* ChipID[7:3] is a fixed identification B0 */
if ((info->regs.dev_id & 0xb0) == 0xb0)
return 0;
if (NVC_PWR_OFF == info->pwr_state ||
NVC_PWR_OFF_FORCE == 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;
int err = 0;
u8 reg;
if (copy_from_user(&params,
(const void __user *)arg,
sizeof(struct nvc_param))) {
dev_err(info->dev,
"%s %d copy_from_user err\n", __func__, __LINE__);
return -EINVAL;
}
switch (params.param) {
case NVC_PARAM_TORCH_QUERY:
dev_dbg(info->dev, "%s QUERY\n", __func__);
data_ptr = &info->query;
data_size = sizeof(info->query);
break;
case NVC_PARAM_FLASH_EXT_CAPS:
dev_dbg(info->dev, "%s EXT_FLASH_CAPS %d\n",
__func__, params.variant);
if (params.variant >= info->query.flash_num) {
dev_err(info->dev, "%s unsupported flash index.\n",
__func__);
err = -EINVAL;
break;
}
data_ptr = info->flash_cap[params.variant];
data_size = info->flash_cap_size;
break;
case NVC_PARAM_TORCH_EXT_CAPS:
dev_dbg(info->dev, "%s EXT_TORCH_CAPS %d\n",
__func__, params.variant);
if (params.variant >= info->query.torch_num) {
dev_err(info->dev, "%s unsupported torch index.\n",
__func__);
err = -EINVAL;
break;
}
data_ptr = info->torch_cap[params.variant];
data_size = info->torch_cap_size;
break;
case NVC_PARAM_FLASH_LEVEL:
if (params.variant >= info->query.flash_num) {
dev_err(info->dev,
"%s unsupported flash index.\n", __func__);
err = -EINVAL;
break;
}
if (info->op_mode != AS364X_REG_CONTROL_MODE_FLASH)
reg = 0;
else if (params.variant > 0)
reg = info->regs.led2_curr;
else
reg = info->regs.led1_curr;
data_ptr = &reg;
data_size = sizeof(reg);
dev_dbg(info->dev, "%s FLASH_LEVEL %d\n", __func__, reg);
break;
case NVC_PARAM_TORCH_LEVEL:
if (params.variant >= info->query.torch_num) {
dev_err(info->dev, "%s unsupported torch index.\n",
__func__);
err = -EINVAL;
break;
}
if (info->op_mode != AS364X_REG_CONTROL_MODE_TORCH)
reg = 0;
else if (params.variant > 0)
reg = info->regs.led2_curr;
else
reg = info->regs.led1_curr;
data_ptr = &reg;
data_size = sizeof(reg);
dev_dbg(info->dev, "%s TORCH_LEVEL %d\n", __func__, reg);
break;
case NVC_PARAM_FLASH_PIN_STATE:
/* By default use Active Pin State Setting */
pinstate = info->pdata->pinstate;
if ((info->op_mode != AS364X_REG_CONTROL_MODE_FLASH) ||
(!info->regs.led1_curr && !info->regs.led2_curr))
pinstate.values ^= 0xffff; /* Inactive Pin Setting */
dev_dbg(info->dev, "%s FLASH_PIN_STATE: %x&%x\n",
__func__, pinstate.mask, pinstate.values);
data_ptr = &pinstate;
data_size = sizeof(pinstate);
break;
default:
dev_err(info->dev, "%s unsupported parameter: %d\n",
__func__, params.param);
err = -EINVAL;
}
if (!err && params.sizeofvalue < data_size) {
dev_err(info->dev, "%s data size mismatch %d != %d\n",
__func__, params.sizeofvalue, data_size);
err = -EINVAL;
}
if (!err && copy_to_user((void __user *)params.p_value,
data_ptr, data_size)) {
dev_err(info->dev, "%s copy_to_user err line %d\n",
__func__, __LINE__);
err = -EFAULT;
}
return err;
}
static int as364x_get_levels(struct as364x_info *info,
struct nvc_param *params,
bool flash_mode,
struct nvc_torch_set_level_v1 *plevels)
{
struct nvc_torch_timer_capabilities_v1 *p_tm;
u8 op_mode;
if (copy_from_user(plevels, (const void __user *)params->p_value,
sizeof(*plevels))) {
dev_err(info->dev, "%s %d copy_from_user err\n",
__func__, __LINE__);
return -EINVAL;
}
if (flash_mode) {
dev_dbg(info->dev, "%s FLASH_LEVEL: %d %d %d\n",
__func__, plevels->ledmask,
plevels->levels[0], plevels->levels[1]);
p_tm = info->flash_timeouts[0];
op_mode = AS364X_REG_CONTROL_MODE_FLASH;
} else {
dev_dbg(info->dev, "%s TORCH_LEVEL: %d %d %d\n",
__func__, plevels->ledmask,
plevels->levels[0], plevels->levels[1]);
p_tm = info->torch_timeouts[0];
op_mode = AS364X_REG_CONTROL_MODE_TORCH;
}
if (plevels->timeout) {
u16 i;
for (i = 0; i < p_tm->timeout_num; i++) {
plevels->timeout = i;
if (plevels->timeout == p_tm->timeouts[i].timeout)
break;
}
} else
plevels->timeout = p_tm->timeout_num - 1;
if (plevels->levels[0] == AS364X_LEVEL_OFF)
plevels->ledmask &= ~1;
if (plevels->levels[1] == AS364X_LEVEL_OFF)
plevels->ledmask &= ~2;
plevels->ledmask &= info->config.led_mask;
if (!plevels->ledmask)
info->op_mode = AS364X_REG_CONTROL_MODE_NONE;
else {
info->op_mode = op_mode;
if (info->config.synchronized_led) {
plevels->ledmask = 3;
plevels->levels[1] = plevels->levels[0];
}
}
dev_dbg(info->dev, "Return: %d - %d %d %d\n", info->op_mode,
plevels->ledmask, plevels->levels[0], plevels->levels[1]);
return 0;
}
static int as364x_user_set_param(struct as364x_info *info, long arg)
{
struct nvc_param params;
struct nvc_torch_set_level_v1 led_levels;
int err = 0;
u8 val;
if (copy_from_user(
&params, (const void __user *)arg, sizeof(struct nvc_param))) {
dev_err(info->dev,
"%s %d copy_from_user err\n", __func__, __LINE__);
return -EINVAL;
}
switch (params.param) {
case NVC_PARAM_FLASH_LEVEL:
as364x_get_levels(info, &params, true, &led_levels);
if (led_levels.timeout == 0)
info->regs.ftime = info->config.def_ftimer;
else
info->regs.ftime = led_levels.timeout;
err = as364x_set_leds(info, info->led_mask,
led_levels.levels[0], led_levels.levels[1]);
break;
case NVC_PARAM_TORCH_LEVEL:
as364x_get_levels(info, &params, false, &led_levels);
info->regs.ftime = led_levels.timeout;
err = as364x_set_leds(info, info->led_mask,
led_levels.levels[0], led_levels.levels[1]);
break;
case NVC_PARAM_FLASH_PIN_STATE:
if (copy_from_user(&val, (const void __user *)params.p_value,
sizeof(val))) {
dev_err(info->dev, "%s %d copy_from_user err\n",
__func__, __LINE__);
err = -EINVAL;
break;
}
dev_dbg(info->dev, "%s FLASH_PIN_STATE: %d\n", __func__, val);
err = as364x_strobe(info, val);
break;
default:
dev_err(info->dev, "%s unsupported parameter: %d\n",
__func__, params.param);
err = -EINVAL;
break;
}
return err;
}
static long as364x_ioctl(struct file *file,
unsigned int cmd,
unsigned long arg)
{
struct as364x_info *info = file->private_data;
int pwr;
int err = 0;
switch (cmd) {
case NVC_IOCTL_PARAM_WR:
err = as364x_user_set_param(info, arg);
break;
case NVC_IOCTL_PARAM_RD:
err = as364x_user_get_param(info, arg);
break;
case NVC_IOCTL_PWR_WR:
/* This is a Guaranteed Level of Service (GLOS) call */
pwr = (int)arg * 2;
dev_dbg(info->dev, "%s PWR_WR: %d\n", __func__, pwr);
if (!pwr || (pwr > NVC_PWR_ON)) /* Invalid Power State */
break;
err = as364x_power(info, pwr);
if (info->pdata->cfg & NVC_CFG_NOERR)
err = 0;
break;
case NVC_IOCTL_PWR_RD:
pwr = info->pwr_state / 2;
dev_dbg(info->dev, "%s PWR_RD: %d\n", __func__, pwr);
if (copy_to_user((void __user *)arg, (const void *)&pwr,
sizeof(pwr))) {
dev_err(info->dev, "%s copy_to_user err line %d\n",
__func__, __LINE__);
err = -EFAULT;
}
break;
default:
dev_err(info->dev, "%s unsupported ioctl: %x\n", __func__, cmd);
err = -EINVAL;
break;
}
return err;
}
static int as364x_open(struct inode *inode, struct file *file)
{
struct as364x_info *info = NULL;
struct as364x_info *pos = NULL;
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;
if (atomic_xchg(&info->in_use, 1))
return -EBUSY;
file->private_data = info;
dev_dbg(info->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->dev, "%s\n", __func__);
as364x_power(info, NVC_PWR_OFF);
file->private_data = NULL;
WARN_ON(!atomic_xchg(&info->in_use, 0));
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->dev, vreg_name);
if (unlikely(IS_ERR_OR_NULL(reg))) {
dev_err(info->dev,
"%s %s ERR: %d\n", __func__, vreg_name, (int)reg);
err = PTR_ERR(reg);
reg = NULL;
} else
dev_dbg(info->dev, "%s: %s\n", __func__, vreg_name);
*vreg = reg;
return err;
}
static int 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 */
info->pwr_state = NVC_PWR_OFF;
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(info, NVC_PWR_OFF);
as364x_power_put(&info->power);
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->dev, "%s\n", __func__);
misc_deregister(&info->miscdev);
as364x_del(info);
return 0;
}
static void as364x_caps_layout(struct as364x_info *info)
{
#define AS364X_FLASH_CAP_TIMEOUT_SIZE \
(as364x_flash_cap_size + as364x_flash_timeout_size)
#define AS364X_TORCH_CAP_TIMEOUT_SIZE \
(as364x_torch_cap_size + as364x_torch_timeout_size)
void *start_ptr;
int i;
start_ptr = (void *)info + sizeof(*info);
for (i = 0; i < 2; i++) {
info->flash_cap[i] = start_ptr;
info->flash_timeouts[i] = start_ptr + as364x_flash_cap_size;
start_ptr += AS364X_FLASH_CAP_TIMEOUT_SIZE;
}
info->flash_cap_size = AS364X_FLASH_CAP_TIMEOUT_SIZE;
for (i = 0; i < 2; i++) {
info->torch_cap[i] = start_ptr;
info->torch_timeouts[i] = start_ptr + as364x_torch_cap_size;
start_ptr += AS364X_TORCH_CAP_TIMEOUT_SIZE;
}
info->torch_cap_size = AS364X_TORCH_CAP_TIMEOUT_SIZE;
dev_dbg(info->dev, "%s: %d(%d + %d), %d(%d + %d)\n", __func__,
info->flash_cap_size, as364x_flash_cap_size,
as364x_flash_timeout_size, info->torch_cap_size,
as364x_torch_cap_size, as364x_torch_timeout_size);
}
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->regmap = devm_regmap_init_i2c(client, &as364x_regmap_config);
if (IS_ERR(info->regmap)) {
dev_err(&client->dev,
"regmap init failed: %ld\n", PTR_ERR(info->regmap));
return -ENODEV;
}
info->i2c_client = client;
info->dev = &client->dev;
if (client->dev.platform_data)
info->pdata = client->dev.platform_data;
else {
info->pdata = &as364x_default_pdata;
dev_dbg(&client->dev,
"%s No platform data. Using defaults.\n",
__func__);
}
as364x_caps_layout(info);
info->dev_cap = &as364x_caps[info->pdata->type];
as364x_config_init(info);
info->op_mode = AS364X_REG_CONTROL_MODE_TORCH; /* 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);
as364x_edp_register(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;
struct as364x_config *pcfg = &k_info->config;
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"
" Flash Strobe = 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->op_mode, k_info->regs.ftime,
pcfg->strobe_type,
pcfg->max_peak_current_mA,
pcfg->use_tx_mask,
pcfg->txmasked_current_mA,
pcfg->freq_switch_on ? "TRUE" : "FALSE",
pcfg->vin_low_v_run_mV,
pcfg->vin_low_v_mV,
pcfg->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]) {
/* enable/disable power */
case 'p':
if (val & 0xffff)
as364x_power(k_info, NVC_PWR_ON);
else
as364x_power(k_info, NVC_PWR_OFF);
break;
/* enable/disable led 1/2 */
case 'l':
k_info->config.led_mask = val;
as364x_configure(k_info, false);
break;
/* change led 1/2 current settings */
case 'c':
as364x_set_leds(k_info, k_info->led_mask,
val & 0xff, (val >> 8) & 0xff);
break;
/* modify flash timeout reg */
case 'f':
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;
/* set led work mode/trigger mode */
case 'x':
if (val & 0xf)
k_info->config.strobe_type = (val & 0xf);
k_info->op_mode = (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;
k_info->dev_cap = &as364x_caps[k_info->pdata->type];
}
as364x_configure(k_info, true);
break;
/* change txmask/torch settings */
case 't':
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;
/* change voltage low settings */
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;
/* set max_peak_current_mA */
case 'k':
if (val & 0xffff)
k_info->config.max_peak_current_mA = val & 0xffff;
as364x_configure(k_info, true);
break;
/* change pinstate setting */
case 'm':
k_info->pdata->pinstate.mask = (val >> 16) & 0xffff;
k_info->pdata->pinstate.values = val & 0xffff;
break;
/* trigger an external flash/torch event */
case 'g':
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");