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