2016-03-01 02:34:54 -08:00
|
|
|
/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
|
|
* only version 2 as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/errno.h>
|
2016-07-14 10:04:50 -07:00
|
|
|
#include <linux/delay.h>
|
2016-03-01 02:34:54 -08:00
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/of.h>
|
2016-06-03 12:27:39 -07:00
|
|
|
#include <linux/of_irq.h>
|
2016-03-16 17:24:17 -07:00
|
|
|
#include <linux/of_gpio.h>
|
|
|
|
#include <linux/gpio.h>
|
2016-03-01 02:34:54 -08:00
|
|
|
#include <linux/regmap.h>
|
2016-07-07 18:09:08 -07:00
|
|
|
#include <linux/power_supply.h>
|
2016-03-01 02:34:54 -08:00
|
|
|
#include <linux/platform_device.h>
|
2016-06-03 12:27:39 -07:00
|
|
|
#include <linux/interrupt.h>
|
2016-06-02 11:59:58 -07:00
|
|
|
#include <linux/regulator/consumer.h>
|
2016-07-26 16:37:28 -07:00
|
|
|
#include <linux/leds-qpnp-flash.h>
|
2016-03-01 02:34:54 -08:00
|
|
|
#include <linux/leds-qpnp-flash-v2.h>
|
2016-11-14 18:43:40 -08:00
|
|
|
#include <linux/qpnp/qpnp-revid.h>
|
2016-07-26 16:37:28 -07:00
|
|
|
#include "leds.h"
|
2016-03-01 02:34:54 -08:00
|
|
|
|
2016-07-19 15:23:56 -07:00
|
|
|
#define FLASH_LED_REG_LED_STATUS1(base) (base + 0x08)
|
|
|
|
#define FLASH_LED_REG_LED_STATUS2(base) (base + 0x09)
|
2016-06-03 12:27:39 -07:00
|
|
|
#define FLASH_LED_REG_INT_RT_STS(base) (base + 0x10)
|
2016-03-01 02:34:54 -08:00
|
|
|
#define FLASH_LED_REG_SAFETY_TMR(base) (base + 0x40)
|
|
|
|
#define FLASH_LED_REG_TGR_CURRENT(base) (base + 0x43)
|
|
|
|
#define FLASH_LED_REG_MOD_CTRL(base) (base + 0x46)
|
|
|
|
#define FLASH_LED_REG_IRES(base) (base + 0x47)
|
2016-03-16 17:24:17 -07:00
|
|
|
#define FLASH_LED_REG_STROBE_CFG(base) (base + 0x48)
|
2016-03-01 02:34:54 -08:00
|
|
|
#define FLASH_LED_REG_STROBE_CTRL(base) (base + 0x49)
|
2016-03-16 17:24:17 -07:00
|
|
|
#define FLASH_LED_EN_LED_CTRL(base) (base + 0x4C)
|
2016-03-01 02:34:54 -08:00
|
|
|
#define FLASH_LED_REG_HDRM_PRGM(base) (base + 0x4D)
|
|
|
|
#define FLASH_LED_REG_HDRM_AUTO_MODE_CTRL(base) (base + 0x50)
|
2016-07-07 16:23:12 -07:00
|
|
|
#define FLASH_LED_REG_WARMUP_DELAY(base) (base + 0x51)
|
2016-03-01 02:34:54 -08:00
|
|
|
#define FLASH_LED_REG_ISC_DELAY(base) (base + 0x52)
|
2016-07-20 12:06:57 -07:00
|
|
|
#define FLASH_LED_REG_THERMAL_THRSH1(base) (base + 0x56)
|
|
|
|
#define FLASH_LED_REG_THERMAL_THRSH2(base) (base + 0x57)
|
|
|
|
#define FLASH_LED_REG_THERMAL_THRSH3(base) (base + 0x58)
|
2016-07-07 16:23:12 -07:00
|
|
|
#define FLASH_LED_REG_VPH_DROOP_THRESHOLD(base) (base + 0x61)
|
|
|
|
#define FLASH_LED_REG_VPH_DROOP_DEBOUNCE(base) (base + 0x62)
|
2016-08-31 14:16:45 -07:00
|
|
|
#define FLASH_LED_REG_ILED_GRT_THRSH(base) (base + 0x67)
|
2016-08-26 12:31:56 -07:00
|
|
|
#define FLASH_LED_REG_LED1N2_ICLAMP_LOW(base) (base + 0x68)
|
|
|
|
#define FLASH_LED_REG_LED1N2_ICLAMP_MID(base) (base + 0x69)
|
|
|
|
#define FLASH_LED_REG_LED3_ICLAMP_LOW(base) (base + 0x6A)
|
|
|
|
#define FLASH_LED_REG_LED3_ICLAMP_MID(base) (base + 0x6B)
|
2016-07-14 10:04:50 -07:00
|
|
|
#define FLASH_LED_REG_MITIGATION_SEL(base) (base + 0x6E)
|
|
|
|
#define FLASH_LED_REG_MITIGATION_SW(base) (base + 0x6F)
|
|
|
|
#define FLASH_LED_REG_LMH_LEVEL(base) (base + 0x70)
|
2016-07-07 16:23:12 -07:00
|
|
|
#define FLASH_LED_REG_CURRENT_DERATE_EN(base) (base + 0x76)
|
2016-03-01 02:34:54 -08:00
|
|
|
|
2016-06-16 13:04:05 -07:00
|
|
|
#define FLASH_LED_HDRM_MODE_PRGM_MASK GENMASK(7, 0)
|
|
|
|
#define FLASH_LED_HDRM_VOL_MASK GENMASK(7, 4)
|
|
|
|
#define FLASH_LED_CURRENT_MASK GENMASK(6, 0)
|
2016-03-16 17:24:17 -07:00
|
|
|
#define FLASH_LED_ENABLE_MASK GENMASK(2, 0)
|
2016-10-24 20:01:16 +05:30
|
|
|
#define FLASH_HW_STROBE_MASK GENMASK(2, 0)
|
2016-06-16 13:04:05 -07:00
|
|
|
#define FLASH_LED_SAFETY_TMR_MASK GENMASK(7, 0)
|
2016-06-03 12:27:39 -07:00
|
|
|
#define FLASH_LED_INT_RT_STS_MASK GENMASK(7, 0)
|
2016-07-07 16:23:12 -07:00
|
|
|
#define FLASH_LED_ISC_WARMUP_DELAY_MASK GENMASK(1, 0)
|
|
|
|
#define FLASH_LED_CURRENT_DERATE_EN_MASK GENMASK(2, 0)
|
|
|
|
#define FLASH_LED_VPH_DROOP_DEBOUNCE_MASK GENMASK(1, 0)
|
2016-08-31 14:53:56 -07:00
|
|
|
#define FLASH_LED_CHGR_MITIGATION_SEL_MASK GENMASK(5, 4)
|
2016-07-14 10:04:50 -07:00
|
|
|
#define FLASH_LED_LMH_MITIGATION_SEL_MASK GENMASK(1, 0)
|
2016-08-31 14:16:45 -07:00
|
|
|
#define FLASH_LED_ILED_GRT_THRSH_MASK GENMASK(5, 0)
|
2016-07-14 10:04:50 -07:00
|
|
|
#define FLASH_LED_LMH_LEVEL_MASK GENMASK(1, 0)
|
2016-07-07 16:23:12 -07:00
|
|
|
#define FLASH_LED_VPH_DROOP_HYSTERESIS_MASK GENMASK(5, 4)
|
|
|
|
#define FLASH_LED_VPH_DROOP_THRESHOLD_MASK GENMASK(2, 0)
|
2016-07-20 12:06:57 -07:00
|
|
|
#define FLASH_LED_THERMAL_THRSH_MASK GENMASK(2, 0)
|
|
|
|
#define FLASH_LED_THERMAL_OTST_MASK GENMASK(2, 0)
|
2016-06-16 13:04:05 -07:00
|
|
|
#define FLASH_LED_MOD_CTRL_MASK BIT(7)
|
2016-10-24 20:01:16 +05:30
|
|
|
#define FLASH_LED_HW_SW_STROBE_SEL_BIT BIT(2)
|
2016-07-19 15:23:56 -07:00
|
|
|
#define FLASH_LED_VPH_DROOP_FAULT_MASK BIT(4)
|
2016-07-14 10:04:50 -07:00
|
|
|
#define FLASH_LED_LMH_MITIGATION_EN_MASK BIT(0)
|
2016-08-31 14:53:56 -07:00
|
|
|
#define FLASH_LED_CHGR_MITIGATION_EN_MASK BIT(4)
|
2016-03-01 02:34:54 -08:00
|
|
|
|
2016-07-07 16:23:12 -07:00
|
|
|
#define VPH_DROOP_DEBOUNCE_US_TO_VAL(val_us) (val_us / 8)
|
|
|
|
#define VPH_DROOP_HYST_MV_TO_VAL(val_mv) (val_mv / 25)
|
|
|
|
#define VPH_DROOP_THRESH_MV_TO_VAL(val_mv) ((val_mv / 100) - 25)
|
2016-07-07 18:46:14 -07:00
|
|
|
#define VPH_DROOP_THRESH_VAL_TO_UV(val) ((val + 25) * 100000)
|
2016-08-31 14:16:45 -07:00
|
|
|
#define MITIGATION_THRSH_MA_TO_VAL(val_ma) (val_ma / 100)
|
2016-10-25 15:54:50 -07:00
|
|
|
#define CURRENT_MA_TO_REG_VAL(curr_ma, ires_ua) ((curr_ma * 1000) / ires_ua - 1)
|
2016-10-25 16:40:39 -07:00
|
|
|
#define SAFETY_TMR_TO_REG_VAL(duration_ms) ((duration_ms / 10) - 1)
|
2016-07-07 16:23:12 -07:00
|
|
|
|
|
|
|
#define FLASH_LED_ISC_WARMUP_DELAY_SHIFT 6
|
|
|
|
#define FLASH_LED_WARMUP_DELAY_DEFAULT 2
|
|
|
|
#define FLASH_LED_ISC_DELAY_DEFAULT 3
|
|
|
|
#define FLASH_LED_VPH_DROOP_DEBOUNCE_DEFAULT 2
|
|
|
|
#define FLASH_LED_VPH_DROOP_HYST_DEFAULT 2
|
|
|
|
#define FLASH_LED_VPH_DROOP_THRESH_DEFAULT 5
|
|
|
|
#define FLASH_LED_VPH_DROOP_DEBOUNCE_MAX 3
|
|
|
|
#define FLASH_LED_VPH_DROOP_HYST_MAX 3
|
|
|
|
#define FLASH_LED_VPH_DROOP_THRESH_MAX 7
|
2016-07-20 12:06:57 -07:00
|
|
|
#define FLASH_LED_THERMAL_THRSH_MIN 3
|
|
|
|
#define FLASH_LED_THERMAL_OTST_LEVELS 3
|
2016-07-07 18:46:14 -07:00
|
|
|
#define FLASH_LED_VLED_MAX_DEFAULT_UV 3500000
|
|
|
|
#define FLASH_LED_IBATT_OCP_THRESH_DEFAULT_UA 4500000
|
|
|
|
#define FLASH_LED_RPARA_DEFAULT_UOHM 0
|
2016-06-16 13:04:05 -07:00
|
|
|
#define FLASH_LED_SAFETY_TMR_ENABLE BIT(7)
|
2016-07-14 10:04:50 -07:00
|
|
|
#define FLASH_LED_LMH_LEVEL_DEFAULT 0
|
|
|
|
#define FLASH_LED_LMH_MITIGATION_ENABLE 1
|
|
|
|
#define FLASH_LED_LMH_MITIGATION_DISABLE 0
|
2016-08-31 14:53:56 -07:00
|
|
|
#define FLASH_LED_CHGR_MITIGATION_ENABLE BIT(4)
|
|
|
|
#define FLASH_LED_CHGR_MITIGATION_DISABLE 0
|
|
|
|
#define FLASH_LED_MITIGATION_SEL_DEFAULT 2
|
|
|
|
#define FLASH_LED_MITIGATION_SEL_MAX 2
|
|
|
|
#define FLASH_LED_CHGR_MITIGATION_SEL_SHIFT 4
|
2016-08-31 14:16:45 -07:00
|
|
|
#define FLASH_LED_MITIGATION_THRSH_DEFAULT 0xA
|
|
|
|
#define FLASH_LED_MITIGATION_THRSH_MAX 0x1F
|
2016-07-14 10:04:50 -07:00
|
|
|
#define FLASH_LED_LMH_OCV_THRESH_DEFAULT_UV 3700000
|
|
|
|
#define FLASH_LED_LMH_RBATT_THRESH_DEFAULT_UOHM 400000
|
2016-03-01 02:34:54 -08:00
|
|
|
#define FLASH_LED_IRES_BASE 3
|
|
|
|
#define FLASH_LED_IRES_DIVISOR 2500
|
|
|
|
#define FLASH_LED_IRES_MIN_UA 5000
|
|
|
|
#define FLASH_LED_IRES_DEFAULT_UA 12500
|
|
|
|
#define FLASH_LED_IRES_DEFAULT_VAL 0x00
|
|
|
|
#define FLASH_LED_HDRM_VOL_SHIFT 4
|
|
|
|
#define FLASH_LED_HDRM_VOL_DEFAULT_MV 0x80
|
|
|
|
#define FLASH_LED_HDRM_VOL_HI_LO_WIN_DEFAULT_MV 0x04
|
|
|
|
#define FLASH_LED_HDRM_VOL_BASE_MV 125
|
|
|
|
#define FLASH_LED_HDRM_VOL_STEP_MV 25
|
2016-03-16 17:24:17 -07:00
|
|
|
#define FLASH_LED_STROBE_CFG_DEFAULT 0x00
|
|
|
|
#define FLASH_LED_HW_STROBE_OPTION_1 0x00
|
|
|
|
#define FLASH_LED_HW_STROBE_OPTION_2 0x01
|
|
|
|
#define FLASH_LED_HW_STROBE_OPTION_3 0x02
|
|
|
|
#define FLASH_LED_ENABLE BIT(0)
|
2016-06-16 13:04:05 -07:00
|
|
|
#define FLASH_LED_MOD_ENABLE BIT(7)
|
2016-03-01 02:34:54 -08:00
|
|
|
#define FLASH_LED_DISABLE 0x00
|
|
|
|
#define FLASH_LED_SAFETY_TMR_DISABLED 0x13
|
2016-06-29 17:48:12 -07:00
|
|
|
#define FLASH_LED_MIN_CURRENT_MA 25
|
2016-07-07 18:46:14 -07:00
|
|
|
#define FLASH_LED_MAX_TOTAL_CURRENT_MA 3750
|
2016-06-29 17:48:12 -07:00
|
|
|
|
2016-06-07 14:22:33 -07:00
|
|
|
/* notifier call chain for flash-led irqs */
|
|
|
|
static ATOMIC_NOTIFIER_HEAD(irq_notifier_list);
|
|
|
|
|
2016-06-29 17:48:12 -07:00
|
|
|
enum flash_led_type {
|
|
|
|
FLASH_LED_TYPE_FLASH,
|
|
|
|
FLASH_LED_TYPE_TORCH,
|
|
|
|
};
|
2016-03-01 02:34:54 -08:00
|
|
|
|
2016-03-16 17:24:17 -07:00
|
|
|
enum {
|
|
|
|
LED1 = 0,
|
|
|
|
LED2,
|
|
|
|
LED3,
|
|
|
|
};
|
|
|
|
|
2016-06-14 16:13:23 -07:00
|
|
|
/*
|
|
|
|
* Configurations for each individual LED
|
|
|
|
*/
|
|
|
|
struct flash_node_data {
|
|
|
|
struct platform_device *pdev;
|
|
|
|
struct led_classdev cdev;
|
|
|
|
struct pinctrl *pinctrl;
|
|
|
|
struct pinctrl_state *gpio_state_active;
|
|
|
|
struct pinctrl_state *gpio_state_suspend;
|
|
|
|
struct pinctrl_state *hw_strobe_state_active;
|
|
|
|
struct pinctrl_state *hw_strobe_state_suspend;
|
|
|
|
int hw_strobe_gpio;
|
|
|
|
int ires_ua;
|
|
|
|
int max_current;
|
|
|
|
int current_ma;
|
|
|
|
u8 duration;
|
|
|
|
u8 id;
|
|
|
|
u8 type;
|
|
|
|
u8 ires;
|
|
|
|
u8 hdrm_val;
|
|
|
|
u8 current_reg_val;
|
|
|
|
u8 trigger;
|
|
|
|
bool led_on;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct flash_switch_data {
|
|
|
|
struct platform_device *pdev;
|
2016-10-24 20:22:19 -07:00
|
|
|
struct regulator *vreg;
|
2016-06-14 16:13:23 -07:00
|
|
|
struct led_classdev cdev;
|
|
|
|
int led_mask;
|
|
|
|
bool regulator_on;
|
|
|
|
bool enabled;
|
|
|
|
};
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
/*
|
|
|
|
* Flash LED configuration read from device tree
|
|
|
|
*/
|
|
|
|
struct flash_led_platform_data {
|
2016-11-14 18:43:40 -08:00
|
|
|
struct pmic_revid_data *pmic_rev_id;
|
|
|
|
int *thermal_derate_current;
|
|
|
|
int all_ramp_up_done_irq;
|
|
|
|
int all_ramp_down_done_irq;
|
|
|
|
int led_fault_irq;
|
|
|
|
int ibatt_ocp_threshold_ua;
|
|
|
|
int vled_max_uv;
|
|
|
|
int rpara_uohm;
|
|
|
|
int lmh_rbatt_threshold_uohm;
|
|
|
|
int lmh_ocv_threshold_uv;
|
|
|
|
u32 led1n2_iclamp_low_ma;
|
|
|
|
u32 led1n2_iclamp_mid_ma;
|
|
|
|
u32 led3_iclamp_low_ma;
|
|
|
|
u32 led3_iclamp_mid_ma;
|
|
|
|
u8 isc_delay;
|
|
|
|
u8 warmup_delay;
|
|
|
|
u8 current_derate_en_cfg;
|
|
|
|
u8 vph_droop_threshold;
|
|
|
|
u8 vph_droop_hysteresis;
|
|
|
|
u8 vph_droop_debounce;
|
|
|
|
u8 lmh_mitigation_sel;
|
|
|
|
u8 chgr_mitigation_sel;
|
|
|
|
u8 lmh_level;
|
|
|
|
u8 iled_thrsh_val;
|
|
|
|
u8 hw_strobe_option;
|
|
|
|
bool hdrm_auto_mode_en;
|
|
|
|
bool thermal_derate_en;
|
2016-03-01 02:34:54 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Flash LED data structure containing flash LED attributes
|
|
|
|
*/
|
|
|
|
struct qpnp_flash_led {
|
|
|
|
struct flash_led_platform_data *pdata;
|
|
|
|
struct platform_device *pdev;
|
|
|
|
struct regmap *regmap;
|
|
|
|
struct flash_node_data *fnode;
|
|
|
|
struct flash_switch_data *snode;
|
2016-07-07 18:09:08 -07:00
|
|
|
struct power_supply *bms_psy;
|
|
|
|
struct notifier_block nb;
|
2016-03-01 02:34:54 -08:00
|
|
|
spinlock_t lock;
|
2016-06-14 01:46:06 -07:00
|
|
|
int num_fnodes;
|
|
|
|
int num_snodes;
|
|
|
|
int enable;
|
2016-03-01 02:34:54 -08:00
|
|
|
u16 base;
|
2016-07-14 10:04:50 -07:00
|
|
|
bool trigger_lmh;
|
2016-08-31 14:53:56 -07:00
|
|
|
bool trigger_chgr;
|
2016-03-01 02:34:54 -08:00
|
|
|
};
|
|
|
|
|
2016-06-03 12:27:39 -07:00
|
|
|
static int
|
|
|
|
qpnp_flash_led_read(struct qpnp_flash_led *led, u16 addr, u8 *data)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
uint val;
|
|
|
|
|
|
|
|
rc = regmap_read(led->regmap, addr, &val);
|
|
|
|
if (rc < 0)
|
|
|
|
dev_err(&led->pdev->dev, "Unable to read from 0x%04X rc = %d\n",
|
|
|
|
addr, rc);
|
|
|
|
else
|
|
|
|
dev_dbg(&led->pdev->dev, "Read 0x%02X from addr 0x%04X\n",
|
|
|
|
val, addr);
|
|
|
|
|
|
|
|
*data = (u8)val;
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-07-20 12:06:57 -07:00
|
|
|
static int
|
|
|
|
qpnp_flash_led_masked_read(struct qpnp_flash_led *led, u16 addr, u8 mask,
|
|
|
|
u8 *val)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_read(led, addr, val);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
*val &= mask;
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
static int
|
|
|
|
qpnp_flash_led_masked_write(struct qpnp_flash_led *led, u16 addr, u8 mask,
|
2016-06-03 12:27:39 -07:00
|
|
|
u8 val)
|
2016-03-01 02:34:54 -08:00
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = regmap_update_bits(led->regmap, addr, mask, val);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0)
|
2016-06-03 12:27:39 -07:00
|
|
|
dev_err(&led->pdev->dev, "Unable to update bits from 0x%04X, rc = %d\n",
|
|
|
|
addr, rc);
|
2016-06-14 01:46:06 -07:00
|
|
|
else
|
2016-06-03 12:27:39 -07:00
|
|
|
dev_dbg(&led->pdev->dev, "Wrote 0x%02X to addr 0x%04X\n",
|
|
|
|
val, addr);
|
2016-03-01 02:34:54 -08:00
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum
|
|
|
|
led_brightness qpnp_flash_led_brightness_get(struct led_classdev *led_cdev)
|
|
|
|
{
|
|
|
|
return led_cdev->brightness;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int qpnp_flash_led_init_settings(struct qpnp_flash_led *led)
|
|
|
|
{
|
|
|
|
int rc, i, addr_offset;
|
|
|
|
u8 val = 0;
|
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
for (i = 0; i < led->num_fnodes; i++) {
|
2016-03-01 02:34:54 -08:00
|
|
|
addr_offset = led->fnode[i].id;
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_HDRM_PRGM(led->base + addr_offset),
|
|
|
|
FLASH_LED_HDRM_MODE_PRGM_MASK,
|
|
|
|
led->fnode[i].hdrm_val);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0)
|
2016-03-01 02:34:54 -08:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
val |= 0x1 << led->fnode[i].id;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_HDRM_AUTO_MODE_CTRL(led->base),
|
|
|
|
FLASH_LED_HDRM_MODE_PRGM_MASK, val);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0)
|
2016-03-01 02:34:54 -08:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_ISC_DELAY(led->base),
|
2016-07-07 16:23:12 -07:00
|
|
|
FLASH_LED_ISC_WARMUP_DELAY_MASK,
|
|
|
|
led->pdata->isc_delay);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_WARMUP_DELAY(led->base),
|
|
|
|
FLASH_LED_ISC_WARMUP_DELAY_MASK,
|
|
|
|
led->pdata->warmup_delay);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_CURRENT_DERATE_EN(led->base),
|
|
|
|
FLASH_LED_CURRENT_DERATE_EN_MASK,
|
|
|
|
led->pdata->current_derate_en_cfg);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_VPH_DROOP_DEBOUNCE(led->base),
|
|
|
|
FLASH_LED_VPH_DROOP_DEBOUNCE_MASK,
|
|
|
|
led->pdata->vph_droop_debounce);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_VPH_DROOP_THRESHOLD(led->base),
|
|
|
|
FLASH_LED_VPH_DROOP_THRESHOLD_MASK,
|
|
|
|
led->pdata->vph_droop_threshold);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_VPH_DROOP_THRESHOLD(led->base),
|
|
|
|
FLASH_LED_VPH_DROOP_HYSTERESIS_MASK,
|
|
|
|
led->pdata->vph_droop_hysteresis);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0)
|
2016-03-01 02:34:54 -08:00
|
|
|
return rc;
|
|
|
|
|
2016-07-14 10:04:50 -07:00
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_MITIGATION_SEL(led->base),
|
|
|
|
FLASH_LED_LMH_MITIGATION_SEL_MASK,
|
|
|
|
led->pdata->lmh_mitigation_sel);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
2016-08-31 14:53:56 -07:00
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_MITIGATION_SEL(led->base),
|
|
|
|
FLASH_LED_CHGR_MITIGATION_SEL_MASK,
|
|
|
|
led->pdata->chgr_mitigation_sel);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
2016-07-14 10:04:50 -07:00
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_LMH_LEVEL(led->base),
|
|
|
|
FLASH_LED_LMH_LEVEL_MASK,
|
|
|
|
led->pdata->lmh_level);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
2016-08-31 14:16:45 -07:00
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_ILED_GRT_THRSH(led->base),
|
|
|
|
FLASH_LED_ILED_GRT_THRSH_MASK,
|
|
|
|
led->pdata->iled_thrsh_val);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
2016-08-26 12:31:56 -07:00
|
|
|
if (led->pdata->led1n2_iclamp_low_ma) {
|
|
|
|
val = CURRENT_MA_TO_REG_VAL(led->pdata->led1n2_iclamp_low_ma,
|
|
|
|
led->fnode[0].ires_ua);
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_LED1N2_ICLAMP_LOW(led->base),
|
|
|
|
FLASH_LED_CURRENT_MASK, val);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (led->pdata->led1n2_iclamp_mid_ma) {
|
|
|
|
val = CURRENT_MA_TO_REG_VAL(led->pdata->led1n2_iclamp_mid_ma,
|
|
|
|
led->fnode[0].ires_ua);
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_LED1N2_ICLAMP_MID(led->base),
|
|
|
|
FLASH_LED_CURRENT_MASK, val);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (led->pdata->led3_iclamp_low_ma) {
|
|
|
|
val = CURRENT_MA_TO_REG_VAL(led->pdata->led3_iclamp_low_ma,
|
|
|
|
led->fnode[3].ires_ua);
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_LED3_ICLAMP_LOW(led->base),
|
|
|
|
FLASH_LED_CURRENT_MASK, val);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (led->pdata->led3_iclamp_mid_ma) {
|
|
|
|
val = CURRENT_MA_TO_REG_VAL(led->pdata->led3_iclamp_mid_ma,
|
|
|
|
led->fnode[3].ires_ua);
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_LED3_ICLAMP_MID(led->base),
|
|
|
|
FLASH_LED_CURRENT_MASK, val);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-03-16 17:24:17 -07:00
|
|
|
static int qpnp_flash_led_hw_strobe_enable(struct flash_node_data *fnode,
|
|
|
|
int hw_strobe_option, bool on)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the LED controlled by this fnode is not GPIO controlled
|
|
|
|
* for the given strobe_option, return.
|
|
|
|
*/
|
|
|
|
if (hw_strobe_option == FLASH_LED_HW_STROBE_OPTION_1)
|
|
|
|
return 0;
|
|
|
|
else if (hw_strobe_option == FLASH_LED_HW_STROBE_OPTION_2
|
|
|
|
&& fnode->id != LED3)
|
|
|
|
return 0;
|
|
|
|
else if (hw_strobe_option == FLASH_LED_HW_STROBE_OPTION_3
|
|
|
|
&& fnode->id == LED1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (gpio_is_valid(fnode->hw_strobe_gpio)) {
|
|
|
|
gpio_set_value(fnode->hw_strobe_gpio, on ? 1 : 0);
|
|
|
|
} else if (fnode->hw_strobe_state_active &&
|
|
|
|
fnode->hw_strobe_state_suspend) {
|
|
|
|
rc = pinctrl_select_state(fnode->pinctrl,
|
|
|
|
on ? fnode->hw_strobe_state_active :
|
|
|
|
fnode->hw_strobe_state_suspend);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0) {
|
2016-03-16 17:24:17 -07:00
|
|
|
dev_err(&fnode->pdev->dev,
|
|
|
|
"failed to change hw strobe pin state\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-04-05 23:36:54 -07:00
|
|
|
static int qpnp_flash_led_regulator_enable(struct qpnp_flash_led *led,
|
|
|
|
struct flash_switch_data *snode, bool on)
|
|
|
|
{
|
2016-10-24 20:22:19 -07:00
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
if (!snode || !snode->vreg)
|
|
|
|
return 0;
|
2016-06-02 11:59:58 -07:00
|
|
|
|
|
|
|
if (snode->regulator_on == on)
|
|
|
|
return 0;
|
|
|
|
|
2016-10-24 20:22:19 -07:00
|
|
|
if (on)
|
|
|
|
rc = regulator_enable(snode->vreg);
|
|
|
|
else
|
|
|
|
rc = regulator_disable(snode->vreg);
|
2016-06-02 11:59:58 -07:00
|
|
|
|
2016-10-24 20:22:19 -07:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "regulator_%s failed, rc=%d\n",
|
|
|
|
on ? "enable" : "disable", rc);
|
|
|
|
return rc;
|
2016-06-02 11:59:58 -07:00
|
|
|
}
|
|
|
|
|
2016-10-24 20:22:19 -07:00
|
|
|
snode->regulator_on = on ? true : false;
|
|
|
|
return 0;
|
2016-04-05 23:36:54 -07:00
|
|
|
}
|
|
|
|
|
2016-07-07 18:46:14 -07:00
|
|
|
static int get_property_from_fg(struct qpnp_flash_led *led,
|
|
|
|
enum power_supply_property prop, int *val)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
union power_supply_propval pval = {0, };
|
|
|
|
|
|
|
|
if (!led->bms_psy) {
|
|
|
|
dev_err(&led->pdev->dev, "no bms psy found\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = power_supply_get_property(led->bms_psy, prop, &pval);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"bms psy doesn't support reading prop %d rc = %d\n",
|
|
|
|
prop, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
*val = pval.intval;
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-08-03 16:56:31 -07:00
|
|
|
#define VOLTAGE_HDRM_DEFAULT_MV 350
|
|
|
|
static int qpnp_flash_led_get_voltage_headroom(struct qpnp_flash_led *led)
|
|
|
|
{
|
|
|
|
int i, voltage_hdrm_mv = 0, voltage_hdrm_max = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < led->num_fnodes; i++) {
|
|
|
|
if (led->fnode[i].led_on) {
|
|
|
|
if (led->fnode[i].id < 2) {
|
|
|
|
if (led->fnode[i].current_ma < 750)
|
|
|
|
voltage_hdrm_mv = 125;
|
|
|
|
else if (led->fnode[i].current_ma < 1000)
|
|
|
|
voltage_hdrm_mv = 175;
|
|
|
|
else if (led->fnode[i].current_ma < 1250)
|
|
|
|
voltage_hdrm_mv = 250;
|
|
|
|
else
|
|
|
|
voltage_hdrm_mv = 350;
|
|
|
|
} else {
|
|
|
|
if (led->fnode[i].current_ma < 375)
|
|
|
|
voltage_hdrm_mv = 125;
|
|
|
|
else if (led->fnode[i].current_ma < 500)
|
|
|
|
voltage_hdrm_mv = 175;
|
|
|
|
else if (led->fnode[i].current_ma < 625)
|
|
|
|
voltage_hdrm_mv = 250;
|
|
|
|
else
|
|
|
|
voltage_hdrm_mv = 350;
|
|
|
|
}
|
|
|
|
|
|
|
|
voltage_hdrm_max = max(voltage_hdrm_max,
|
|
|
|
voltage_hdrm_mv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!voltage_hdrm_max)
|
|
|
|
return VOLTAGE_HDRM_DEFAULT_MV;
|
|
|
|
|
|
|
|
return voltage_hdrm_max;
|
|
|
|
}
|
|
|
|
|
2016-07-07 18:46:14 -07:00
|
|
|
#define UCONV 1000000LL
|
|
|
|
#define MCONV 1000LL
|
|
|
|
#define FLASH_VDIP_MARGIN 50000
|
|
|
|
#define BOB_EFFICIENCY 900LL
|
|
|
|
#define VIN_FLASH_MIN_UV 3300000LL
|
|
|
|
static int qpnp_flash_led_calc_max_current(struct qpnp_flash_led *led)
|
|
|
|
{
|
2016-08-03 16:56:31 -07:00
|
|
|
int ocv_uv, rbatt_uohm, ibat_now, voltage_hdrm_mv, rc;
|
2016-07-07 18:46:14 -07:00
|
|
|
int64_t ibat_flash_ua, avail_flash_ua, avail_flash_power_fw;
|
|
|
|
int64_t ibat_safe_ua, vin_flash_uv, vph_flash_uv, vph_flash_vdip;
|
|
|
|
|
|
|
|
/* RESISTANCE = esr_uohm + rslow_uohm */
|
|
|
|
rc = get_property_from_fg(led, POWER_SUPPLY_PROP_RESISTANCE,
|
|
|
|
&rbatt_uohm);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "bms psy does not support resistance, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If no battery is connected, return max possible flash current */
|
|
|
|
if (!rbatt_uohm)
|
|
|
|
return FLASH_LED_MAX_TOTAL_CURRENT_MA;
|
|
|
|
|
|
|
|
rc = get_property_from_fg(led, POWER_SUPPLY_PROP_VOLTAGE_OCV, &ocv_uv);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "bms psy does not support OCV, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = get_property_from_fg(led, POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
|
|
&ibat_now);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "bms psy does not support current, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rbatt_uohm += led->pdata->rpara_uohm;
|
2016-08-03 16:56:31 -07:00
|
|
|
voltage_hdrm_mv = qpnp_flash_led_get_voltage_headroom(led);
|
2016-07-07 18:46:14 -07:00
|
|
|
vph_flash_vdip =
|
|
|
|
VPH_DROOP_THRESH_VAL_TO_UV(led->pdata->vph_droop_threshold)
|
|
|
|
+ FLASH_VDIP_MARGIN;
|
|
|
|
|
2016-07-14 10:04:50 -07:00
|
|
|
/* Check if LMH_MITIGATION needs to be triggered */
|
|
|
|
if (!led->trigger_lmh && (ocv_uv < led->pdata->lmh_ocv_threshold_uv ||
|
|
|
|
rbatt_uohm > led->pdata->lmh_rbatt_threshold_uohm)) {
|
|
|
|
led->trigger_lmh = true;
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_MITIGATION_SW(led->base),
|
|
|
|
FLASH_LED_LMH_MITIGATION_EN_MASK,
|
|
|
|
FLASH_LED_LMH_MITIGATION_ENABLE);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "trigger lmh mitigation failed, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Wait for LMH mitigation to take effect */
|
|
|
|
udelay(100);
|
|
|
|
|
|
|
|
return qpnp_flash_led_calc_max_current(led);
|
|
|
|
}
|
|
|
|
|
2016-07-07 18:46:14 -07:00
|
|
|
/*
|
|
|
|
* Calculate the maximum current that can pulled out of the battery
|
|
|
|
* before the battery voltage dips below a safe threshold.
|
|
|
|
*/
|
|
|
|
ibat_safe_ua = div_s64((ocv_uv - vph_flash_vdip) * UCONV,
|
|
|
|
rbatt_uohm);
|
|
|
|
|
|
|
|
if (ibat_safe_ua <= led->pdata->ibatt_ocp_threshold_ua) {
|
|
|
|
/*
|
|
|
|
* If the calculated current is below the OCP threshold, then
|
|
|
|
* use it as the possible flash current.
|
|
|
|
*/
|
|
|
|
ibat_flash_ua = ibat_safe_ua - ibat_now;
|
|
|
|
vph_flash_uv = vph_flash_vdip;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* If the calculated current is above the OCP threshold, then
|
|
|
|
* use the ocp threshold instead.
|
|
|
|
*
|
|
|
|
* Any higher current will be tripping the battery OCP.
|
|
|
|
*/
|
|
|
|
ibat_flash_ua = led->pdata->ibatt_ocp_threshold_ua - ibat_now;
|
|
|
|
vph_flash_uv = ocv_uv - div64_s64((int64_t)rbatt_uohm
|
|
|
|
* led->pdata->ibatt_ocp_threshold_ua, UCONV);
|
|
|
|
}
|
|
|
|
/* Calculate the input voltage of the flash module. */
|
2016-08-03 16:56:31 -07:00
|
|
|
vin_flash_uv = max((led->pdata->vled_max_uv +
|
|
|
|
(voltage_hdrm_mv * MCONV)), VIN_FLASH_MIN_UV);
|
2016-07-07 18:46:14 -07:00
|
|
|
/* Calculate the available power for the flash module. */
|
|
|
|
avail_flash_power_fw = BOB_EFFICIENCY * vph_flash_uv * ibat_flash_ua;
|
|
|
|
/*
|
|
|
|
* Calculate the available amount of current the flash module can draw
|
|
|
|
* before collapsing the battery. (available power/ flash input voltage)
|
|
|
|
*/
|
|
|
|
avail_flash_ua = div64_s64(avail_flash_power_fw, vin_flash_uv * MCONV);
|
2016-07-14 10:04:50 -07:00
|
|
|
dev_dbg(&led->pdev->dev, "avail_iflash=%lld, ocv=%d, ibat=%d, rbatt=%d, trigger_lmh=%d\n",
|
|
|
|
avail_flash_ua, ocv_uv, ibat_now, rbatt_uohm,
|
|
|
|
led->trigger_lmh);
|
2016-07-07 18:46:14 -07:00
|
|
|
return min(FLASH_LED_MAX_TOTAL_CURRENT_MA,
|
2016-09-02 14:10:20 +05:30
|
|
|
(int)(div64_s64(avail_flash_ua, MCONV)));
|
2016-07-07 18:46:14 -07:00
|
|
|
}
|
|
|
|
|
2016-07-20 12:06:57 -07:00
|
|
|
static int qpnp_flash_led_calc_thermal_current_lim(struct qpnp_flash_led *led)
|
|
|
|
{
|
|
|
|
int thermal_current_lim = 0;
|
|
|
|
int rc;
|
|
|
|
u8 thermal_thrsh1, thermal_thrsh2, thermal_thrsh3, otst_status;
|
|
|
|
|
|
|
|
/* Store THERMAL_THRSHx register values */
|
|
|
|
rc = qpnp_flash_led_masked_read(led,
|
|
|
|
FLASH_LED_REG_THERMAL_THRSH1(led->base),
|
|
|
|
FLASH_LED_THERMAL_THRSH_MASK,
|
|
|
|
&thermal_thrsh1);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_read(led,
|
|
|
|
FLASH_LED_REG_THERMAL_THRSH2(led->base),
|
|
|
|
FLASH_LED_THERMAL_THRSH_MASK,
|
|
|
|
&thermal_thrsh2);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_read(led,
|
|
|
|
FLASH_LED_REG_THERMAL_THRSH3(led->base),
|
|
|
|
FLASH_LED_THERMAL_THRSH_MASK,
|
|
|
|
&thermal_thrsh3);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
/* Lower THERMAL_THRSHx thresholds to minimum */
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_THERMAL_THRSH1(led->base),
|
|
|
|
FLASH_LED_THERMAL_THRSH_MASK,
|
|
|
|
FLASH_LED_THERMAL_THRSH_MIN);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_THERMAL_THRSH2(led->base),
|
|
|
|
FLASH_LED_THERMAL_THRSH_MASK,
|
|
|
|
FLASH_LED_THERMAL_THRSH_MIN);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_THERMAL_THRSH3(led->base),
|
|
|
|
FLASH_LED_THERMAL_THRSH_MASK,
|
|
|
|
FLASH_LED_THERMAL_THRSH_MIN);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
/* Check THERMAL_OTST status */
|
|
|
|
rc = qpnp_flash_led_read(led,
|
|
|
|
FLASH_LED_REG_LED_STATUS2(led->base),
|
|
|
|
&otst_status);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
/* Look up current limit based on THERMAL_OTST status */
|
|
|
|
if (otst_status)
|
|
|
|
thermal_current_lim =
|
|
|
|
led->pdata->thermal_derate_current[otst_status >> 1];
|
|
|
|
|
|
|
|
/* Restore THERMAL_THRESHx registers to original values */
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_THERMAL_THRSH1(led->base),
|
|
|
|
FLASH_LED_THERMAL_THRSH_MASK,
|
|
|
|
thermal_thrsh1);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_THERMAL_THRSH2(led->base),
|
|
|
|
FLASH_LED_THERMAL_THRSH_MASK,
|
|
|
|
thermal_thrsh2);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_THERMAL_THRSH3(led->base),
|
|
|
|
FLASH_LED_THERMAL_THRSH_MASK,
|
|
|
|
thermal_thrsh3);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
return thermal_current_lim;
|
|
|
|
}
|
|
|
|
|
2016-07-14 10:04:50 -07:00
|
|
|
static int qpnp_flash_led_get_max_avail_current(struct qpnp_flash_led *led)
|
2016-04-05 23:36:54 -07:00
|
|
|
{
|
2016-07-20 12:06:57 -07:00
|
|
|
int max_avail_current, thermal_current_lim = 0;
|
|
|
|
|
2016-07-14 10:04:50 -07:00
|
|
|
led->trigger_lmh = false;
|
2016-07-20 12:06:57 -07:00
|
|
|
max_avail_current = qpnp_flash_led_calc_max_current(led);
|
|
|
|
if (led->pdata->thermal_derate_en)
|
|
|
|
thermal_current_lim =
|
|
|
|
qpnp_flash_led_calc_thermal_current_lim(led);
|
|
|
|
|
|
|
|
if (thermal_current_lim)
|
|
|
|
max_avail_current = min(max_avail_current, thermal_current_lim);
|
|
|
|
|
|
|
|
return max_avail_current;
|
2016-04-05 23:36:54 -07:00
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
static void qpnp_flash_led_node_set(struct flash_node_data *fnode, int value)
|
|
|
|
{
|
2016-06-29 17:48:12 -07:00
|
|
|
int prgm_current_ma = value;
|
2016-03-01 02:34:54 -08:00
|
|
|
|
2016-06-29 17:48:12 -07:00
|
|
|
if (value <= 0)
|
|
|
|
prgm_current_ma = 0;
|
|
|
|
else if (value < FLASH_LED_MIN_CURRENT_MA)
|
|
|
|
prgm_current_ma = FLASH_LED_MIN_CURRENT_MA;
|
|
|
|
|
|
|
|
prgm_current_ma = min(prgm_current_ma, fnode->max_current);
|
|
|
|
fnode->current_ma = prgm_current_ma;
|
2016-03-01 02:34:54 -08:00
|
|
|
fnode->cdev.brightness = prgm_current_ma;
|
2016-10-25 15:54:50 -07:00
|
|
|
fnode->current_reg_val = CURRENT_MA_TO_REG_VAL(prgm_current_ma,
|
|
|
|
fnode->ires_ua);
|
2016-03-01 02:34:54 -08:00
|
|
|
fnode->led_on = prgm_current_ma != 0;
|
|
|
|
}
|
|
|
|
|
2016-06-14 16:13:23 -07:00
|
|
|
static int qpnp_flash_led_switch_disable(struct flash_switch_data *snode)
|
|
|
|
{
|
|
|
|
struct qpnp_flash_led *led = dev_get_drvdata(&snode->pdev->dev);
|
|
|
|
int i, rc, addr_offset;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_EN_LED_CTRL(led->base),
|
|
|
|
snode->led_mask, FLASH_LED_DISABLE);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
2016-07-14 10:04:50 -07:00
|
|
|
if (led->trigger_lmh) {
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_MITIGATION_SW(led->base),
|
|
|
|
FLASH_LED_LMH_MITIGATION_EN_MASK,
|
|
|
|
FLASH_LED_LMH_MITIGATION_DISABLE);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "disable lmh mitigation failed, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-31 14:53:56 -07:00
|
|
|
if (!led->trigger_chgr) {
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_MITIGATION_SW(led->base),
|
|
|
|
FLASH_LED_CHGR_MITIGATION_EN_MASK,
|
|
|
|
FLASH_LED_CHGR_MITIGATION_DISABLE);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "disable chgr mitigation failed, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-14 16:13:23 -07:00
|
|
|
led->enable--;
|
|
|
|
if (led->enable == 0) {
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_MOD_CTRL(led->base),
|
|
|
|
FLASH_LED_MOD_CTRL_MASK, FLASH_LED_DISABLE);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < led->num_fnodes; i++) {
|
|
|
|
if (!led->fnode[i].led_on ||
|
|
|
|
!(snode->led_mask & BIT(led->fnode[i].id)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
addr_offset = led->fnode[i].id;
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_TGR_CURRENT(led->base + addr_offset),
|
|
|
|
FLASH_LED_CURRENT_MASK, 0);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
led->fnode[i].led_on = false;
|
|
|
|
|
|
|
|
if (led->fnode[i].pinctrl) {
|
|
|
|
rc = pinctrl_select_state(led->fnode[i].pinctrl,
|
|
|
|
led->fnode[i].gpio_state_suspend);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"failed to disable GPIO, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-24 20:01:16 +05:30
|
|
|
if (led->fnode[i].trigger & FLASH_LED_HW_SW_STROBE_SEL_BIT) {
|
2016-06-14 16:13:23 -07:00
|
|
|
rc = qpnp_flash_led_hw_strobe_enable(&led->fnode[i],
|
|
|
|
led->pdata->hw_strobe_option, false);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to disable hw strobe, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
snode->enabled = false;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
static int qpnp_flash_led_switch_set(struct flash_switch_data *snode, bool on)
|
|
|
|
{
|
|
|
|
struct qpnp_flash_led *led = dev_get_drvdata(&snode->pdev->dev);
|
|
|
|
int rc, i, addr_offset;
|
2016-10-24 20:01:16 +05:30
|
|
|
u8 val, mask;
|
2016-03-01 02:34:54 -08:00
|
|
|
|
2016-06-14 16:13:23 -07:00
|
|
|
if (snode->enabled == on) {
|
2016-10-24 20:22:19 -07:00
|
|
|
dev_dbg(&led->pdev->dev, "Switch node is already %s!\n",
|
2016-06-14 16:13:23 -07:00
|
|
|
on ? "enabled" : "disabled");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!on) {
|
|
|
|
rc = qpnp_flash_led_switch_disable(snode);
|
|
|
|
return rc;
|
|
|
|
}
|
2016-03-01 02:34:54 -08:00
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
/* Iterate over all leds for this switch node */
|
2016-03-01 02:34:54 -08:00
|
|
|
val = 0;
|
2016-06-14 01:46:06 -07:00
|
|
|
for (i = 0; i < led->num_fnodes; i++)
|
|
|
|
if (snode->led_mask & BIT(led->fnode[i].id))
|
|
|
|
val |= led->fnode[i].ires << (led->fnode[i].id * 2);
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
rc = qpnp_flash_led_masked_write(led, FLASH_LED_REG_IRES(led->base),
|
|
|
|
FLASH_LED_CURRENT_MASK, val);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0)
|
2016-03-01 02:34:54 -08:00
|
|
|
return rc;
|
|
|
|
|
2016-03-16 17:24:17 -07:00
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_STROBE_CFG(led->base),
|
|
|
|
FLASH_LED_ENABLE_MASK,
|
|
|
|
led->pdata->hw_strobe_option);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0)
|
2016-03-16 17:24:17 -07:00
|
|
|
return rc;
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
val = 0;
|
2016-06-14 01:46:06 -07:00
|
|
|
for (i = 0; i < led->num_fnodes; i++) {
|
|
|
|
if (!led->fnode[i].led_on ||
|
|
|
|
!(snode->led_mask & BIT(led->fnode[i].id)))
|
2016-03-01 02:34:54 -08:00
|
|
|
continue;
|
|
|
|
|
|
|
|
addr_offset = led->fnode[i].id;
|
2016-10-24 20:01:16 +05:30
|
|
|
if (led->fnode[i].trigger & FLASH_LED_HW_SW_STROBE_SEL_BIT)
|
|
|
|
mask = FLASH_HW_STROBE_MASK;
|
|
|
|
else
|
|
|
|
mask = FLASH_LED_HW_SW_STROBE_SEL_BIT;
|
2016-03-01 02:34:54 -08:00
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_STROBE_CTRL(led->base + addr_offset),
|
2016-10-24 20:01:16 +05:30
|
|
|
mask, led->fnode[i].trigger);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0)
|
2016-03-01 02:34:54 -08:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_TGR_CURRENT(led->base + addr_offset),
|
2016-06-29 17:48:12 -07:00
|
|
|
FLASH_LED_CURRENT_MASK, led->fnode[i].current_reg_val);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0)
|
2016-03-01 02:34:54 -08:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_SAFETY_TMR(led->base + addr_offset),
|
2016-06-16 13:04:05 -07:00
|
|
|
FLASH_LED_SAFETY_TMR_MASK, led->fnode[i].duration);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0)
|
2016-03-01 02:34:54 -08:00
|
|
|
return rc;
|
|
|
|
|
2016-03-16 17:24:17 -07:00
|
|
|
val |= FLASH_LED_ENABLE << led->fnode[i].id;
|
2016-03-01 02:34:54 -08:00
|
|
|
|
|
|
|
if (led->fnode[i].pinctrl) {
|
|
|
|
rc = pinctrl_select_state(led->fnode[i].pinctrl,
|
|
|
|
led->fnode[i].gpio_state_active);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0) {
|
2016-03-01 02:34:54 -08:00
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"failed to enable GPIO\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
2016-03-16 17:24:17 -07:00
|
|
|
|
2016-10-24 20:01:16 +05:30
|
|
|
if (led->fnode[i].trigger & FLASH_LED_HW_SW_STROBE_SEL_BIT) {
|
2016-03-16 17:24:17 -07:00
|
|
|
rc = qpnp_flash_led_hw_strobe_enable(&led->fnode[i],
|
|
|
|
led->pdata->hw_strobe_option, true);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0) {
|
2016-03-16 17:24:17 -07:00
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to enable hw strobe\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
2016-03-01 02:34:54 -08:00
|
|
|
}
|
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
if (led->enable == 0) {
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_MOD_CTRL(led->base),
|
2016-03-01 02:34:54 -08:00
|
|
|
FLASH_LED_MOD_CTRL_MASK, FLASH_LED_MOD_ENABLE);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
led->enable++;
|
2016-03-01 02:34:54 -08:00
|
|
|
|
2016-07-14 10:04:50 -07:00
|
|
|
if (led->trigger_lmh) {
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_MITIGATION_SW(led->base),
|
|
|
|
FLASH_LED_LMH_MITIGATION_EN_MASK,
|
|
|
|
FLASH_LED_LMH_MITIGATION_ENABLE);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "trigger lmh mitigation failed, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-31 14:53:56 -07:00
|
|
|
if (led->trigger_chgr) {
|
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
|
|
|
FLASH_LED_REG_MITIGATION_SW(led->base),
|
|
|
|
FLASH_LED_CHGR_MITIGATION_EN_MASK,
|
|
|
|
FLASH_LED_CHGR_MITIGATION_ENABLE);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "trigger chgr mitigation failed, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
rc = qpnp_flash_led_masked_write(led,
|
2016-03-16 17:24:17 -07:00
|
|
|
FLASH_LED_EN_LED_CTRL(led->base),
|
2016-06-14 01:46:06 -07:00
|
|
|
snode->led_mask, val);
|
|
|
|
if (rc < 0)
|
2016-03-01 02:34:54 -08:00
|
|
|
return rc;
|
|
|
|
|
2016-06-14 16:13:23 -07:00
|
|
|
snode->enabled = true;
|
2016-03-01 02:34:54 -08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-08-09 01:11:15 -07:00
|
|
|
int qpnp_flash_led_prepare(struct led_trigger *trig, int options,
|
|
|
|
int *max_current)
|
2016-04-05 23:36:54 -07:00
|
|
|
{
|
2016-07-26 16:37:28 -07:00
|
|
|
struct led_classdev *led_cdev = trigger_to_lcdev(trig);
|
|
|
|
struct flash_switch_data *snode;
|
|
|
|
struct qpnp_flash_led *led;
|
2016-08-09 01:11:15 -07:00
|
|
|
int rc;
|
2016-04-05 23:36:54 -07:00
|
|
|
|
2016-07-26 16:37:28 -07:00
|
|
|
if (!led_cdev) {
|
|
|
|
pr_err("Invalid led_trigger provided\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
snode = container_of(led_cdev, struct flash_switch_data, cdev);
|
|
|
|
led = dev_get_drvdata(&snode->pdev->dev);
|
|
|
|
|
2016-08-09 01:11:15 -07:00
|
|
|
if (!(options & FLASH_LED_PREPARE_OPTIONS_MASK)) {
|
2016-04-05 23:36:54 -07:00
|
|
|
dev_err(&led->pdev->dev, "Invalid options %d\n", options);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options & ENABLE_REGULATOR) {
|
|
|
|
rc = qpnp_flash_led_regulator_enable(led, snode, true);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"enable regulator failed, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-09 01:11:15 -07:00
|
|
|
if (options & DISABLE_REGULATOR) {
|
|
|
|
rc = qpnp_flash_led_regulator_enable(led, snode, false);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"disable regulator failed, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-05 23:36:54 -07:00
|
|
|
if (options & QUERY_MAX_CURRENT) {
|
2016-08-09 01:11:15 -07:00
|
|
|
rc = qpnp_flash_led_get_max_avail_current(led);
|
|
|
|
if (rc < 0) {
|
2016-04-05 23:36:54 -07:00
|
|
|
dev_err(&led->pdev->dev,
|
2016-08-09 01:11:15 -07:00
|
|
|
"query max current failed, rc=%d\n", rc);
|
|
|
|
return rc;
|
2016-04-05 23:36:54 -07:00
|
|
|
}
|
2016-08-09 01:11:15 -07:00
|
|
|
*max_current = rc;
|
2016-04-05 23:36:54 -07:00
|
|
|
}
|
|
|
|
|
2016-08-31 14:53:56 -07:00
|
|
|
led->trigger_chgr = false;
|
|
|
|
if (options & PRE_FLASH)
|
|
|
|
led->trigger_chgr = true;
|
|
|
|
|
2016-08-09 01:11:15 -07:00
|
|
|
return 0;
|
2016-04-05 23:36:54 -07:00
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
static void qpnp_flash_led_brightness_set(struct led_classdev *led_cdev,
|
|
|
|
enum led_brightness value)
|
|
|
|
{
|
|
|
|
struct flash_node_data *fnode = NULL;
|
|
|
|
struct flash_switch_data *snode = NULL;
|
2016-08-29 14:00:12 -07:00
|
|
|
struct qpnp_flash_led *led = NULL;
|
2016-03-01 02:34:54 -08:00
|
|
|
int rc;
|
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
if (!strncmp(led_cdev->name, "led:switch", strlen("led:switch"))) {
|
2016-03-01 02:34:54 -08:00
|
|
|
snode = container_of(led_cdev, struct flash_switch_data, cdev);
|
|
|
|
led = dev_get_drvdata(&snode->pdev->dev);
|
2016-08-29 14:00:12 -07:00
|
|
|
} else if (!strncmp(led_cdev->name, "led:flash", strlen("led:flash")) ||
|
|
|
|
!strncmp(led_cdev->name, "led:torch",
|
|
|
|
strlen("led:torch"))) {
|
2016-03-01 02:34:54 -08:00
|
|
|
fnode = container_of(led_cdev, struct flash_node_data, cdev);
|
|
|
|
led = dev_get_drvdata(&fnode->pdev->dev);
|
|
|
|
}
|
|
|
|
|
2016-08-29 14:00:12 -07:00
|
|
|
if (!led) {
|
|
|
|
pr_err("Failed to get flash driver data\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
spin_lock(&led->lock);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (snode) {
|
2016-03-01 02:34:54 -08:00
|
|
|
rc = qpnp_flash_led_switch_set(snode, value > 0);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0)
|
2016-03-01 02:34:54 -08:00
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Failed to set flash LED switch\n");
|
2016-06-14 01:46:06 -07:00
|
|
|
} else if (fnode) {
|
2016-03-01 02:34:54 -08:00
|
|
|
qpnp_flash_led_node_set(fnode, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock(&led->lock);
|
|
|
|
}
|
|
|
|
|
2016-08-17 16:19:32 -07:00
|
|
|
/* sysfs show function for flash_max_current */
|
|
|
|
static ssize_t qpnp_flash_led_max_current_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
struct flash_switch_data *snode;
|
|
|
|
struct qpnp_flash_led *led;
|
|
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
snode = container_of(led_cdev, struct flash_switch_data, cdev);
|
|
|
|
led = dev_get_drvdata(&snode->pdev->dev);
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_get_max_avail_current(led);
|
|
|
|
if (rc < 0)
|
|
|
|
dev_err(&led->pdev->dev, "query max current failed, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%d\n", rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* sysfs attributes exported by flash_led */
|
|
|
|
static struct device_attribute qpnp_flash_led_attrs[] = {
|
|
|
|
__ATTR(max_current, (S_IRUGO | S_IWUSR | S_IWGRP),
|
|
|
|
qpnp_flash_led_max_current_show, NULL),
|
|
|
|
};
|
|
|
|
|
2016-07-07 18:09:08 -07:00
|
|
|
static int flash_led_psy_notifier_call(struct notifier_block *nb,
|
|
|
|
unsigned long ev, void *v)
|
|
|
|
{
|
|
|
|
struct power_supply *psy = v;
|
|
|
|
struct qpnp_flash_led *led =
|
|
|
|
container_of(nb, struct qpnp_flash_led, nb);
|
|
|
|
|
|
|
|
if (ev != PSY_EVENT_PROP_CHANGED)
|
|
|
|
return NOTIFY_OK;
|
|
|
|
|
|
|
|
if (!strcmp(psy->desc->name, "bms")) {
|
|
|
|
led->bms_psy = power_supply_get_by_name("bms");
|
|
|
|
if (!led->bms_psy)
|
|
|
|
dev_err(&led->pdev->dev, "Failed to get bms power_supply\n");
|
|
|
|
else
|
|
|
|
power_supply_unreg_notifier(&led->nb);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NOTIFY_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int flash_led_psy_register_notifier(struct qpnp_flash_led *led)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
led->nb.notifier_call = flash_led_psy_notifier_call;
|
|
|
|
rc = power_supply_reg_notifier(&led->nb);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't register psy notifier, rc = %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-06-03 12:27:39 -07:00
|
|
|
/* irq handler */
|
|
|
|
static irqreturn_t qpnp_flash_led_irq_handler(int irq, void *_led)
|
|
|
|
{
|
|
|
|
struct qpnp_flash_led *led = _led;
|
|
|
|
enum flash_led_irq_type irq_type = INVALID_IRQ;
|
|
|
|
int rc;
|
2016-07-19 15:23:56 -07:00
|
|
|
u8 irq_status, led_status1, led_status2;
|
2016-06-03 12:27:39 -07:00
|
|
|
|
|
|
|
dev_dbg(&led->pdev->dev, "irq received, irq=%d\n", irq);
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_read(led,
|
2016-07-19 15:23:56 -07:00
|
|
|
FLASH_LED_REG_INT_RT_STS(led->base), &irq_status);
|
2016-06-03 12:27:39 -07:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "Failed to read interrupt status reg, rc=%d\n",
|
|
|
|
rc);
|
2016-07-19 15:23:56 -07:00
|
|
|
goto exit;
|
2016-06-03 12:27:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (irq == led->pdata->all_ramp_up_done_irq)
|
|
|
|
irq_type = ALL_RAMP_UP_DONE_IRQ;
|
|
|
|
else if (irq == led->pdata->all_ramp_down_done_irq)
|
|
|
|
irq_type = ALL_RAMP_DOWN_DONE_IRQ;
|
2016-07-19 15:23:56 -07:00
|
|
|
else if (irq == led->pdata->led_fault_irq)
|
|
|
|
irq_type = LED_FAULT_IRQ;
|
2016-06-03 12:27:39 -07:00
|
|
|
|
2016-06-07 14:22:33 -07:00
|
|
|
if (irq_type == ALL_RAMP_UP_DONE_IRQ)
|
|
|
|
atomic_notifier_call_chain(&irq_notifier_list,
|
|
|
|
irq_type, NULL);
|
|
|
|
|
2016-07-19 15:23:56 -07:00
|
|
|
if (irq_type == LED_FAULT_IRQ) {
|
|
|
|
rc = qpnp_flash_led_read(led,
|
|
|
|
FLASH_LED_REG_LED_STATUS1(led->base), &led_status1);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "Failed to read led_status1 reg, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_read(led,
|
|
|
|
FLASH_LED_REG_LED_STATUS2(led->base), &led_status2);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "Failed to read led_status2 reg, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (led_status1)
|
|
|
|
dev_emerg(&led->pdev->dev, "led short/open fault detected! led_status1=%x\n",
|
|
|
|
led_status1);
|
|
|
|
|
|
|
|
if (led_status2 & FLASH_LED_VPH_DROOP_FAULT_MASK)
|
|
|
|
dev_emerg(&led->pdev->dev, "led vph_droop fault detected!\n");
|
|
|
|
}
|
|
|
|
|
2016-06-03 12:27:39 -07:00
|
|
|
dev_dbg(&led->pdev->dev, "irq handled, irq_type=%x, irq_status=%x\n",
|
2016-07-19 15:23:56 -07:00
|
|
|
irq_type, irq_status);
|
2016-06-07 14:22:33 -07:00
|
|
|
|
2016-07-19 15:23:56 -07:00
|
|
|
exit:
|
2016-06-03 12:27:39 -07:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2016-06-07 14:22:33 -07:00
|
|
|
int qpnp_flash_led_register_irq_notifier(struct notifier_block *nb)
|
|
|
|
{
|
|
|
|
return atomic_notifier_chain_register(&irq_notifier_list, nb);
|
|
|
|
}
|
|
|
|
|
|
|
|
int qpnp_flash_led_unregister_irq_notifier(struct notifier_block *nb)
|
|
|
|
{
|
|
|
|
return atomic_notifier_chain_unregister(&irq_notifier_list, nb);
|
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
static int qpnp_flash_led_parse_each_led_dt(struct qpnp_flash_led *led,
|
|
|
|
struct flash_node_data *fnode, struct device_node *node)
|
|
|
|
{
|
|
|
|
const char *temp_string;
|
|
|
|
int rc;
|
|
|
|
u32 val;
|
2016-03-16 17:24:17 -07:00
|
|
|
bool strobe_sel = 0, edge_trigger = 0, active_high = 0;
|
2016-03-01 02:34:54 -08:00
|
|
|
|
|
|
|
fnode->pdev = led->pdev;
|
|
|
|
fnode->cdev.brightness_set = qpnp_flash_led_brightness_set;
|
|
|
|
fnode->cdev.brightness_get = qpnp_flash_led_brightness_get;
|
|
|
|
|
|
|
|
rc = of_property_read_string(node, "qcom,led-name", &fnode->cdev.name);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0) {
|
2016-03-01 02:34:54 -08:00
|
|
|
dev_err(&led->pdev->dev, "Unable to read flash LED names\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = of_property_read_string(node, "label", &temp_string);
|
|
|
|
if (!rc) {
|
2016-06-14 01:46:06 -07:00
|
|
|
if (!strcmp(temp_string, "flash")) {
|
2016-03-01 02:34:54 -08:00
|
|
|
fnode->type = FLASH_LED_TYPE_FLASH;
|
2016-06-14 01:46:06 -07:00
|
|
|
} else if (!strcmp(temp_string, "torch")) {
|
2016-03-01 02:34:54 -08:00
|
|
|
fnode->type = FLASH_LED_TYPE_TORCH;
|
2016-06-14 01:46:06 -07:00
|
|
|
} else {
|
2016-03-01 02:34:54 -08:00
|
|
|
dev_err(&led->pdev->dev, "Wrong flash LED type\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to read flash LED label\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = of_property_read_u32(node, "qcom,id", &val);
|
|
|
|
if (!rc) {
|
|
|
|
fnode->id = (u8)val;
|
|
|
|
} else {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to read flash LED ID\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = of_property_read_string(node, "qcom,default-led-trigger",
|
|
|
|
&fnode->cdev.default_trigger);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0) {
|
2016-03-01 02:34:54 -08:00
|
|
|
dev_err(&led->pdev->dev, "Unable to read trigger name\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
fnode->ires_ua = FLASH_LED_IRES_DEFAULT_UA;
|
|
|
|
fnode->ires = FLASH_LED_IRES_DEFAULT_VAL;
|
|
|
|
rc = of_property_read_u32(node, "qcom,ires-ua", &val);
|
|
|
|
if (!rc) {
|
|
|
|
fnode->ires_ua = val;
|
|
|
|
fnode->ires = FLASH_LED_IRES_BASE -
|
|
|
|
(val - FLASH_LED_IRES_MIN_UA) / FLASH_LED_IRES_DIVISOR;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to read current resolution\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-06-29 17:48:12 -07:00
|
|
|
rc = of_property_read_u32(node, "qcom,max-current", &val);
|
|
|
|
if (!rc) {
|
|
|
|
if (val < FLASH_LED_MIN_CURRENT_MA)
|
|
|
|
val = FLASH_LED_MIN_CURRENT_MA;
|
|
|
|
fnode->max_current = val;
|
|
|
|
fnode->cdev.max_brightness = val;
|
|
|
|
} else {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to read max current, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = of_property_read_u32(node, "qcom,current-ma", &val);
|
|
|
|
if (!rc) {
|
|
|
|
if (val < FLASH_LED_MIN_CURRENT_MA ||
|
|
|
|
val > fnode->max_current)
|
|
|
|
dev_warn(&led->pdev->dev,
|
|
|
|
"Invalid operational current specified, capping it\n");
|
|
|
|
if (val < FLASH_LED_MIN_CURRENT_MA)
|
|
|
|
val = FLASH_LED_MIN_CURRENT_MA;
|
|
|
|
if (val > fnode->max_current)
|
|
|
|
val = fnode->max_current;
|
|
|
|
fnode->current_ma = val;
|
|
|
|
fnode->cdev.brightness = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to read operational current, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
fnode->duration = FLASH_LED_SAFETY_TMR_DISABLED;
|
|
|
|
rc = of_property_read_u32(node, "qcom,duration-ms", &val);
|
|
|
|
if (!rc) {
|
2016-10-25 16:40:39 -07:00
|
|
|
fnode->duration = (u8)(SAFETY_TMR_TO_REG_VAL(val) |
|
2016-06-16 13:04:05 -07:00
|
|
|
FLASH_LED_SAFETY_TMR_ENABLE);
|
2016-03-01 02:34:54 -08:00
|
|
|
} else if (rc == -EINVAL) {
|
|
|
|
if (fnode->type == FLASH_LED_TYPE_FLASH) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Timer duration is required for flash LED\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to read timer duration\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
fnode->hdrm_val = FLASH_LED_HDRM_VOL_DEFAULT_MV;
|
|
|
|
rc = of_property_read_u32(node, "qcom,hdrm-voltage-mv", &val);
|
|
|
|
if (!rc) {
|
|
|
|
val = (val - FLASH_LED_HDRM_VOL_BASE_MV) /
|
|
|
|
FLASH_LED_HDRM_VOL_STEP_MV;
|
|
|
|
fnode->hdrm_val = (val << FLASH_LED_HDRM_VOL_SHIFT) &
|
|
|
|
FLASH_LED_HDRM_VOL_MASK;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to read headroom voltage\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = of_property_read_u32(node, "qcom,hdrm-vol-hi-lo-win-mv", &val);
|
|
|
|
if (!rc) {
|
|
|
|
fnode->hdrm_val |= (val / FLASH_LED_HDRM_VOL_STEP_MV) &
|
|
|
|
~FLASH_LED_HDRM_VOL_MASK;
|
|
|
|
} else if (rc == -EINVAL) {
|
|
|
|
fnode->hdrm_val |= FLASH_LED_HDRM_VOL_HI_LO_WIN_DEFAULT_MV;
|
|
|
|
} else {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to read hdrm hi-lo window voltage\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-03-16 17:24:17 -07:00
|
|
|
strobe_sel = of_property_read_bool(node, "qcom,hw-strobe-sel");
|
|
|
|
if (strobe_sel) {
|
|
|
|
edge_trigger = of_property_read_bool(node,
|
|
|
|
"qcom,hw-strobe-edge-trigger");
|
|
|
|
active_high = !of_property_read_bool(node,
|
|
|
|
"qcom,hw-strobe-active-low");
|
|
|
|
}
|
|
|
|
fnode->trigger = (strobe_sel << 2) | (edge_trigger << 1) | active_high;
|
|
|
|
|
2016-10-24 20:01:16 +05:30
|
|
|
if (fnode->trigger & FLASH_LED_HW_SW_STROBE_SEL_BIT) {
|
2016-03-16 17:24:17 -07:00
|
|
|
if (of_find_property(node, "qcom,hw-strobe-gpio", NULL)) {
|
|
|
|
fnode->hw_strobe_gpio = of_get_named_gpio(node,
|
|
|
|
"qcom,hw-strobe-gpio", 0);
|
|
|
|
if (fnode->hw_strobe_gpio < 0) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Invalid gpio specified\n");
|
|
|
|
return fnode->hw_strobe_gpio;
|
|
|
|
}
|
|
|
|
gpio_direction_output(fnode->hw_strobe_gpio, 0);
|
|
|
|
} else {
|
|
|
|
fnode->hw_strobe_gpio = -1;
|
|
|
|
fnode->hw_strobe_state_active =
|
|
|
|
pinctrl_lookup_state(fnode->pinctrl,
|
|
|
|
"strobe_enable");
|
|
|
|
if (IS_ERR_OR_NULL(fnode->hw_strobe_state_active)) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"No active pin for hardware strobe, rc=%ld\n",
|
|
|
|
PTR_ERR(fnode->hw_strobe_state_active));
|
|
|
|
fnode->hw_strobe_state_active = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
fnode->hw_strobe_state_suspend =
|
|
|
|
pinctrl_lookup_state(fnode->pinctrl,
|
|
|
|
"strobe_disable");
|
|
|
|
if (IS_ERR_OR_NULL(fnode->hw_strobe_state_suspend)) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"No suspend pin for hardware strobe, rc=%ld\n",
|
|
|
|
PTR_ERR(fnode->hw_strobe_state_suspend)
|
|
|
|
);
|
|
|
|
fnode->hw_strobe_state_suspend = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
rc = led_classdev_register(&led->pdev->dev, &fnode->cdev);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0) {
|
2016-03-01 02:34:54 -08:00
|
|
|
dev_err(&led->pdev->dev, "Unable to register led node %d\n",
|
|
|
|
fnode->id);
|
|
|
|
return rc;
|
|
|
|
}
|
2016-06-14 01:46:06 -07:00
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
fnode->cdev.dev->of_node = node;
|
|
|
|
|
|
|
|
fnode->pinctrl = devm_pinctrl_get(fnode->cdev.dev);
|
|
|
|
if (IS_ERR_OR_NULL(fnode->pinctrl)) {
|
2016-10-24 20:22:19 -07:00
|
|
|
dev_dbg(&led->pdev->dev, "No pinctrl defined\n");
|
2016-03-01 02:34:54 -08:00
|
|
|
fnode->pinctrl = NULL;
|
|
|
|
} else {
|
|
|
|
fnode->gpio_state_active =
|
|
|
|
pinctrl_lookup_state(fnode->pinctrl, "led_enable");
|
|
|
|
if (IS_ERR_OR_NULL(fnode->gpio_state_active)) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Cannot lookup LED active state\n");
|
|
|
|
devm_pinctrl_put(fnode->pinctrl);
|
|
|
|
fnode->pinctrl = NULL;
|
|
|
|
return PTR_ERR(fnode->gpio_state_active);
|
|
|
|
}
|
|
|
|
|
|
|
|
fnode->gpio_state_suspend =
|
|
|
|
pinctrl_lookup_state(fnode->pinctrl, "led_disable");
|
|
|
|
if (IS_ERR_OR_NULL(fnode->gpio_state_suspend)) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Cannot lookup LED disable state\n");
|
|
|
|
devm_pinctrl_put(fnode->pinctrl);
|
|
|
|
fnode->pinctrl = NULL;
|
|
|
|
return PTR_ERR(fnode->gpio_state_suspend);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int qpnp_flash_led_parse_and_register_switch(struct qpnp_flash_led *led,
|
2016-06-14 01:46:06 -07:00
|
|
|
struct flash_switch_data *snode,
|
2016-03-01 02:34:54 -08:00
|
|
|
struct device_node *node)
|
|
|
|
{
|
2016-10-24 20:22:19 -07:00
|
|
|
int rc = 0, num;
|
|
|
|
char reg_name[16], reg_sup_name[16];
|
2016-03-01 02:34:54 -08:00
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
rc = of_property_read_string(node, "qcom,led-name", &snode->cdev.name);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Failed to read switch node name, rc=%d\n", rc);
|
2016-03-01 02:34:54 -08:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-10-24 20:22:19 -07:00
|
|
|
rc = sscanf(snode->cdev.name, "led:switch_%d", &num);
|
|
|
|
if (!rc) {
|
|
|
|
pr_err("No number for switch device?\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
rc = of_property_read_string(node, "qcom,default-led-trigger",
|
2016-06-14 01:46:06 -07:00
|
|
|
&snode->cdev.default_trigger);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to read trigger name, rc=%d\n", rc);
|
2016-03-01 02:34:54 -08:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
rc = of_property_read_u32(node, "qcom,led-mask", &snode->led_mask);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to read led mask rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (snode->led_mask < 1 || snode->led_mask > 7) {
|
|
|
|
dev_err(&led->pdev->dev, "Invalid value for led-mask\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2016-10-24 20:22:19 -07:00
|
|
|
scnprintf(reg_name, sizeof(reg_name), "switch%d-supply", num);
|
|
|
|
if (of_find_property(led->pdev->dev.of_node, reg_name, NULL)) {
|
|
|
|
scnprintf(reg_sup_name, sizeof(reg_sup_name), "switch%d", num);
|
|
|
|
snode->vreg = devm_regulator_get(&led->pdev->dev, reg_sup_name);
|
|
|
|
if (IS_ERR_OR_NULL(snode->vreg)) {
|
|
|
|
rc = PTR_ERR(snode->vreg);
|
|
|
|
if (rc != -EPROBE_DEFER)
|
|
|
|
dev_err(&led->pdev->dev, "Failed to get regulator, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
snode->vreg = NULL;
|
2016-06-02 11:59:58 -07:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
snode->pdev = led->pdev;
|
|
|
|
snode->cdev.brightness_set = qpnp_flash_led_brightness_set;
|
|
|
|
snode->cdev.brightness_get = qpnp_flash_led_brightness_get;
|
|
|
|
rc = led_classdev_register(&led->pdev->dev, &snode->cdev);
|
|
|
|
if (rc < 0) {
|
2016-03-01 02:34:54 -08:00
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to register led switch node\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
snode->cdev.dev->of_node = node;
|
2016-03-01 02:34:54 -08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int qpnp_flash_led_parse_common_dt(struct qpnp_flash_led *led,
|
|
|
|
struct device_node *node)
|
|
|
|
{
|
2016-11-14 18:43:40 -08:00
|
|
|
struct device_node *revid_node;
|
2016-03-01 02:34:54 -08:00
|
|
|
int rc;
|
|
|
|
u32 val;
|
2016-07-07 16:23:12 -07:00
|
|
|
bool short_circuit_det, open_circuit_det, vph_droop_det;
|
2016-03-01 02:34:54 -08:00
|
|
|
|
2016-11-14 18:43:40 -08:00
|
|
|
revid_node = of_parse_phandle(node, "qcom,pmic-revid", 0);
|
|
|
|
if (!revid_node) {
|
|
|
|
pr_err("Missing qcom,pmic-revid property - driver failed\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
led->pdata->pmic_rev_id = get_revid_data(revid_node);
|
|
|
|
if (IS_ERR_OR_NULL(led->pdata->pmic_rev_id)) {
|
|
|
|
pr_err("Unable to get pmic_revid rc=%ld\n",
|
|
|
|
PTR_ERR(led->pdata->pmic_rev_id));
|
|
|
|
/*
|
|
|
|
* the revid peripheral must be registered, any failure
|
|
|
|
* here only indicates that the rev-id module has not
|
|
|
|
* probed yet.
|
|
|
|
*/
|
|
|
|
return -EPROBE_DEFER;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_debug("PMIC subtype %d Digital major %d\n",
|
|
|
|
led->pdata->pmic_rev_id->pmic_subtype,
|
|
|
|
led->pdata->pmic_rev_id->rev4);
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
led->pdata->hdrm_auto_mode_en = of_property_read_bool(node,
|
|
|
|
"qcom,hdrm-auto-mode");
|
|
|
|
|
2016-07-07 16:23:12 -07:00
|
|
|
led->pdata->isc_delay = FLASH_LED_ISC_DELAY_DEFAULT;
|
|
|
|
rc = of_property_read_u32(node, "qcom,isc-delay-us", &val);
|
2016-03-01 02:34:54 -08:00
|
|
|
if (!rc) {
|
2016-07-07 16:23:12 -07:00
|
|
|
led->pdata->isc_delay =
|
|
|
|
val >> FLASH_LED_ISC_WARMUP_DELAY_SHIFT;
|
2016-03-01 02:34:54 -08:00
|
|
|
} else if (rc != -EINVAL) {
|
2016-07-07 16:23:12 -07:00
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to read ISC delay, rc=%d\n", rc);
|
2016-03-01 02:34:54 -08:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-07-07 16:23:12 -07:00
|
|
|
led->pdata->warmup_delay = FLASH_LED_WARMUP_DELAY_DEFAULT;
|
|
|
|
rc = of_property_read_u32(node, "qcom,warmup-delay-us", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->warmup_delay =
|
|
|
|
val >> FLASH_LED_ISC_WARMUP_DELAY_SHIFT;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to read WARMUP delay, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
short_circuit_det =
|
|
|
|
of_property_read_bool(node, "qcom,short-circuit-det");
|
|
|
|
open_circuit_det = of_property_read_bool(node, "qcom,open-circuit-det");
|
|
|
|
vph_droop_det = of_property_read_bool(node, "qcom,vph-droop-det");
|
|
|
|
led->pdata->current_derate_en_cfg = (vph_droop_det << 2) |
|
|
|
|
(open_circuit_det << 1) | short_circuit_det;
|
|
|
|
|
2016-07-20 12:06:57 -07:00
|
|
|
led->pdata->thermal_derate_en =
|
|
|
|
of_property_read_bool(node, "qcom,thermal-derate-en");
|
|
|
|
|
|
|
|
if (led->pdata->thermal_derate_en) {
|
|
|
|
led->pdata->thermal_derate_current =
|
|
|
|
devm_kcalloc(&led->pdev->dev,
|
|
|
|
FLASH_LED_THERMAL_OTST_LEVELS,
|
|
|
|
sizeof(int), GFP_KERNEL);
|
|
|
|
if (!led->pdata->thermal_derate_current)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
rc = of_property_read_u32_array(node,
|
|
|
|
"qcom,thermal-derate-current",
|
|
|
|
led->pdata->thermal_derate_current,
|
|
|
|
FLASH_LED_THERMAL_OTST_LEVELS);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to read thermal current limits, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-07 16:23:12 -07:00
|
|
|
led->pdata->vph_droop_debounce = FLASH_LED_VPH_DROOP_DEBOUNCE_DEFAULT;
|
|
|
|
rc = of_property_read_u32(node, "qcom,vph-droop-debounce-us", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->vph_droop_debounce =
|
|
|
|
VPH_DROOP_DEBOUNCE_US_TO_VAL(val);
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to read VPH droop debounce, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (led->pdata->vph_droop_debounce > FLASH_LED_VPH_DROOP_DEBOUNCE_MAX) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Invalid VPH droop debounce specified");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
led->pdata->vph_droop_threshold = FLASH_LED_VPH_DROOP_THRESH_DEFAULT;
|
|
|
|
rc = of_property_read_u32(node, "qcom,vph-droop-threshold-mv", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->vph_droop_threshold =
|
|
|
|
VPH_DROOP_THRESH_MV_TO_VAL(val);
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to read VPH droop threshold, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (led->pdata->vph_droop_threshold > FLASH_LED_VPH_DROOP_THRESH_MAX) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Invalid VPH droop threshold specified");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
led->pdata->vph_droop_hysteresis =
|
|
|
|
FLASH_LED_VPH_DROOP_HYST_DEFAULT;
|
|
|
|
rc = of_property_read_u32(node, "qcom,vph-droop-hysteresis-mv", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->vph_droop_hysteresis =
|
|
|
|
VPH_DROOP_HYST_MV_TO_VAL(val);
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to read VPH droop hysteresis, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (led->pdata->vph_droop_hysteresis > FLASH_LED_VPH_DROOP_HYST_MAX) {
|
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Invalid VPH droop hysteresis specified");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2016-03-16 17:24:17 -07:00
|
|
|
rc = of_property_read_u32(node, "qcom,hw-strobe-option", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->hw_strobe_option = (u8)val;
|
|
|
|
} else if (rc != -EINVAL) {
|
2016-07-07 16:23:12 -07:00
|
|
|
dev_err(&led->pdev->dev,
|
|
|
|
"Unable to parse hw strobe option, rc=%d\n", rc);
|
2016-03-16 17:24:17 -07:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-08-26 12:31:56 -07:00
|
|
|
rc = of_property_read_u32(node, "qcom,led1n2-iclamp-low-ma", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->led1n2_iclamp_low_ma = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to read led1n2_iclamp_low current, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = of_property_read_u32(node, "qcom,led1n2-iclamp-mid-ma", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->led1n2_iclamp_mid_ma = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to read led1n2_iclamp_mid current, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = of_property_read_u32(node, "qcom,led3-iclamp-low-ma", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->led3_iclamp_low_ma = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to read led3_iclamp_low current, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = of_property_read_u32(node, "qcom,led3-iclamp-mid-ma", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->led3_iclamp_mid_ma = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to read led3_iclamp_mid current, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-07-07 18:46:14 -07:00
|
|
|
led->pdata->vled_max_uv = FLASH_LED_VLED_MAX_DEFAULT_UV;
|
|
|
|
rc = of_property_read_u32(node, "qcom,vled-max-uv", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->vled_max_uv = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to parse vled_max voltage, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
led->pdata->ibatt_ocp_threshold_ua =
|
|
|
|
FLASH_LED_IBATT_OCP_THRESH_DEFAULT_UA;
|
|
|
|
rc = of_property_read_u32(node, "qcom,ibatt-ocp-threshold-ua", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->ibatt_ocp_threshold_ua = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to parse ibatt_ocp threshold, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
led->pdata->rpara_uohm = FLASH_LED_RPARA_DEFAULT_UOHM;
|
|
|
|
rc = of_property_read_u32(node, "qcom,rparasitic-uohm", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->rpara_uohm = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to parse rparasitic, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-07-14 10:04:50 -07:00
|
|
|
led->pdata->lmh_ocv_threshold_uv =
|
|
|
|
FLASH_LED_LMH_OCV_THRESH_DEFAULT_UV;
|
|
|
|
rc = of_property_read_u32(node, "qcom,lmh-ocv-threshold-uv", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->lmh_ocv_threshold_uv = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to parse lmh ocv threshold, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
led->pdata->lmh_rbatt_threshold_uohm =
|
|
|
|
FLASH_LED_LMH_RBATT_THRESH_DEFAULT_UOHM;
|
|
|
|
rc = of_property_read_u32(node, "qcom,lmh-rbatt-threshold-uohm", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->lmh_rbatt_threshold_uohm = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to parse lmh rbatt threshold, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
led->pdata->lmh_level = FLASH_LED_LMH_LEVEL_DEFAULT;
|
|
|
|
rc = of_property_read_u32(node, "qcom,lmh-level", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->lmh_level = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to parse lmh_level, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-08-31 14:53:56 -07:00
|
|
|
led->pdata->lmh_mitigation_sel = FLASH_LED_MITIGATION_SEL_DEFAULT;
|
2016-07-14 10:04:50 -07:00
|
|
|
rc = of_property_read_u32(node, "qcom,lmh-mitigation-sel", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->lmh_mitigation_sel = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to parse lmh_mitigation_sel, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-08-31 14:53:56 -07:00
|
|
|
if (led->pdata->lmh_mitigation_sel > FLASH_LED_MITIGATION_SEL_MAX) {
|
2016-07-14 10:04:50 -07:00
|
|
|
dev_err(&led->pdev->dev, "Invalid lmh_mitigation_sel specified\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2016-08-31 14:53:56 -07:00
|
|
|
led->pdata->chgr_mitigation_sel = FLASH_LED_MITIGATION_SEL_DEFAULT;
|
|
|
|
rc = of_property_read_u32(node, "qcom,chgr-mitigation-sel", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->chgr_mitigation_sel = val;
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to parse chgr_mitigation_sel, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (led->pdata->chgr_mitigation_sel > FLASH_LED_MITIGATION_SEL_MAX) {
|
|
|
|
dev_err(&led->pdev->dev, "Invalid chgr_mitigation_sel specified\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
led->pdata->chgr_mitigation_sel <<= FLASH_LED_CHGR_MITIGATION_SEL_SHIFT;
|
|
|
|
|
2016-08-31 14:16:45 -07:00
|
|
|
led->pdata->iled_thrsh_val = FLASH_LED_MITIGATION_THRSH_DEFAULT;
|
|
|
|
rc = of_property_read_u32(node, "qcom,iled-thrsh-ma", &val);
|
|
|
|
if (!rc) {
|
|
|
|
led->pdata->iled_thrsh_val = MITIGATION_THRSH_MA_TO_VAL(val);
|
|
|
|
} else if (rc != -EINVAL) {
|
|
|
|
dev_err(&led->pdev->dev, "Unable to parse iled_thrsh_val, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (led->pdata->iled_thrsh_val > FLASH_LED_MITIGATION_THRSH_MAX) {
|
|
|
|
dev_err(&led->pdev->dev, "Invalid iled_thrsh_val specified\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2016-06-03 12:27:39 -07:00
|
|
|
led->pdata->all_ramp_up_done_irq =
|
|
|
|
of_irq_get_byname(node, "all-ramp-up-done-irq");
|
|
|
|
if (led->pdata->all_ramp_up_done_irq < 0)
|
|
|
|
dev_dbg(&led->pdev->dev, "all-ramp-up-done-irq not used\n");
|
|
|
|
|
|
|
|
led->pdata->all_ramp_down_done_irq =
|
|
|
|
of_irq_get_byname(node, "all-ramp-down-done-irq");
|
|
|
|
if (led->pdata->all_ramp_down_done_irq < 0)
|
|
|
|
dev_dbg(&led->pdev->dev, "all-ramp-down-done-irq not used\n");
|
|
|
|
|
2016-07-19 15:23:56 -07:00
|
|
|
led->pdata->led_fault_irq =
|
|
|
|
of_irq_get_byname(node, "led-fault-irq");
|
|
|
|
if (led->pdata->led_fault_irq < 0)
|
|
|
|
dev_dbg(&led->pdev->dev, "led-fault-irq not used\n");
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int qpnp_flash_led_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct qpnp_flash_led *led;
|
|
|
|
struct device_node *node, *temp;
|
2016-06-14 01:46:06 -07:00
|
|
|
const char *temp_string;
|
2016-03-01 02:34:54 -08:00
|
|
|
unsigned int base;
|
2016-08-17 16:19:32 -07:00
|
|
|
int rc, i = 0, j = 0;
|
2016-03-01 02:34:54 -08:00
|
|
|
|
|
|
|
node = pdev->dev.of_node;
|
|
|
|
if (!node) {
|
|
|
|
dev_info(&pdev->dev, "No flash LED nodes defined\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = of_property_read_u32(node, "reg", &base);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0) {
|
2016-03-01 02:34:54 -08:00
|
|
|
dev_err(&pdev->dev, "Couldn't find reg in node %s, rc = %d\n",
|
|
|
|
node->full_name, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
led = devm_kzalloc(&pdev->dev, sizeof(struct qpnp_flash_led),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!led)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
|
|
|
if (!led->regmap) {
|
|
|
|
dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
led->base = base;
|
|
|
|
led->pdev = pdev;
|
|
|
|
led->pdata = devm_kzalloc(&pdev->dev,
|
|
|
|
sizeof(struct flash_led_platform_data), GFP_KERNEL);
|
|
|
|
if (!led->pdata)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
rc = qpnp_flash_led_parse_common_dt(led, node);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0) {
|
2016-03-01 02:34:54 -08:00
|
|
|
dev_err(&pdev->dev,
|
|
|
|
"Failed to parse common flash LED device tree\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
for_each_available_child_of_node(node, temp) {
|
|
|
|
rc = of_property_read_string(temp, "label", &temp_string);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&pdev->dev,
|
|
|
|
"Failed to parse label, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp("switch", temp_string)) {
|
|
|
|
led->num_snodes++;
|
|
|
|
} else if (!strcmp("flash", temp_string) ||
|
|
|
|
!strcmp("torch", temp_string)) {
|
|
|
|
led->num_fnodes++;
|
|
|
|
} else {
|
|
|
|
dev_err(&pdev->dev,
|
|
|
|
"Invalid label for led node\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!led->num_fnodes) {
|
2016-03-01 02:34:54 -08:00
|
|
|
dev_err(&pdev->dev, "No LED nodes defined\n");
|
|
|
|
return -ECHILD;
|
|
|
|
}
|
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
led->fnode = devm_kcalloc(&pdev->dev, led->num_fnodes,
|
|
|
|
sizeof(*led->fnode),
|
|
|
|
GFP_KERNEL);
|
2016-03-01 02:34:54 -08:00
|
|
|
if (!led->fnode)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
led->snode = devm_kcalloc(&pdev->dev, led->num_snodes,
|
|
|
|
sizeof(*led->snode),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!led->snode)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
temp = NULL;
|
2016-10-24 20:22:19 -07:00
|
|
|
i = 0;
|
|
|
|
j = 0;
|
|
|
|
for_each_available_child_of_node(node, temp) {
|
|
|
|
rc = of_property_read_string(temp, "label", &temp_string);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0) {
|
2016-03-01 02:34:54 -08:00
|
|
|
dev_err(&pdev->dev,
|
2016-10-24 20:22:19 -07:00
|
|
|
"Failed to parse label, rc=%d\n", rc);
|
|
|
|
return rc;
|
2016-03-01 02:34:54 -08:00
|
|
|
}
|
|
|
|
|
2016-10-24 20:22:19 -07:00
|
|
|
if (!strcmp("flash", temp_string) ||
|
|
|
|
!strcmp("torch", temp_string)) {
|
|
|
|
rc = qpnp_flash_led_parse_each_led_dt(led,
|
|
|
|
&led->fnode[i++], temp);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&pdev->dev, "Unable to parse flash node %d rc=%d\n",
|
|
|
|
i, rc);
|
|
|
|
goto error_led_register;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp("switch", temp_string)) {
|
|
|
|
rc = qpnp_flash_led_parse_and_register_switch(led,
|
|
|
|
&led->snode[j++], temp);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&pdev->dev, "Unable to parse and register switch node, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
goto error_switch_register;
|
|
|
|
}
|
2016-06-14 01:46:06 -07:00
|
|
|
}
|
2016-03-01 02:34:54 -08:00
|
|
|
}
|
|
|
|
|
2016-06-03 12:27:39 -07:00
|
|
|
/* setup irqs */
|
|
|
|
if (led->pdata->all_ramp_up_done_irq >= 0) {
|
|
|
|
rc = devm_request_threaded_irq(&led->pdev->dev,
|
|
|
|
led->pdata->all_ramp_up_done_irq,
|
|
|
|
NULL, qpnp_flash_led_irq_handler,
|
|
|
|
IRQF_ONESHOT,
|
|
|
|
"qpnp_flash_led_all_ramp_up_done_irq", led);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&pdev->dev,
|
|
|
|
"Unable to request all_ramp_up_done(%d) IRQ(err:%d)\n",
|
|
|
|
led->pdata->all_ramp_up_done_irq, rc);
|
2016-07-07 18:09:08 -07:00
|
|
|
goto error_switch_register;
|
2016-06-03 12:27:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (led->pdata->all_ramp_down_done_irq >= 0) {
|
|
|
|
rc = devm_request_threaded_irq(&led->pdev->dev,
|
|
|
|
led->pdata->all_ramp_down_done_irq,
|
|
|
|
NULL, qpnp_flash_led_irq_handler,
|
|
|
|
IRQF_ONESHOT,
|
|
|
|
"qpnp_flash_led_all_ramp_down_done_irq", led);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&pdev->dev,
|
|
|
|
"Unable to request all_ramp_down_done(%d) IRQ(err:%d)\n",
|
|
|
|
led->pdata->all_ramp_down_done_irq, rc);
|
2016-07-07 18:09:08 -07:00
|
|
|
goto error_switch_register;
|
2016-06-03 12:27:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-19 15:23:56 -07:00
|
|
|
if (led->pdata->led_fault_irq >= 0) {
|
|
|
|
rc = devm_request_threaded_irq(&led->pdev->dev,
|
|
|
|
led->pdata->led_fault_irq,
|
|
|
|
NULL, qpnp_flash_led_irq_handler,
|
|
|
|
IRQF_ONESHOT,
|
|
|
|
"qpnp_flash_led_fault_irq", led);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&pdev->dev,
|
|
|
|
"Unable to request led_fault(%d) IRQ(err:%d)\n",
|
|
|
|
led->pdata->led_fault_irq, rc);
|
2016-07-07 18:09:08 -07:00
|
|
|
goto error_switch_register;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
led->bms_psy = power_supply_get_by_name("bms");
|
|
|
|
if (!led->bms_psy) {
|
|
|
|
rc = flash_led_psy_register_notifier(led);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&pdev->dev, "Couldn't register psy notifier, rc = %d\n",
|
|
|
|
rc);
|
|
|
|
goto error_switch_register;
|
2016-07-19 15:23:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
rc = qpnp_flash_led_init_settings(led);
|
2016-06-14 01:46:06 -07:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&pdev->dev,
|
|
|
|
"Failed to initialize flash LED, rc=%d\n", rc);
|
2016-07-07 18:09:08 -07:00
|
|
|
goto unreg_notifier;
|
2016-03-01 02:34:54 -08:00
|
|
|
}
|
|
|
|
|
2016-08-17 16:19:32 -07:00
|
|
|
for (i = 0; i < led->num_snodes; i++) {
|
|
|
|
for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++) {
|
|
|
|
rc = sysfs_create_file(&led->snode[i].cdev.dev->kobj,
|
|
|
|
&qpnp_flash_led_attrs[j].attr);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&pdev->dev, "sysfs creation failed, rc=%d\n",
|
|
|
|
rc);
|
|
|
|
goto sysfs_fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-01 02:34:54 -08:00
|
|
|
spin_lock_init(&led->lock);
|
|
|
|
|
|
|
|
dev_set_drvdata(&pdev->dev, led);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
2016-08-17 16:19:32 -07:00
|
|
|
sysfs_fail:
|
|
|
|
for (--j; j >= 0; j--)
|
|
|
|
sysfs_remove_file(&led->snode[i].cdev.dev->kobj,
|
|
|
|
&qpnp_flash_led_attrs[j].attr);
|
|
|
|
|
|
|
|
for (--i; i >= 0; i--) {
|
|
|
|
for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++)
|
|
|
|
sysfs_remove_file(&led->snode[i].cdev.dev->kobj,
|
|
|
|
&qpnp_flash_led_attrs[j].attr);
|
|
|
|
}
|
|
|
|
|
|
|
|
i = led->num_snodes;
|
2016-07-07 18:09:08 -07:00
|
|
|
unreg_notifier:
|
|
|
|
power_supply_unreg_notifier(&led->nb);
|
2016-03-01 02:34:54 -08:00
|
|
|
error_switch_register:
|
2016-06-14 01:46:06 -07:00
|
|
|
while (i > 0)
|
|
|
|
led_classdev_unregister(&led->snode[--i].cdev);
|
|
|
|
i = led->num_fnodes;
|
2016-03-01 02:34:54 -08:00
|
|
|
error_led_register:
|
|
|
|
while (i > 0)
|
|
|
|
led_classdev_unregister(&led->fnode[--i].cdev);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int qpnp_flash_led_remove(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct qpnp_flash_led *led = dev_get_drvdata(&pdev->dev);
|
2016-08-17 16:19:32 -07:00
|
|
|
int i, j;
|
2016-06-14 01:46:06 -07:00
|
|
|
|
|
|
|
for (i = 0; i < led->num_snodes; i++) {
|
2016-08-17 16:19:32 -07:00
|
|
|
for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++)
|
|
|
|
sysfs_remove_file(&led->snode[i].cdev.dev->kobj,
|
|
|
|
&qpnp_flash_led_attrs[j].attr);
|
|
|
|
|
2016-10-24 20:22:19 -07:00
|
|
|
if (led->snode[i].regulator_on)
|
|
|
|
qpnp_flash_led_regulator_enable(led,
|
2016-06-14 01:46:06 -07:00
|
|
|
&led->snode[i], false);
|
2016-06-02 11:59:58 -07:00
|
|
|
}
|
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
while (i > 0)
|
|
|
|
led_classdev_unregister(&led->snode[--i].cdev);
|
2016-08-17 16:19:32 -07:00
|
|
|
|
2016-06-14 01:46:06 -07:00
|
|
|
i = led->num_fnodes;
|
2016-03-01 02:34:54 -08:00
|
|
|
while (i > 0)
|
|
|
|
led_classdev_unregister(&led->fnode[--i].cdev);
|
|
|
|
|
2016-07-07 18:09:08 -07:00
|
|
|
power_supply_unreg_notifier(&led->nb);
|
2016-03-01 02:34:54 -08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const struct of_device_id qpnp_flash_led_match_table[] = {
|
|
|
|
{ .compatible = "qcom,qpnp-flash-led-v2",},
|
|
|
|
{ },
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct platform_driver qpnp_flash_led_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "qcom,qpnp-flash-led-v2",
|
|
|
|
.of_match_table = qpnp_flash_led_match_table,
|
|
|
|
},
|
|
|
|
.probe = qpnp_flash_led_probe,
|
|
|
|
.remove = qpnp_flash_led_remove,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init qpnp_flash_led_init(void)
|
|
|
|
{
|
|
|
|
return platform_driver_register(&qpnp_flash_led_driver);
|
|
|
|
}
|
|
|
|
late_initcall(qpnp_flash_led_init);
|
|
|
|
|
|
|
|
static void __exit qpnp_flash_led_exit(void)
|
|
|
|
{
|
|
|
|
platform_driver_unregister(&qpnp_flash_led_driver);
|
|
|
|
}
|
|
|
|
module_exit(qpnp_flash_led_exit);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("QPNP Flash LED driver v2");
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
MODULE_ALIAS("leds:leds-qpnp-flash-v2");
|