/* Copyright (c) 2014-2017, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* base addresses */ #define QPNP_WLED_CTRL_BASE "qpnp-wled-ctrl-base" #define QPNP_WLED_SINK_BASE "qpnp-wled-sink-base" /* ctrl registers */ #define QPNP_WLED_FAULT_STATUS(b) (b + 0x08) #define QPNP_WLED_INT_RT_STS(b) (b + 0x10) #define QPNP_WLED_EN_REG(b) (b + 0x46) #define QPNP_WLED_FDBK_OP_REG(b) (b + 0x48) #define QPNP_WLED_VREF_REG(b) (b + 0x49) #define QPNP_WLED_BOOST_DUTY_REG(b) (b + 0x4B) #define QPNP_WLED_SWITCH_FREQ_REG(b) (b + 0x4C) #define QPNP_WLED_OVP_REG(b) (b + 0x4D) #define QPNP_WLED_ILIM_REG(b) (b + 0x4E) #define QPNP_WLED_AMOLED_VOUT_REG(b) (b + 0x4F) #define QPNP_WLED_SOFTSTART_RAMP_DLY(b) (b + 0x53) #define QPNP_WLED_VLOOP_COMP_RES_REG(b) (b + 0x55) #define QPNP_WLED_VLOOP_COMP_GM_REG(b) (b + 0x56) #define QPNP_WLED_EN_PSM_REG(b) (b + 0x5A) #define QPNP_WLED_PSM_CTRL_REG(b) (b + 0x5B) #define QPNP_WLED_LCD_AUTO_PFM_REG(b) (b + 0x5C) #define QPNP_WLED_SC_PRO_REG(b) (b + 0x5E) #define QPNP_WLED_SWIRE_AVDD_REG(b) (b + 0x5F) #define QPNP_WLED_CTRL_SPARE_REG(b) (b + 0xDF) #define QPNP_WLED_TEST1_REG(b) (b + 0xE2) #define QPNP_WLED_TEST4_REG(b) (b + 0xE5) #define QPNP_WLED_REF_7P7_TRIM_REG(b) (b + 0xF2) #define QPNP_WLED_7P7_TRIM_MASK GENMASK(3, 0) #define QPNP_WLED_EN_MASK 0x7F #define QPNP_WLED_EN_SHIFT 7 #define QPNP_WLED_FDBK_OP_MASK 0xF8 #define QPNP_WLED_VREF_MASK GENMASK(3, 0) #define QPNP_WLED_VLOOP_COMP_RES_MASK 0xF0 #define QPNP_WLED_VLOOP_COMP_RES_OVERWRITE 0x80 #define QPNP_WLED_LOOP_COMP_RES_STEP_KOHM 20 #define QPNP_WLED_LOOP_COMP_RES_MIN_KOHM 20 #define QPNP_WLED_LOOP_COMP_RES_MAX_KOHM 320 #define QPNP_WLED_VLOOP_COMP_GM_MASK GENMASK(3, 0) #define QPNP_WLED_VLOOP_COMP_GM_OVERWRITE 0x80 #define QPNP_WLED_VLOOP_COMP_AUTO_GM_EN BIT(6) #define QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_MASK GENMASK(5, 4) #define QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_SHIFT 4 #define QPNP_WLED_LOOP_EA_GM_DFLT_AMOLED_PMI8994 0x03 #define QPNP_WLED_LOOP_GM_DFLT_AMOLED_PMI8998 0x09 #define QPNP_WLED_LOOP_GM_DFLT_WLED 0x09 #define QPNP_WLED_LOOP_EA_GM_MIN 0x0 #define QPNP_WLED_LOOP_EA_GM_MAX 0xF #define QPNP_WLED_LOOP_AUTO_GM_THRESH_MAX 3 #define QPNP_WLED_LOOP_AUTO_GM_DFLT_THRESH 1 #define QPNP_WLED_VREF_PSM_MASK 0xF8 #define QPNP_WLED_VREF_PSM_STEP_MV 50 #define QPNP_WLED_VREF_PSM_MIN_MV 400 #define QPNP_WLED_VREF_PSM_MAX_MV 750 #define QPNP_WLED_VREF_PSM_DFLT_AMOLED_MV 450 #define QPNP_WLED_PSM_OVERWRITE_BIT BIT(7) #define QPNP_WLED_LCD_AUTO_PFM_DFLT_THRESH 1 #define QPNP_WLED_LCD_AUTO_PFM_THRESH_MAX 0xF #define QPNP_WLED_LCD_AUTO_PFM_EN_SHIFT 7 #define QPNP_WLED_LCD_AUTO_PFM_EN_BIT BIT(7) #define QPNP_WLED_LCD_AUTO_PFM_THRESH_MASK GENMASK(3, 0) #define QPNP_WLED_EN_PSM_BIT BIT(7) #define QPNP_WLED_ILIM_MASK GENMASK(2, 0) #define QPNP_WLED_ILIM_OVERWRITE BIT(7) #define PMI8994_WLED_ILIM_MIN_MA 105 #define PMI8994_WLED_ILIM_MAX_MA 1980 #define PMI8994_WLED_DFLT_ILIM_MA 980 #define PMI8994_AMOLED_DFLT_ILIM_MA 385 #define PMI8998_WLED_ILIM_MAX_MA 1500 #define PMI8998_WLED_DFLT_ILIM_MA 970 #define PMI8998_AMOLED_DFLT_ILIM_MA 620 #define QPNP_WLED_BOOST_DUTY_MASK 0xFC #define QPNP_WLED_BOOST_DUTY_STEP_NS 52 #define QPNP_WLED_BOOST_DUTY_MIN_NS 26 #define QPNP_WLED_BOOST_DUTY_MAX_NS 156 #define QPNP_WLED_DEF_BOOST_DUTY_NS 104 #define QPNP_WLED_SWITCH_FREQ_MASK GENMASK(3, 0) #define QPNP_WLED_SWITCH_FREQ_OVERWRITE BIT(7) #define QPNP_WLED_OVP_MASK GENMASK(1, 0) #define QPNP_WLED_TEST4_EN_DEB_BYPASS_ILIM_BIT BIT(6) #define QPNP_WLED_TEST4_EN_SH_FOR_SS_BIT BIT(5) #define QPNP_WLED_TEST4_EN_CLAMP_BIT BIT(4) #define QPNP_WLED_TEST4_EN_SOFT_START_BIT BIT(1) #define QPNP_WLED_TEST4_EN_VREF_UP \ (QPNP_WLED_TEST4_EN_SH_FOR_SS_BIT | \ QPNP_WLED_TEST4_EN_CLAMP_BIT | \ QPNP_WLED_TEST4_EN_SOFT_START_BIT) #define QPNP_WLED_TEST4_EN_IIND_UP 0x1 #define QPNP_WLED_ILIM_FAULT_BIT BIT(0) #define QPNP_WLED_OVP_FAULT_BIT BIT(1) #define QPNP_WLED_SC_FAULT_BIT BIT(2) #define QPNP_WLED_OVP_FLT_RT_STS_BIT BIT(1) /* QPNP_WLED_SOFTSTART_RAMP_DLY */ #define SOFTSTART_OVERWRITE_BIT BIT(7) #define SOFTSTART_RAMP_DELAY_MASK GENMASK(2, 0) /* sink registers */ #define QPNP_WLED_CURR_SINK_REG(b) (b + 0x46) #define QPNP_WLED_SYNC_REG(b) (b + 0x47) #define QPNP_WLED_MOD_REG(b) (b + 0x4A) #define QPNP_WLED_HYB_THRES_REG(b) (b + 0x4B) #define QPNP_WLED_MOD_EN_REG(b, n) (b + 0x50 + (n * 0x10)) #define QPNP_WLED_SYNC_DLY_REG(b, n) (QPNP_WLED_MOD_EN_REG(b, n) + 0x01) #define QPNP_WLED_FS_CURR_REG(b, n) (QPNP_WLED_MOD_EN_REG(b, n) + 0x02) #define QPNP_WLED_CABC_REG(b, n) (QPNP_WLED_MOD_EN_REG(b, n) + 0x06) #define QPNP_WLED_BRIGHT_LSB_REG(b, n) (QPNP_WLED_MOD_EN_REG(b, n) + 0x07) #define QPNP_WLED_BRIGHT_MSB_REG(b, n) (QPNP_WLED_MOD_EN_REG(b, n) + 0x08) #define QPNP_WLED_SINK_TEST5_REG(b) (b + 0xE6) #define QPNP_WLED_MOD_FREQ_1200_KHZ 1200 #define QPNP_WLED_MOD_FREQ_2400_KHZ 2400 #define QPNP_WLED_MOD_FREQ_9600_KHZ 9600 #define QPNP_WLED_MOD_FREQ_19200_KHZ 19200 #define QPNP_WLED_MOD_FREQ_MASK 0x3F #define QPNP_WLED_MOD_FREQ_SHIFT 6 #define QPNP_WLED_ACC_CLK_FREQ_MASK 0xE7 #define QPNP_WLED_ACC_CLK_FREQ_SHIFT 3 #define QPNP_WLED_PHASE_STAG_MASK 0xDF #define QPNP_WLED_PHASE_STAG_SHIFT 5 #define QPNP_WLED_DIM_RES_MASK 0xFD #define QPNP_WLED_DIM_RES_SHIFT 1 #define QPNP_WLED_DIM_HYB_MASK 0xFB #define QPNP_WLED_DIM_HYB_SHIFT 2 #define QPNP_WLED_DIM_ANA_MASK 0xFE #define QPNP_WLED_HYB_THRES_MASK 0xF8 #define QPNP_WLED_HYB_THRES_MIN 78 #define QPNP_WLED_DEF_HYB_THRES 625 #define QPNP_WLED_HYB_THRES_MAX 10000 #define QPNP_WLED_MOD_EN_MASK 0x7F #define QPNP_WLED_MOD_EN_SHFT 7 #define QPNP_WLED_MOD_EN 1 #define QPNP_WLED_GATE_DRV_MASK 0xFE #define QPNP_WLED_SYNC_DLY_MASK GENMASK(2, 0) #define QPNP_WLED_SYNC_DLY_MIN_US 0 #define QPNP_WLED_SYNC_DLY_MAX_US 1400 #define QPNP_WLED_SYNC_DLY_STEP_US 200 #define QPNP_WLED_DEF_SYNC_DLY_US 400 #define QPNP_WLED_FS_CURR_MASK GENMASK(3, 0) #define QPNP_WLED_FS_CURR_MIN_UA 0 #define QPNP_WLED_FS_CURR_MAX_UA 30000 #define QPNP_WLED_FS_CURR_STEP_UA 2500 #define QPNP_WLED_CABC_MASK 0x80 #define QPNP_WLED_CABC_SHIFT 7 #define QPNP_WLED_CURR_SINK_SHIFT 4 #define QPNP_WLED_CURR_SINK_MASK GENMASK(7, 4) #define QPNP_WLED_BRIGHT_LSB_MASK 0xFF #define QPNP_WLED_BRIGHT_MSB_SHIFT 8 #define QPNP_WLED_BRIGHT_MSB_MASK 0x0F #define QPNP_WLED_SYNC 0x0F #define QPNP_WLED_SYNC_RESET 0x00 #define QPNP_WLED_SINK_TEST5_HYB 0x14 #define QPNP_WLED_SINK_TEST5_DIG 0x1E #define QPNP_WLED_SINK_TEST5_HVG_PULL_STR_BIT BIT(3) #define QPNP_WLED_SWITCH_FREQ_800_KHZ_CODE 0x0B #define QPNP_WLED_SWITCH_FREQ_1600_KHZ_CODE 0x05 #define QPNP_WLED_DISP_SEL_REG(b) (b + 0x44) #define QPNP_WLED_MODULE_RDY_REG(b) (b + 0x45) #define QPNP_WLED_MODULE_EN_REG(b) (b + 0x46) #define QPNP_WLED_MODULE_RDY_MASK 0x7F #define QPNP_WLED_MODULE_RDY_SHIFT 7 #define QPNP_WLED_MODULE_EN_MASK BIT(7) #define QPNP_WLED_MODULE_EN_SHIFT 7 #define QPNP_WLED_DISP_SEL_MASK 0x7F #define QPNP_WLED_DISP_SEL_SHIFT 7 #define QPNP_WLED_EN_SC_DEB_CYCLES_MASK 0x79 #define QPNP_WLED_EN_DEB_CYCLES_MASK 0xF9 #define QPNP_WLED_EN_SC_SHIFT 7 #define QPNP_WLED_SC_PRO_EN_DSCHGR 0x8 #define QPNP_WLED_SC_DEB_CYCLES_MIN 2 #define QPNP_WLED_SC_DEB_CYCLES_MAX 16 #define QPNP_WLED_SC_DEB_CYCLES_SUB 2 #define QPNP_WLED_SC_DEB_CYCLES_DFLT 4 #define QPNP_WLED_EXT_FET_DTEST2 0x09 #define QPNP_WLED_SEC_ACCESS_REG(b) (b + 0xD0) #define QPNP_WLED_SEC_UNLOCK 0xA5 #define NUM_DDIC_CODES 256 #define QPNP_WLED_MAX_STRINGS 4 #define QPNP_PM660_WLED_MAX_STRINGS 3 #define WLED_MAX_LEVEL_4095 4095 #define QPNP_WLED_RAMP_DLY_MS 20 #define QPNP_WLED_TRIGGER_NONE "none" #define QPNP_WLED_STR_SIZE 20 #define QPNP_WLED_MIN_MSLEEP 20 #define QPNP_WLED_SC_DLY_MS 20 #define QPNP_WLED_SOFT_START_DLY_US 10000 #define NUM_SUPPORTED_AVDD_VOLTAGES 6 #define QPNP_WLED_DFLT_AVDD_MV 7600 #define QPNP_WLED_AVDD_MIN_MV 5650 #define QPNP_WLED_AVDD_MAX_MV 7900 #define QPNP_WLED_AVDD_STEP_MV 150 #define QPNP_WLED_AVDD_MIN_TRIM_VAL 0x0 #define QPNP_WLED_AVDD_MAX_TRIM_VAL 0xF #define QPNP_WLED_AVDD_SEL_SPMI_BIT BIT(7) #define QPNP_WLED_AVDD_SET_BIT BIT(4) #define NUM_SUPPORTED_OVP_THRESHOLDS 4 #define NUM_SUPPORTED_ILIM_THRESHOLDS 8 #define QPNP_WLED_AVDD_MV_TO_REG(val) \ ((val - QPNP_WLED_AVDD_MIN_MV) / QPNP_WLED_AVDD_STEP_MV) /* output feedback mode */ enum qpnp_wled_fdbk_op { QPNP_WLED_FDBK_AUTO, QPNP_WLED_FDBK_WLED1, QPNP_WLED_FDBK_WLED2, QPNP_WLED_FDBK_WLED3, QPNP_WLED_FDBK_WLED4, }; /* dimming modes */ enum qpnp_wled_dim_mode { QPNP_WLED_DIM_ANALOG, QPNP_WLED_DIM_DIGITAL, QPNP_WLED_DIM_HYBRID, }; /* wled ctrl debug registers */ static u8 qpnp_wled_ctrl_dbg_regs[] = { 0x44, 0x46, 0x48, 0x49, 0x4b, 0x4c, 0x4d, 0x4e, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x5a, 0x5b, 0x5d, 0x5e, 0xe2 }; /* wled sink debug registers */ static u8 qpnp_wled_sink_dbg_regs[] = { 0x46, 0x47, 0x48, 0x4a, 0x4b, 0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x58, 0x60, 0x61, 0x62, 0x63, 0x66, 0x67, 0x68, 0x70, 0x71, 0x72, 0x73, 0x76, 0x77, 0x78, 0x80, 0x81, 0x82, 0x83, 0x86, 0x87, 0x88, 0xe6, }; static int qpnp_wled_avdd_target_voltages[NUM_SUPPORTED_AVDD_VOLTAGES] = { 7900, 7600, 7300, 6400, 6100, 5800, }; static u8 qpnp_wled_ovp_reg_settings[NUM_SUPPORTED_AVDD_VOLTAGES] = { 0x0, 0x0, 0x1, 0x2, 0x2, 0x3, }; static int qpnp_wled_avdd_trim_adjustments[NUM_SUPPORTED_AVDD_VOLTAGES] = { 3, 0, -2, 7, 3, 3, }; static int qpnp_wled_ovp_thresholds_pmi8994[NUM_SUPPORTED_OVP_THRESHOLDS] = { 31000, 29500, 19400, 17800, }; static int qpnp_wled_ovp_thresholds_pmi8998[NUM_SUPPORTED_OVP_THRESHOLDS] = { 31100, 29600, 19600, 18100, }; static int qpnp_wled_ilim_settings_pmi8994[NUM_SUPPORTED_ILIM_THRESHOLDS] = { 105, 385, 660, 980, 1150, 1420, 1700, 1980, }; static int qpnp_wled_ilim_settings_pmi8998[NUM_SUPPORTED_ILIM_THRESHOLDS] = { 105, 280, 450, 620, 970, 1150, 1300, 1500, }; struct wled_vref_setting { u32 min_uv; u32 max_uv; u32 step_uv; u32 default_uv; }; static struct wled_vref_setting vref_setting_pmi8994 = { 300000, 675000, 25000, 350000, }; static struct wled_vref_setting vref_setting_pmi8998 = { 60000, 397500, 22500, 127500, }; /** * qpnp_wled - wed data structure * @ cdev - led class device * @ pdev - platform device * @ work - worker for led operation * @ wq - workqueue for setting brightness level * @ lock - mutex lock for exclusive access * @ fdbk_op - output feedback mode * @ dim_mode - dimming mode * @ ovp_irq - over voltage protection irq * @ sc_irq - short circuit irq * @ sc_cnt - short circuit irq count * @ avdd_target_voltage_mv - target voltage for AVDD module in mV * @ ctrl_base - base address for wled ctrl * @ sink_base - base address for wled sink * @ mod_freq_khz - modulator frequency in KHZ * @ hyb_thres - threshold for hybrid dimming * @ sync_dly_us - sync delay in us * @ vref_uv - ref voltage in uv * @ vref_psm_mv - ref psm voltage in mv * @ loop_comp_res_kohm - control to select the compensation resistor * @ loop_ea_gm - control to select the gm for the gm stage in control loop * @ sc_deb_cycles - debounce time for short circuit detection * @ switch_freq_khz - switching frequency in KHZ * @ ovp_mv - over voltage protection in mv * @ ilim_ma - current limiter in ma * @ boost_duty_ns - boost duty cycle in ns * @ fs_curr_ua - full scale current in ua * @ ramp_ms - delay between ramp steps in ms * @ ramp_step - ramp step size * @ cons_sync_write_delay_us - delay between two consecutive writes to SYNC * @ auto_calibration_ovp_count - OVP fault irq count to run auto calibration * @ max_strings - Number of strings supported in WLED peripheral * @ prev_level - Previous brightness level * @ brt_map_table - Brightness map table * @ strings - supported list of strings * @ num_strings - number of strings * @ loop_auto_gm_thresh - the clamping level for auto gm * @ lcd_auto_pfm_thresh - the threshold for lcd auto pfm mode * @ loop_auto_gm_en - select if auto gm is enabled * @ lcd_auto_pfm_en - select if auto pfm is enabled in lcd mode * @ lcd_psm_ctrl - select if psm needs to be controlled in lcd mode * @ avdd_mode_spmi - enable avdd programming via spmi * @ en_9b_dim_res - enable or disable 9bit dimming * @ en_phase_stag - enable or disable phase staggering * @ en_cabc - enable or disable cabc * @ disp_type_amoled - type of display: LCD/AMOLED * @ en_ext_pfet_sc_pro - enable sc protection on external pfet * @ prev_state - previous state of WLED * @ stepper_en - Flag to enable stepper algorithm * @ ovp_irq_disabled - OVP interrupt disable status * @ auto_calib_enabled - Flag to enable auto calibration feature * @ auto_calib_done - Flag to indicate auto calibration is done * @ module_dis_perm - Flat to keep module permanently disabled * @ start_ovp_fault_time - Time when the OVP fault first occurred */ struct qpnp_wled { struct led_classdev cdev; struct platform_device *pdev; struct regmap *regmap; struct pmic_revid_data *pmic_rev_id; struct work_struct work; struct workqueue_struct *wq; struct mutex lock; struct mutex bus_lock; enum qpnp_wled_fdbk_op fdbk_op; enum qpnp_wled_dim_mode dim_mode; int ovp_irq; int sc_irq; u32 sc_cnt; u32 avdd_target_voltage_mv; u16 ctrl_base; u16 sink_base; u16 mod_freq_khz; u16 hyb_thres; u16 sync_dly_us; u32 vref_uv; u16 vref_psm_mv; u16 loop_comp_res_kohm; u16 loop_ea_gm; u16 sc_deb_cycles; u16 switch_freq_khz; u16 ovp_mv; u16 ilim_ma; u16 boost_duty_ns; u16 fs_curr_ua; u16 ramp_ms; u16 ramp_step; u16 cons_sync_write_delay_us; u16 auto_calibration_ovp_count; u16 max_strings; u16 prev_level; u16 *brt_map_table; u8 strings[QPNP_WLED_MAX_STRINGS]; u8 num_strings; u8 loop_auto_gm_thresh; u8 lcd_auto_pfm_thresh; bool loop_auto_gm_en; bool lcd_auto_pfm_en; bool lcd_psm_ctrl; bool avdd_mode_spmi; bool en_9b_dim_res; bool en_phase_stag; bool en_cabc; bool disp_type_amoled; bool en_ext_pfet_sc_pro; bool prev_state; bool stepper_en; bool ovp_irq_disabled; bool auto_calib_enabled; bool auto_calib_done; bool module_dis_perm; ktime_t start_ovp_fault_time; }; static int qpnp_wled_step_delay_us = 52000; module_param_named( total_step_delay_us, qpnp_wled_step_delay_us, int, 0600 ); static int qpnp_wled_step_size_threshold = 3; module_param_named( step_size_threshold, qpnp_wled_step_size_threshold, int, 0600 ); static int qpnp_wled_step_delay_gain = 2; module_param_named( step_delay_gain, qpnp_wled_step_delay_gain, int, 0600 ); /* helper to read a pmic register */ static int qpnp_wled_read_reg(struct qpnp_wled *wled, u16 addr, u8 *data) { int rc; uint val; rc = regmap_read(wled->regmap, addr, &val); if (rc < 0) { dev_err(&wled->pdev->dev, "Error reading address: %x(%d)\n", addr, rc); return rc; } *data = (u8)val; return 0; } /* helper to write a pmic register */ static int qpnp_wled_write_reg(struct qpnp_wled *wled, u16 addr, u8 data) { int rc; mutex_lock(&wled->bus_lock); rc = regmap_write(wled->regmap, addr, data); if (rc < 0) { dev_err(&wled->pdev->dev, "Error writing address: %x(%d)\n", addr, rc); goto out; } dev_dbg(&wled->pdev->dev, "wrote: WLED_0x%x = 0x%x\n", addr, data); out: mutex_unlock(&wled->bus_lock); return rc; } static int qpnp_wled_masked_write_reg(struct qpnp_wled *wled, u16 addr, u8 mask, u8 data) { int rc; mutex_lock(&wled->bus_lock); rc = regmap_update_bits(wled->regmap, addr, mask, data); if (rc < 0) { dev_err(&wled->pdev->dev, "Error writing address: %x(%d)\n", addr, rc); goto out; } dev_dbg(&wled->pdev->dev, "wrote: WLED_0x%x = 0x%x\n", addr, data); out: mutex_unlock(&wled->bus_lock); return rc; } static int qpnp_wled_sec_write_reg(struct qpnp_wled *wled, u16 addr, u8 data) { int rc; u8 reg = QPNP_WLED_SEC_UNLOCK; u16 base_addr = addr & 0xFF00; mutex_lock(&wled->bus_lock); rc = regmap_write(wled->regmap, QPNP_WLED_SEC_ACCESS_REG(base_addr), reg); if (rc < 0) { dev_err(&wled->pdev->dev, "Error writing address: %x(%d)\n", QPNP_WLED_SEC_ACCESS_REG(base_addr), rc); goto out; } rc = regmap_write(wled->regmap, addr, data); if (rc < 0) { dev_err(&wled->pdev->dev, "Error writing address: %x(%d)\n", addr, rc); goto out; } dev_dbg(&wled->pdev->dev, "wrote: WLED_0x%x = 0x%x\n", addr, data); out: mutex_unlock(&wled->bus_lock); return rc; } static int qpnp_wled_swire_avdd_config(struct qpnp_wled *wled) { int rc; u8 val; if (wled->pmic_rev_id->pmic_subtype != PMI8998_SUBTYPE && wled->pmic_rev_id->pmic_subtype != PM660L_SUBTYPE) return 0; if (!wled->disp_type_amoled || wled->avdd_mode_spmi) return 0; val = QPNP_WLED_AVDD_MV_TO_REG(wled->avdd_target_voltage_mv); rc = qpnp_wled_write_reg(wled, QPNP_WLED_SWIRE_AVDD_REG(wled->ctrl_base), val); return rc; } static int qpnp_wled_sync_reg_toggle(struct qpnp_wled *wled) { int rc; u8 reg; /* sync */ reg = QPNP_WLED_SYNC; rc = qpnp_wled_write_reg(wled, QPNP_WLED_SYNC_REG(wled->sink_base), reg); if (rc < 0) return rc; if (wled->cons_sync_write_delay_us) usleep_range(wled->cons_sync_write_delay_us, wled->cons_sync_write_delay_us + 1); reg = QPNP_WLED_SYNC_RESET; rc = qpnp_wled_write_reg(wled, QPNP_WLED_SYNC_REG(wled->sink_base), reg); if (rc < 0) return rc; return 0; } /* set wled to a level of brightness */ static int qpnp_wled_set_level(struct qpnp_wled *wled, int level) { int i, rc; u8 reg; u16 low_limit = WLED_MAX_LEVEL_4095 * 4 / 1000; /* WLED's lower limit of operation is 0.4% */ if (level > 0 && level < low_limit) level = low_limit; /* set brightness registers */ for (i = 0; i < wled->max_strings; i++) { reg = level & QPNP_WLED_BRIGHT_LSB_MASK; rc = qpnp_wled_write_reg(wled, QPNP_WLED_BRIGHT_LSB_REG(wled->sink_base, wled->strings[i]), reg); if (rc < 0) return rc; reg = level >> QPNP_WLED_BRIGHT_MSB_SHIFT; reg = reg & QPNP_WLED_BRIGHT_MSB_MASK; rc = qpnp_wled_write_reg(wled, QPNP_WLED_BRIGHT_MSB_REG(wled->sink_base, wled->strings[i]), reg); if (rc < 0) return rc; } rc = qpnp_wled_sync_reg_toggle(wled); if (rc < 0) { dev_err(&wled->pdev->dev, "Failed to toggle sync reg %d\n", rc); return rc; } pr_debug("level:%d\n", level); return 0; } static int qpnp_wled_set_map_level(struct qpnp_wled *wled, int level) { int rc, i; if (level < wled->prev_level) { for (i = wled->prev_level; i >= level; i--) { rc = qpnp_wled_set_level(wled, wled->brt_map_table[i]); if (rc < 0) { pr_err("set brightness level failed, rc:%d\n", rc); return rc; } } } else if (level > wled->prev_level) { for (i = wled->prev_level; i <= level; i++) { rc = qpnp_wled_set_level(wled, wled->brt_map_table[i]); if (rc < 0) { pr_err("set brightness level failed, rc:%d\n", rc); return rc; } } } return 0; } static int qpnp_wled_set_step_level(struct qpnp_wled *wled, int new_level) { int rc, i, num_steps, delay_us; u16 level, start_level, end_level, step_size; bool level_inc = false; level = wled->prev_level; start_level = wled->brt_map_table[level]; end_level = wled->brt_map_table[new_level]; level_inc = (new_level > level); num_steps = abs(start_level - end_level); if (!num_steps) return 0; delay_us = qpnp_wled_step_delay_us / num_steps; pr_debug("level goes from [%d %d] num_steps: %d, delay: %d\n", start_level, end_level, num_steps, delay_us); if (delay_us < 500) { step_size = 1000 / delay_us; num_steps = num_steps / step_size; delay_us = 1000; } else { if (num_steps < qpnp_wled_step_size_threshold) delay_us *= qpnp_wled_step_delay_gain; step_size = 1; } i = start_level; while (num_steps--) { if (level_inc) i += step_size; else i -= step_size; rc = qpnp_wled_set_level(wled, i); if (rc < 0) return rc; if (delay_us > 0) { if (delay_us < 20000) usleep_range(delay_us, delay_us + 1); else msleep(delay_us / USEC_PER_MSEC); } } if (i != end_level) { i = end_level; rc = qpnp_wled_set_level(wled, i); if (rc < 0) return rc; } return 0; } static int qpnp_wled_psm_config(struct qpnp_wled *wled, bool enable) { int rc; if (!wled->lcd_psm_ctrl) return 0; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_EN_PSM_REG(wled->ctrl_base), QPNP_WLED_EN_PSM_BIT, enable ? QPNP_WLED_EN_PSM_BIT : 0); if (rc < 0) return rc; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_PSM_CTRL_REG(wled->ctrl_base), QPNP_WLED_PSM_OVERWRITE_BIT, enable ? QPNP_WLED_PSM_OVERWRITE_BIT : 0); if (rc < 0) return rc; return 0; } static int qpnp_wled_module_en(struct qpnp_wled *wled, u16 base_addr, bool state) { int rc; if (wled->module_dis_perm) return 0; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(base_addr), QPNP_WLED_MODULE_EN_MASK, state << QPNP_WLED_MODULE_EN_SHIFT); if (rc < 0) return rc; /* * Wait for at least 10ms before enabling OVP fault interrupt after * enabling the module so that soft start is completed. Also, this * delay can be used to control PSM during enable when required. Keep * OVP interrupt disabled when the module is disabled. */ if (state) { usleep_range(QPNP_WLED_SOFT_START_DLY_US, QPNP_WLED_SOFT_START_DLY_US + 1000); rc = qpnp_wled_psm_config(wled, false); if (rc < 0) return rc; if (wled->ovp_irq > 0 && wled->ovp_irq_disabled) { enable_irq(wled->ovp_irq); wled->ovp_irq_disabled = false; } } else { if (wled->ovp_irq > 0 && !wled->ovp_irq_disabled) { disable_irq(wled->ovp_irq); wled->ovp_irq_disabled = true; } rc = qpnp_wled_psm_config(wled, true); if (rc < 0) return rc; } return 0; } /* sysfs store function for ramp */ static ssize_t qpnp_wled_ramp_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct qpnp_wled *wled = dev_get_drvdata(dev); int i, rc; mutex_lock(&wled->lock); if (!wled->cdev.brightness) { rc = qpnp_wled_module_en(wled, wled->ctrl_base, true); if (rc) { dev_err(&wled->pdev->dev, "wled enable failed\n"); goto unlock_mutex; } } /* ramp up */ for (i = 0; i <= wled->cdev.max_brightness;) { rc = qpnp_wled_set_level(wled, i); if (rc) { dev_err(&wled->pdev->dev, "wled set level failed\n"); goto restore_brightness; } if (wled->ramp_ms < QPNP_WLED_MIN_MSLEEP) usleep_range(wled->ramp_ms * USEC_PER_MSEC, wled->ramp_ms * USEC_PER_MSEC); else msleep(wled->ramp_ms); if (i == wled->cdev.max_brightness) break; i += wled->ramp_step; if (i > wled->cdev.max_brightness) i = wled->cdev.max_brightness; } /* ramp down */ for (i = wled->cdev.max_brightness; i >= 0;) { rc = qpnp_wled_set_level(wled, i); if (rc) { dev_err(&wled->pdev->dev, "wled set level failed\n"); goto restore_brightness; } if (wled->ramp_ms < QPNP_WLED_MIN_MSLEEP) usleep_range(wled->ramp_ms * USEC_PER_MSEC, wled->ramp_ms * USEC_PER_MSEC); else msleep(wled->ramp_ms); if (i == 0) break; i -= wled->ramp_step; if (i < 0) i = 0; } dev_info(&wled->pdev->dev, "wled ramp complete\n"); restore_brightness: /* restore the old brightness */ qpnp_wled_set_level(wled, wled->cdev.brightness); if (!wled->cdev.brightness) { rc = qpnp_wled_module_en(wled, wled->ctrl_base, false); if (rc) dev_err(&wled->pdev->dev, "wled enable failed\n"); } unlock_mutex: mutex_unlock(&wled->lock); return count; } static int qpnp_wled_dump_regs(struct qpnp_wled *wled, u16 base_addr, u8 dbg_regs[], u8 size, char *label, int count, char *buf) { int i, rc; u8 reg; for (i = 0; i < size; i++) { rc = qpnp_wled_read_reg(wled, base_addr + dbg_regs[i], ®); if (rc < 0) return rc; count += snprintf(buf + count, PAGE_SIZE - count, "%s: REG_0x%x = 0x%x\n", label, base_addr + dbg_regs[i], reg); if (count >= PAGE_SIZE) return PAGE_SIZE - 1; } return count; } /* sysfs show function for debug registers */ static ssize_t qpnp_wled_dump_regs_show(struct device *dev, struct device_attribute *attr, char *buf) { struct qpnp_wled *wled = dev_get_drvdata(dev); int count = 0; count = qpnp_wled_dump_regs(wled, wled->ctrl_base, qpnp_wled_ctrl_dbg_regs, ARRAY_SIZE(qpnp_wled_ctrl_dbg_regs), "wled_ctrl", count, buf); if (count < 0 || count == PAGE_SIZE - 1) return count; count = qpnp_wled_dump_regs(wled, wled->sink_base, qpnp_wled_sink_dbg_regs, ARRAY_SIZE(qpnp_wled_sink_dbg_regs), "wled_sink", count, buf); if (count < 0 || count == PAGE_SIZE - 1) return count; return count; } /* sysfs show function for ramp delay in each step */ static ssize_t qpnp_wled_ramp_ms_show(struct device *dev, struct device_attribute *attr, char *buf) { struct qpnp_wled *wled = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", wled->ramp_ms); } /* sysfs store function for ramp delay in each step */ static ssize_t qpnp_wled_ramp_ms_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct qpnp_wled *wled = dev_get_drvdata(dev); int data, rc; rc = kstrtoint(buf, 10, &data); if (rc) return rc; wled->ramp_ms = data; return count; } /* sysfs show function for ramp step */ static ssize_t qpnp_wled_ramp_step_show(struct device *dev, struct device_attribute *attr, char *buf) { struct qpnp_wled *wled = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", wled->ramp_step); } /* sysfs store function for ramp step */ static ssize_t qpnp_wled_ramp_step_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct qpnp_wled *wled = dev_get_drvdata(dev); int data, rc; rc = kstrtoint(buf, 10, &data); if (rc) return rc; wled->ramp_step = data; return count; } /* sysfs show function for dim mode */ static ssize_t qpnp_wled_dim_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct qpnp_wled *wled = dev_get_drvdata(dev); char *str; if (wled->dim_mode == QPNP_WLED_DIM_ANALOG) str = "analog"; else if (wled->dim_mode == QPNP_WLED_DIM_DIGITAL) str = "digital"; else str = "hybrid"; return snprintf(buf, PAGE_SIZE, "%s\n", str); } /* sysfs store function for dim mode*/ static ssize_t qpnp_wled_dim_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct qpnp_wled *wled = dev_get_drvdata(dev); char str[QPNP_WLED_STR_SIZE + 1]; int rc, temp; u8 reg; if (snprintf(str, QPNP_WLED_STR_SIZE, "%s", buf) > QPNP_WLED_STR_SIZE) return -EINVAL; if (strcmp(str, "analog") == 0) temp = QPNP_WLED_DIM_ANALOG; else if (strcmp(str, "digital") == 0) temp = QPNP_WLED_DIM_DIGITAL; else temp = QPNP_WLED_DIM_HYBRID; if (temp == wled->dim_mode) return count; rc = qpnp_wled_read_reg(wled, QPNP_WLED_MOD_REG(wled->sink_base), ®); if (rc < 0) return rc; if (temp == QPNP_WLED_DIM_HYBRID) { reg &= QPNP_WLED_DIM_HYB_MASK; reg |= (1 << QPNP_WLED_DIM_HYB_SHIFT); } else { reg &= QPNP_WLED_DIM_HYB_MASK; reg |= (0 << QPNP_WLED_DIM_HYB_SHIFT); reg &= QPNP_WLED_DIM_ANA_MASK; reg |= temp; } rc = qpnp_wled_write_reg(wled, QPNP_WLED_MOD_REG(wled->sink_base), reg); if (rc) return rc; wled->dim_mode = temp; return count; } /* sysfs show function for full scale current in ua*/ static ssize_t qpnp_wled_fs_curr_ua_show(struct device *dev, struct device_attribute *attr, char *buf) { struct qpnp_wled *wled = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", wled->fs_curr_ua); } /* sysfs store function for full scale current in ua*/ static ssize_t qpnp_wled_fs_curr_ua_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct qpnp_wled *wled = dev_get_drvdata(dev); int data, i, rc; u8 reg; rc = kstrtoint(buf, 10, &data); if (rc) return rc; for (i = 0; i < wled->max_strings; i++) { if (data < QPNP_WLED_FS_CURR_MIN_UA) data = QPNP_WLED_FS_CURR_MIN_UA; else if (data > QPNP_WLED_FS_CURR_MAX_UA) data = QPNP_WLED_FS_CURR_MAX_UA; reg = data / QPNP_WLED_FS_CURR_STEP_UA; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, i), QPNP_WLED_FS_CURR_MASK, reg); if (rc < 0) return rc; } wled->fs_curr_ua = data; rc = qpnp_wled_sync_reg_toggle(wled); if (rc < 0) { dev_err(&wled->pdev->dev, "Failed to toggle sync reg %d\n", rc); return rc; } return count; } /* sysfs attributes exported by wled */ static struct device_attribute qpnp_wled_attrs[] = { __ATTR(dump_regs, 0664, qpnp_wled_dump_regs_show, NULL), __ATTR(dim_mode, 0664, qpnp_wled_dim_mode_show, qpnp_wled_dim_mode_store), __ATTR(fs_curr_ua, 0664, qpnp_wled_fs_curr_ua_show, qpnp_wled_fs_curr_ua_store), __ATTR(start_ramp, 0664, NULL, qpnp_wled_ramp_store), __ATTR(ramp_ms, 0664, qpnp_wled_ramp_ms_show, qpnp_wled_ramp_ms_store), __ATTR(ramp_step, 0664, qpnp_wled_ramp_step_show, qpnp_wled_ramp_step_store), }; /* worker for setting wled brightness */ static void qpnp_wled_work(struct work_struct *work) { struct qpnp_wled *wled; int level, level_255, rc; wled = container_of(work, struct qpnp_wled, work); mutex_lock(&wled->lock); level = wled->cdev.brightness; if (wled->brt_map_table) { /* * Change the 12 bit level to 8 bit level and use the mapped * values for 12 bit level from brightness map table. */ level_255 = DIV_ROUND_CLOSEST(level, 16); if (level_255 > 255) level_255 = 255; pr_debug("level: %d level_255: %d\n", level, level_255); if (wled->stepper_en) rc = qpnp_wled_set_step_level(wled, level_255); else rc = qpnp_wled_set_map_level(wled, level_255); if (rc) { dev_err(&wled->pdev->dev, "wled set level failed\n"); goto unlock_mutex; } wled->prev_level = level_255; } else if (level) { rc = qpnp_wled_set_level(wled, level); if (rc) { dev_err(&wled->pdev->dev, "wled set level failed\n"); goto unlock_mutex; } } if (!!level != wled->prev_state) { if (!!level) { /* * For AMOLED display in pmi8998, SWIRE_AVDD_DEFAULT has * to be reconfigured every time the module is enabled. */ rc = qpnp_wled_swire_avdd_config(wled); if (rc < 0) { pr_err("Write to SWIRE_AVDD_DEFAULT register failed rc:%d\n", rc); goto unlock_mutex; } } rc = qpnp_wled_module_en(wled, wled->ctrl_base, !!level); if (rc) { dev_err(&wled->pdev->dev, "wled %sable failed\n", level ? "en" : "dis"); goto unlock_mutex; } } wled->prev_state = !!level; unlock_mutex: mutex_unlock(&wled->lock); } /* get api registered with led classdev for wled brightness */ static enum led_brightness qpnp_wled_get(struct led_classdev *led_cdev) { struct qpnp_wled *wled; wled = container_of(led_cdev, struct qpnp_wled, cdev); return wled->cdev.brightness; } /* set api registered with led classdev for wled brightness */ static void qpnp_wled_set(struct led_classdev *led_cdev, enum led_brightness level) { struct qpnp_wled *wled; wled = container_of(led_cdev, struct qpnp_wled, cdev); if (level < LED_OFF) level = LED_OFF; else if (level > wled->cdev.max_brightness) level = wled->cdev.max_brightness; wled->cdev.brightness = level; queue_work(wled->wq, &wled->work); } static int qpnp_wled_set_disp(struct qpnp_wled *wled, u16 base_addr) { int rc; u8 reg; /* display type */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_DISP_SEL_REG(base_addr), ®); if (rc < 0) return rc; reg &= QPNP_WLED_DISP_SEL_MASK; reg |= (wled->disp_type_amoled << QPNP_WLED_DISP_SEL_SHIFT); rc = qpnp_wled_sec_write_reg(wled, QPNP_WLED_DISP_SEL_REG(base_addr), reg); if (rc) return rc; if (wled->disp_type_amoled) { /* Configure the PSM CTRL register for AMOLED */ if (wled->vref_psm_mv < QPNP_WLED_VREF_PSM_MIN_MV) wled->vref_psm_mv = QPNP_WLED_VREF_PSM_MIN_MV; else if (wled->vref_psm_mv > QPNP_WLED_VREF_PSM_MAX_MV) wled->vref_psm_mv = QPNP_WLED_VREF_PSM_MAX_MV; rc = qpnp_wled_read_reg(wled, QPNP_WLED_PSM_CTRL_REG(wled->ctrl_base), ®); if (rc < 0) return rc; reg &= QPNP_WLED_VREF_PSM_MASK; reg |= ((wled->vref_psm_mv - QPNP_WLED_VREF_PSM_MIN_MV)/ QPNP_WLED_VREF_PSM_STEP_MV); reg |= QPNP_WLED_PSM_OVERWRITE_BIT; rc = qpnp_wled_write_reg(wled, QPNP_WLED_PSM_CTRL_REG(wled->ctrl_base), reg); if (rc) return rc; /* Configure the VLOOP COMP RES register for AMOLED */ if (wled->loop_comp_res_kohm < QPNP_WLED_LOOP_COMP_RES_MIN_KOHM) wled->loop_comp_res_kohm = QPNP_WLED_LOOP_COMP_RES_MIN_KOHM; else if (wled->loop_comp_res_kohm > QPNP_WLED_LOOP_COMP_RES_MAX_KOHM) wled->loop_comp_res_kohm = QPNP_WLED_LOOP_COMP_RES_MAX_KOHM; rc = qpnp_wled_read_reg(wled, QPNP_WLED_VLOOP_COMP_RES_REG(wled->ctrl_base), ®); if (rc < 0) return rc; reg &= QPNP_WLED_VLOOP_COMP_RES_MASK; reg |= ((wled->loop_comp_res_kohm - QPNP_WLED_LOOP_COMP_RES_MIN_KOHM)/ QPNP_WLED_LOOP_COMP_RES_STEP_KOHM); reg |= QPNP_WLED_VLOOP_COMP_RES_OVERWRITE; rc = qpnp_wled_write_reg(wled, QPNP_WLED_VLOOP_COMP_RES_REG(wled->ctrl_base), reg); if (rc) return rc; /* Configure the CTRL TEST4 register for AMOLED */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_TEST4_REG(wled->ctrl_base), ®); if (rc < 0) return rc; reg |= QPNP_WLED_TEST4_EN_IIND_UP; rc = qpnp_wled_sec_write_reg(wled, QPNP_WLED_TEST4_REG(base_addr), reg); if (rc) return rc; } else { /* * enable VREF_UP to avoid false ovp on low brightness for LCD */ reg = QPNP_WLED_TEST4_EN_VREF_UP | QPNP_WLED_TEST4_EN_DEB_BYPASS_ILIM_BIT; rc = qpnp_wled_sec_write_reg(wled, QPNP_WLED_TEST4_REG(base_addr), reg); if (rc) return rc; } return 0; } #define AUTO_CALIB_BRIGHTNESS 200 static int wled_auto_calibrate(struct qpnp_wled *wled) { int rc = 0, i; u8 reg = 0, sink_config = 0, sink_test = 0, sink_valid = 0, int_sts; /* read configured sink configuration */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), &sink_config); if (rc < 0) { pr_err("Failed to read SINK configuration rc=%d\n", rc); goto failed_calib; } /* disable the module before starting calibration */ rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), QPNP_WLED_MODULE_EN_MASK, 0); if (rc < 0) { pr_err("Failed to disable WLED module rc=%d\n", rc); goto failed_calib; } /* set low brightness across all sinks */ rc = qpnp_wled_set_level(wled, AUTO_CALIB_BRIGHTNESS); if (rc < 0) { pr_err("Failed to set brightness for calibration rc=%d\n", rc); goto failed_calib; } if (wled->en_cabc) { for (i = 0; i < wled->max_strings; i++) { reg = 0; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_CABC_REG(wled->sink_base, i), QPNP_WLED_CABC_MASK, reg); if (rc < 0) goto failed_calib; } } /* disable all sinks */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), 0); if (rc < 0) { pr_err("Failed to disable all sinks rc=%d\n", rc); goto failed_calib; } /* iterate through the strings one by one */ for (i = 0; i < wled->max_strings; i++) { sink_test = 1 << (QPNP_WLED_CURR_SINK_SHIFT + i); /* Enable feedback control */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_FDBK_OP_REG(wled->ctrl_base), i + 1); if (rc < 0) { pr_err("Failed to enable feedback for SINK %d rc = %d\n", i + 1, rc); goto failed_calib; } /* enable the sink */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), sink_test); if (rc < 0) { pr_err("Failed to configure SINK %d rc=%d\n", i + 1, rc); goto failed_calib; } /* Enable the module */ rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), QPNP_WLED_MODULE_EN_MASK, QPNP_WLED_MODULE_EN_MASK); if (rc < 0) { pr_err("Failed to enable WLED module rc=%d\n", rc); goto failed_calib; } /* delay for WLED soft-start */ usleep_range(QPNP_WLED_SOFT_START_DLY_US, QPNP_WLED_SOFT_START_DLY_US + 1000); rc = qpnp_wled_read_reg(wled, QPNP_WLED_INT_RT_STS(wled->ctrl_base), &int_sts); if (rc < 0) { pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc); goto failed_calib; } if (int_sts & QPNP_WLED_OVP_FAULT_BIT) pr_debug("WLED OVP fault detected with SINK %d\n", i + 1); else sink_valid |= sink_test; /* Disable the module */ rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), QPNP_WLED_MODULE_EN_MASK, 0); if (rc < 0) { pr_err("Failed to disable WLED module rc=%d\n", rc); goto failed_calib; } } if (sink_valid == sink_config) { pr_debug("WLED auto-calibration complete, default sink-config=%x OK!\n", sink_config); } else { pr_warn("Invalid WLED default sink config=%x changing it to=%x\n", sink_config, sink_valid); sink_config = sink_valid; } if (!sink_config) { pr_warn("No valid WLED sinks found\n"); wled->module_dis_perm = true; goto failed_calib; } /* write the new sink configuration */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), sink_config); if (rc < 0) { pr_err("Failed to reconfigure the default sink rc=%d\n", rc); goto failed_calib; } /* MODULATOR_EN setting for valid sinks */ for (i = 0; i < wled->max_strings; i++) { if (wled->en_cabc) { reg = 1 << QPNP_WLED_CABC_SHIFT; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_CABC_REG(wled->sink_base, i), QPNP_WLED_CABC_MASK, reg); if (rc < 0) goto failed_calib; } if (sink_config & (1 << (QPNP_WLED_CURR_SINK_SHIFT + i))) reg = (QPNP_WLED_MOD_EN << QPNP_WLED_MOD_EN_SHFT); else reg = 0x0; /* disable modulator_en for unused sink */ if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) reg &= QPNP_WLED_GATE_DRV_MASK; else reg |= ~QPNP_WLED_GATE_DRV_MASK; rc = qpnp_wled_write_reg(wled, QPNP_WLED_MOD_EN_REG(wled->sink_base, i), reg); if (rc < 0) { pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc); goto failed_calib; } } /* restore the feedback setting */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_FDBK_OP_REG(wled->ctrl_base), wled->fdbk_op); if (rc < 0) { pr_err("Failed to restore feedback setting rc=%d\n", rc); goto failed_calib; } /* restore brightness */ rc = qpnp_wled_set_level(wled, !wled->cdev.brightness ? AUTO_CALIB_BRIGHTNESS : wled->cdev.brightness); if (rc < 0) { pr_err("Failed to set brightness after calibration rc=%d\n", rc); goto failed_calib; } rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), QPNP_WLED_MODULE_EN_MASK, QPNP_WLED_MODULE_EN_MASK); if (rc < 0) { pr_err("Failed to enable WLED module rc=%d\n", rc); goto failed_calib; } /* delay for WLED soft-start */ usleep_range(QPNP_WLED_SOFT_START_DLY_US, QPNP_WLED_SOFT_START_DLY_US + 1000); failed_calib: return rc; } #define WLED_AUTO_CAL_OVP_COUNT 5 #define WLED_AUTO_CAL_CNT_DLY_US 1000000 /* 1 second */ static bool qpnp_wled_auto_cal_required(struct qpnp_wled *wled) { s64 elapsed_time_us; /* * Check if the OVP fault was an occasional one * or if its firing continuously, the latter qualifies * for an auto-calibration check. */ if (!wled->auto_calibration_ovp_count) { wled->start_ovp_fault_time = ktime_get(); wled->auto_calibration_ovp_count++; } else { elapsed_time_us = ktime_us_delta(ktime_get(), wled->start_ovp_fault_time); if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US) wled->auto_calibration_ovp_count = 0; else wled->auto_calibration_ovp_count++; if (wled->auto_calibration_ovp_count >= WLED_AUTO_CAL_OVP_COUNT) { wled->auto_calibration_ovp_count = 0; return true; } } return false; } static int qpnp_wled_auto_calibrate_at_init(struct qpnp_wled *wled) { int rc; u8 fault_status = 0, rt_status = 0; if (!wled->auto_calib_enabled) return 0; rc = qpnp_wled_read_reg(wled, QPNP_WLED_INT_RT_STS(wled->ctrl_base), &rt_status); if (rc < 0) pr_err("Failed to read RT status rc=%d\n", rc); rc = qpnp_wled_read_reg(wled, QPNP_WLED_FAULT_STATUS(wled->ctrl_base), &fault_status); if (rc < 0) pr_err("Failed to read fault status rc=%d\n", rc); if ((rt_status & QPNP_WLED_OVP_FLT_RT_STS_BIT) || (fault_status & QPNP_WLED_OVP_FAULT_BIT)) { mutex_lock(&wled->lock); rc = wled_auto_calibrate(wled); if (rc < 0) pr_err("Failed auto-calibration rc=%d\n", rc); else wled->auto_calib_done = true; mutex_unlock(&wled->lock); } return rc; } /* ovp irq handler */ static irqreturn_t qpnp_wled_ovp_irq_handler(int irq, void *_wled) { struct qpnp_wled *wled = _wled; int rc; u8 fault_sts, int_sts; rc = qpnp_wled_read_reg(wled, QPNP_WLED_INT_RT_STS(wled->ctrl_base), &int_sts); if (rc < 0) { pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc); return IRQ_HANDLED; } rc = qpnp_wled_read_reg(wled, QPNP_WLED_FAULT_STATUS(wled->ctrl_base), &fault_sts); if (rc < 0) { pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc); return IRQ_HANDLED; } if (fault_sts & (QPNP_WLED_OVP_FAULT_BIT | QPNP_WLED_ILIM_FAULT_BIT)) pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n", int_sts, fault_sts); if (fault_sts & QPNP_WLED_OVP_FAULT_BIT) { if (wled->auto_calib_enabled && !wled->auto_calib_done) { if (qpnp_wled_auto_cal_required(wled)) { mutex_lock(&wled->lock); if (wled->ovp_irq > 0 && !wled->ovp_irq_disabled) { disable_irq_nosync(wled->ovp_irq); wled->ovp_irq_disabled = true; } rc = wled_auto_calibrate(wled); if (rc < 0) pr_err("Failed auto-calibration rc=%d\n", rc); else wled->auto_calib_done = true; if (wled->ovp_irq > 0 && wled->ovp_irq_disabled) { enable_irq(wled->ovp_irq); wled->ovp_irq_disabled = false; } mutex_unlock(&wled->lock); } } } return IRQ_HANDLED; } /* short circuit irq handler */ static irqreturn_t qpnp_wled_sc_irq_handler(int irq, void *_wled) { struct qpnp_wled *wled = _wled; int rc; u8 val; rc = qpnp_wled_read_reg(wled, QPNP_WLED_FAULT_STATUS(wled->ctrl_base), &val); if (rc < 0) { pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc); return IRQ_HANDLED; } pr_err("WLED short circuit detected %d times fault_status=%x\n", ++wled->sc_cnt, val); mutex_lock(&wled->lock); qpnp_wled_module_en(wled, wled->ctrl_base, false); msleep(QPNP_WLED_SC_DLY_MS); qpnp_wled_module_en(wled, wled->ctrl_base, true); mutex_unlock(&wled->lock); return IRQ_HANDLED; } static bool is_avdd_trim_adjustment_required(struct qpnp_wled *wled) { int rc; u8 reg = 0; /* * AVDD trim adjustment is not required for pmi8998/pm660l and not * supported for pmi8994. */ if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PMI8994_SUBTYPE) return false; /* * Configure TRIM_REG only if disp_type_amoled and it has * not already been programmed by bootloader. */ if (!wled->disp_type_amoled) return false; rc = qpnp_wled_read_reg(wled, QPNP_WLED_CTRL_SPARE_REG(wled->ctrl_base), ®); if (rc < 0) return false; return !(reg & QPNP_WLED_AVDD_SET_BIT); } static int qpnp_wled_gm_config(struct qpnp_wled *wled) { int rc; u8 mask = 0, reg = 0; /* Configure the LOOP COMP GM register */ if ((wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE)) { if (wled->disp_type_amoled) { reg = 0; mask |= QPNP_WLED_VLOOP_COMP_AUTO_GM_EN | QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_MASK; } else { if (wled->loop_auto_gm_en) reg |= QPNP_WLED_VLOOP_COMP_AUTO_GM_EN; if (wled->loop_auto_gm_thresh > QPNP_WLED_LOOP_AUTO_GM_THRESH_MAX) wled->loop_auto_gm_thresh = QPNP_WLED_LOOP_AUTO_GM_THRESH_MAX; reg |= wled->loop_auto_gm_thresh << QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_SHIFT; mask |= QPNP_WLED_VLOOP_COMP_AUTO_GM_EN | QPNP_WLED_VLOOP_COMP_AUTO_GM_THRESH_MASK; } } if (wled->loop_ea_gm < QPNP_WLED_LOOP_EA_GM_MIN) wled->loop_ea_gm = QPNP_WLED_LOOP_EA_GM_MIN; else if (wled->loop_ea_gm > QPNP_WLED_LOOP_EA_GM_MAX) wled->loop_ea_gm = QPNP_WLED_LOOP_EA_GM_MAX; reg |= wled->loop_ea_gm | QPNP_WLED_VLOOP_COMP_GM_OVERWRITE; mask |= QPNP_WLED_VLOOP_COMP_GM_MASK | QPNP_WLED_VLOOP_COMP_GM_OVERWRITE; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_VLOOP_COMP_GM_REG(wled->ctrl_base), mask, reg); if (rc) pr_err("write VLOOP_COMP_GM_REG failed, rc=%d]\n", rc); return rc; } static int qpnp_wled_ovp_config(struct qpnp_wled *wled) { int rc, i, *ovp_table; u8 reg; /* * Configure the OVP register based on ovp_mv only if display type is * not AMOLED. */ if (wled->disp_type_amoled) return 0; if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) ovp_table = qpnp_wled_ovp_thresholds_pmi8998; else ovp_table = qpnp_wled_ovp_thresholds_pmi8994; for (i = 0; i < NUM_SUPPORTED_OVP_THRESHOLDS; i++) { if (wled->ovp_mv == ovp_table[i]) break; } if (i == NUM_SUPPORTED_OVP_THRESHOLDS) { dev_err(&wled->pdev->dev, "Invalid ovp threshold specified in device tree\n"); return -EINVAL; } reg = i & QPNP_WLED_OVP_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_OVP_REG(wled->ctrl_base), QPNP_WLED_OVP_MASK, reg); if (rc) return rc; return 0; } static int qpnp_wled_avdd_trim_config(struct qpnp_wled *wled) { int rc, i; u8 reg; for (i = 0; i < NUM_SUPPORTED_AVDD_VOLTAGES; i++) { if (wled->avdd_target_voltage_mv == qpnp_wled_avdd_target_voltages[i]) break; } if (i == NUM_SUPPORTED_AVDD_VOLTAGES) { dev_err(&wled->pdev->dev, "Invalid avdd target voltage specified in device tree\n"); return -EINVAL; } /* Update WLED_OVP register based on desired target voltage */ reg = qpnp_wled_ovp_reg_settings[i]; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_OVP_REG(wled->ctrl_base), QPNP_WLED_OVP_MASK, reg); if (rc) return rc; /* Update WLED_TRIM register based on desired target voltage */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_REF_7P7_TRIM_REG(wled->ctrl_base), ®); if (rc) return rc; reg += qpnp_wled_avdd_trim_adjustments[i]; if ((s8)reg < QPNP_WLED_AVDD_MIN_TRIM_VAL || (s8)reg > QPNP_WLED_AVDD_MAX_TRIM_VAL) { dev_dbg(&wled->pdev->dev, "adjusted trim %d is not within range, capping it\n", (s8)reg); if ((s8)reg < QPNP_WLED_AVDD_MIN_TRIM_VAL) reg = QPNP_WLED_AVDD_MIN_TRIM_VAL; else reg = QPNP_WLED_AVDD_MAX_TRIM_VAL; } reg &= QPNP_WLED_7P7_TRIM_MASK; rc = qpnp_wled_sec_write_reg(wled, QPNP_WLED_REF_7P7_TRIM_REG(wled->ctrl_base), reg); if (rc < 0) dev_err(&wled->pdev->dev, "Write to 7P7_TRIM register failed, rc=%d\n", rc); return rc; } static int qpnp_wled_avdd_mode_config(struct qpnp_wled *wled) { int rc; u8 reg = 0; /* * At present, configuring the mode to SPMI/SWIRE for controlling * AVDD voltage is available only in pmi8998/pm660l. */ if (wled->pmic_rev_id->pmic_subtype != PMI8998_SUBTYPE && wled->pmic_rev_id->pmic_subtype != PM660L_SUBTYPE) return 0; /* AMOLED_VOUT should be configured for AMOLED */ if (!wled->disp_type_amoled) return 0; /* Configure avdd register */ if (wled->avdd_target_voltage_mv > QPNP_WLED_AVDD_MAX_MV) { dev_dbg(&wled->pdev->dev, "Capping avdd target voltage to %d\n", QPNP_WLED_AVDD_MAX_MV); wled->avdd_target_voltage_mv = QPNP_WLED_AVDD_MAX_MV; } else if (wled->avdd_target_voltage_mv < QPNP_WLED_AVDD_MIN_MV) { dev_info(&wled->pdev->dev, "Capping avdd target voltage to %d\n", QPNP_WLED_AVDD_MIN_MV); wled->avdd_target_voltage_mv = QPNP_WLED_AVDD_MIN_MV; } if (wled->avdd_mode_spmi) { reg = QPNP_WLED_AVDD_MV_TO_REG(wled->avdd_target_voltage_mv); reg |= QPNP_WLED_AVDD_SEL_SPMI_BIT; rc = qpnp_wled_write_reg(wled, QPNP_WLED_AMOLED_VOUT_REG(wled->ctrl_base), reg); if (rc < 0) pr_err("Write to AMOLED_VOUT register failed, rc=%d\n", rc); } else { rc = qpnp_wled_swire_avdd_config(wled); if (rc < 0) pr_err("Write to SWIRE_AVDD_DEFAULT register failed rc:%d\n", rc); } return rc; } static int qpnp_wled_ilim_config(struct qpnp_wled *wled) { int rc, i, *ilim_table; u8 reg; if (wled->ilim_ma < PMI8994_WLED_ILIM_MIN_MA) wled->ilim_ma = PMI8994_WLED_ILIM_MIN_MA; if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) { ilim_table = qpnp_wled_ilim_settings_pmi8998; if (wled->ilim_ma > PMI8998_WLED_ILIM_MAX_MA) wled->ilim_ma = PMI8998_WLED_ILIM_MAX_MA; } else { ilim_table = qpnp_wled_ilim_settings_pmi8994; if (wled->ilim_ma > PMI8994_WLED_ILIM_MAX_MA) wled->ilim_ma = PMI8994_WLED_ILIM_MAX_MA; } for (i = 0; i < NUM_SUPPORTED_ILIM_THRESHOLDS; i++) { if (wled->ilim_ma == ilim_table[i]) break; } if (i == NUM_SUPPORTED_ILIM_THRESHOLDS) { dev_err(&wled->pdev->dev, "Invalid ilim threshold specified in device tree\n"); return -EINVAL; } reg = (i & QPNP_WLED_ILIM_MASK) | QPNP_WLED_ILIM_OVERWRITE; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_ILIM_REG(wled->ctrl_base), QPNP_WLED_ILIM_MASK | QPNP_WLED_ILIM_OVERWRITE, reg); if (rc < 0) dev_err(&wled->pdev->dev, "Write to ILIM register failed, rc=%d\n", rc); return rc; } static int qpnp_wled_vref_config(struct qpnp_wled *wled) { struct wled_vref_setting vref_setting; int rc; u8 reg = 0; if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) vref_setting = vref_setting_pmi8998; else vref_setting = vref_setting_pmi8994; if (wled->vref_uv < vref_setting.min_uv) wled->vref_uv = vref_setting.min_uv; else if (wled->vref_uv > vref_setting.max_uv) wled->vref_uv = vref_setting.max_uv; reg |= DIV_ROUND_CLOSEST(wled->vref_uv - vref_setting.min_uv, vref_setting.step_uv); rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_VREF_REG(wled->ctrl_base), QPNP_WLED_VREF_MASK, reg); if (rc) pr_err("Write VREF_REG failed, rc=%d\n", rc); return rc; } /* Configure WLED registers */ static int qpnp_wled_config(struct qpnp_wled *wled) { int rc, i, temp; u8 reg = 0, sink_en = 0, mask; /* Configure display type */ rc = qpnp_wled_set_disp(wled, wled->ctrl_base); if (rc < 0) return rc; /* Configure the FEEDBACK OUTPUT register */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_FDBK_OP_REG(wled->ctrl_base), ®); if (rc < 0) return rc; reg &= QPNP_WLED_FDBK_OP_MASK; reg |= wled->fdbk_op; rc = qpnp_wled_write_reg(wled, QPNP_WLED_FDBK_OP_REG(wled->ctrl_base), reg); if (rc) return rc; /* Configure the VREF register */ rc = qpnp_wled_vref_config(wled); if (rc < 0) { pr_err("Error in configuring wled vref, rc=%d\n", rc); return rc; } /* Configure VLOOP_COMP_GM register */ rc = qpnp_wled_gm_config(wled); if (rc < 0) { pr_err("Error in configureing wled gm, rc=%d\n", rc); return rc; } /* Configure the ILIM register */ rc = qpnp_wled_ilim_config(wled); if (rc < 0) { pr_err("Error in configuring wled ilim, rc=%d\n", rc); return rc; } /* Configure auto PFM mode for LCD mode only */ if ((wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) && !wled->disp_type_amoled) { reg = 0; reg |= wled->lcd_auto_pfm_thresh; reg |= wled->lcd_auto_pfm_en << QPNP_WLED_LCD_AUTO_PFM_EN_SHIFT; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_LCD_AUTO_PFM_REG(wled->ctrl_base), QPNP_WLED_LCD_AUTO_PFM_EN_BIT | QPNP_WLED_LCD_AUTO_PFM_THRESH_MASK, reg); if (rc < 0) { pr_err("Write LCD_AUTO_PFM failed, rc=%d\n", rc); return rc; } } /* Configure the Soft start Ramp delay: for AMOLED - 0,for LCD - 2 */ reg = (wled->disp_type_amoled) ? 0 : 2; mask = SOFTSTART_RAMP_DELAY_MASK; if ((wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) && wled->disp_type_amoled) { reg |= SOFTSTART_OVERWRITE_BIT; mask |= SOFTSTART_OVERWRITE_BIT; } rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_SOFTSTART_RAMP_DLY(wled->ctrl_base), mask, reg); if (rc) return rc; /* Configure the MAX BOOST DUTY register */ if (wled->boost_duty_ns < QPNP_WLED_BOOST_DUTY_MIN_NS) wled->boost_duty_ns = QPNP_WLED_BOOST_DUTY_MIN_NS; else if (wled->boost_duty_ns > QPNP_WLED_BOOST_DUTY_MAX_NS) wled->boost_duty_ns = QPNP_WLED_BOOST_DUTY_MAX_NS; rc = qpnp_wled_read_reg(wled, QPNP_WLED_BOOST_DUTY_REG(wled->ctrl_base), ®); if (rc < 0) return rc; reg &= QPNP_WLED_BOOST_DUTY_MASK; reg |= (wled->boost_duty_ns / QPNP_WLED_BOOST_DUTY_STEP_NS); rc = qpnp_wled_write_reg(wled, QPNP_WLED_BOOST_DUTY_REG(wled->ctrl_base), reg); if (rc) return rc; /* Configure the SWITCHING FREQ register */ if (wled->switch_freq_khz == 1600) reg = QPNP_WLED_SWITCH_FREQ_1600_KHZ_CODE; else reg = QPNP_WLED_SWITCH_FREQ_800_KHZ_CODE; /* * Do not set the overwrite bit when switching frequency is selected * for AMOLED. This register is in logic reset block which can cause * the value to be overwritten during module enable/disable. */ mask = QPNP_WLED_SWITCH_FREQ_MASK | QPNP_WLED_SWITCH_FREQ_OVERWRITE; if (!wled->disp_type_amoled) reg |= QPNP_WLED_SWITCH_FREQ_OVERWRITE; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_SWITCH_FREQ_REG(wled->ctrl_base), mask, reg); if (rc < 0) return rc; rc = qpnp_wled_ovp_config(wled); if (rc < 0) { pr_err("Error in configuring OVP threshold, rc=%d\n", rc); return rc; } if (is_avdd_trim_adjustment_required(wled)) { rc = qpnp_wled_avdd_trim_config(wled); if (rc < 0) return rc; } rc = qpnp_wled_avdd_mode_config(wled); if (rc < 0) return rc; /* Configure the MODULATION register */ if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_1200_KHZ) { wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_1200_KHZ; temp = 3; } else if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_2400_KHZ) { wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_2400_KHZ; temp = 2; } else if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_9600_KHZ) { wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_9600_KHZ; temp = 1; } else if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_19200_KHZ) { wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_19200_KHZ; temp = 0; } else { wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_9600_KHZ; temp = 1; } rc = qpnp_wled_read_reg(wled, QPNP_WLED_MOD_REG(wled->sink_base), ®); if (rc < 0) return rc; reg &= QPNP_WLED_MOD_FREQ_MASK; reg |= (temp << QPNP_WLED_MOD_FREQ_SHIFT); reg &= QPNP_WLED_PHASE_STAG_MASK; reg |= (wled->en_phase_stag << QPNP_WLED_PHASE_STAG_SHIFT); reg &= QPNP_WLED_ACC_CLK_FREQ_MASK; reg |= (temp << QPNP_WLED_ACC_CLK_FREQ_SHIFT); reg &= QPNP_WLED_DIM_RES_MASK; reg |= (wled->en_9b_dim_res << QPNP_WLED_DIM_RES_SHIFT); if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) { reg &= QPNP_WLED_DIM_HYB_MASK; reg |= (1 << QPNP_WLED_DIM_HYB_SHIFT); } else { reg &= QPNP_WLED_DIM_HYB_MASK; reg |= (0 << QPNP_WLED_DIM_HYB_SHIFT); reg &= QPNP_WLED_DIM_ANA_MASK; reg |= wled->dim_mode; } rc = qpnp_wled_write_reg(wled, QPNP_WLED_MOD_REG(wled->sink_base), reg); if (rc) return rc; /* Configure the HYBRID THRESHOLD register */ if (wled->hyb_thres < QPNP_WLED_HYB_THRES_MIN) wled->hyb_thres = QPNP_WLED_HYB_THRES_MIN; else if (wled->hyb_thres > QPNP_WLED_HYB_THRES_MAX) wled->hyb_thres = QPNP_WLED_HYB_THRES_MAX; rc = qpnp_wled_read_reg(wled, QPNP_WLED_HYB_THRES_REG(wled->sink_base), ®); if (rc < 0) return rc; reg &= QPNP_WLED_HYB_THRES_MASK; temp = fls(wled->hyb_thres / QPNP_WLED_HYB_THRES_MIN) - 1; reg |= temp; rc = qpnp_wled_write_reg(wled, QPNP_WLED_HYB_THRES_REG(wled->sink_base), reg); if (rc) return rc; /* Configure TEST5 register */ if (wled->dim_mode == QPNP_WLED_DIM_DIGITAL) { reg = QPNP_WLED_SINK_TEST5_DIG; } else { reg = QPNP_WLED_SINK_TEST5_HYB; if (wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) reg |= QPNP_WLED_SINK_TEST5_HVG_PULL_STR_BIT; } rc = qpnp_wled_sec_write_reg(wled, QPNP_WLED_SINK_TEST5_REG(wled->sink_base), reg); if (rc) return rc; /* disable all current sinks and enable selected strings */ reg = 0x00; rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), reg); for (i = 0; i < wled->max_strings; i++) { /* SYNC DELAY */ if (wled->sync_dly_us > QPNP_WLED_SYNC_DLY_MAX_US) wled->sync_dly_us = QPNP_WLED_SYNC_DLY_MAX_US; reg = wled->sync_dly_us / QPNP_WLED_SYNC_DLY_STEP_US; mask = QPNP_WLED_SYNC_DLY_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_SYNC_DLY_REG(wled->sink_base, i), mask, reg); if (rc < 0) return rc; /* FULL SCALE CURRENT */ if (wled->fs_curr_ua > QPNP_WLED_FS_CURR_MAX_UA) wled->fs_curr_ua = QPNP_WLED_FS_CURR_MAX_UA; reg = wled->fs_curr_ua / QPNP_WLED_FS_CURR_STEP_UA; mask = QPNP_WLED_FS_CURR_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, i), mask, reg); if (rc < 0) return rc; /* CABC */ reg = wled->en_cabc ? (1 << QPNP_WLED_CABC_SHIFT) : 0; mask = QPNP_WLED_CABC_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_CABC_REG(wled->sink_base, i), mask, reg); if (rc < 0) return rc; } /* Settings specific to valid sinks */ for (i = 0; i < wled->num_strings; i++) { if (wled->strings[i] >= wled->max_strings) { dev_err(&wled->pdev->dev, "Invalid string number\n"); return -EINVAL; } /* MODULATOR */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_MOD_EN_REG(wled->sink_base, i), ®); if (rc < 0) return rc; reg &= QPNP_WLED_MOD_EN_MASK; reg |= (QPNP_WLED_MOD_EN << QPNP_WLED_MOD_EN_SHFT); if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) reg &= QPNP_WLED_GATE_DRV_MASK; else reg |= ~QPNP_WLED_GATE_DRV_MASK; rc = qpnp_wled_write_reg(wled, QPNP_WLED_MOD_EN_REG(wled->sink_base, i), reg); if (rc) return rc; /* SINK EN */ temp = wled->strings[i] + QPNP_WLED_CURR_SINK_SHIFT; sink_en |= (1 << temp); } mask = QPNP_WLED_CURR_SINK_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), mask, sink_en); if (rc < 0) { dev_err(&wled->pdev->dev, "Failed to enable WLED sink config rc = %d\n", rc); return rc; } rc = qpnp_wled_sync_reg_toggle(wled); if (rc < 0) { dev_err(&wled->pdev->dev, "Failed to toggle sync reg %d\n", rc); return rc; } rc = qpnp_wled_auto_calibrate_at_init(wled); if (rc < 0) pr_err("Failed to auto-calibrate at init rc=%d\n", rc); /* setup ovp and sc irqs */ if (wled->ovp_irq >= 0) { rc = devm_request_threaded_irq(&wled->pdev->dev, wled->ovp_irq, NULL, qpnp_wled_ovp_irq_handler, IRQF_ONESHOT, "qpnp_wled_ovp_irq", wled); if (rc < 0) { dev_err(&wled->pdev->dev, "Unable to request ovp(%d) IRQ(err:%d)\n", wled->ovp_irq, rc); return rc; } rc = qpnp_wled_read_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), ®); /* disable the OVP irq only if the module is not enabled */ if (!rc && !(reg & QPNP_WLED_MODULE_EN_MASK)) { disable_irq(wled->ovp_irq); wled->ovp_irq_disabled = true; } } if (wled->sc_irq >= 0) { wled->sc_cnt = 0; rc = devm_request_threaded_irq(&wled->pdev->dev, wled->sc_irq, NULL, qpnp_wled_sc_irq_handler, IRQF_ONESHOT, "qpnp_wled_sc_irq", wled); if (rc < 0) { dev_err(&wled->pdev->dev, "Unable to request sc(%d) IRQ(err:%d)\n", wled->sc_irq, rc); return rc; } rc = qpnp_wled_read_reg(wled, QPNP_WLED_SC_PRO_REG(wled->ctrl_base), ®); if (rc < 0) return rc; reg &= QPNP_WLED_EN_SC_DEB_CYCLES_MASK; reg |= 1 << QPNP_WLED_EN_SC_SHIFT; if (wled->sc_deb_cycles < QPNP_WLED_SC_DEB_CYCLES_MIN) wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_MIN; else if (wled->sc_deb_cycles > QPNP_WLED_SC_DEB_CYCLES_MAX) wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_MAX; temp = fls(wled->sc_deb_cycles) - QPNP_WLED_SC_DEB_CYCLES_SUB; reg |= (temp << 1); if (wled->disp_type_amoled) reg |= QPNP_WLED_SC_PRO_EN_DSCHGR; rc = qpnp_wled_write_reg(wled, QPNP_WLED_SC_PRO_REG(wled->ctrl_base), reg); if (rc) return rc; if (wled->en_ext_pfet_sc_pro) { reg = QPNP_WLED_EXT_FET_DTEST2; rc = qpnp_wled_sec_write_reg(wled, QPNP_WLED_TEST1_REG(wled->ctrl_base), reg); if (rc) return rc; } } else { rc = qpnp_wled_read_reg(wled, QPNP_WLED_SC_PRO_REG(wled->ctrl_base), ®); if (rc < 0) return rc; reg &= QPNP_WLED_EN_DEB_CYCLES_MASK; if (wled->sc_deb_cycles < QPNP_WLED_SC_DEB_CYCLES_MIN) wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_MIN; else if (wled->sc_deb_cycles > QPNP_WLED_SC_DEB_CYCLES_MAX) wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_MAX; temp = fls(wled->sc_deb_cycles) - QPNP_WLED_SC_DEB_CYCLES_SUB; reg |= (temp << 1); rc = qpnp_wled_write_reg(wled, QPNP_WLED_SC_PRO_REG(wled->ctrl_base), reg); if (rc) return rc; } return 0; } /* parse wled dtsi parameters */ static int qpnp_wled_parse_dt(struct qpnp_wled *wled) { struct platform_device *pdev = wled->pdev; struct property *prop; const char *temp_str; u32 temp_val; int rc, i, size; u8 *strings; wled->cdev.name = "wled"; rc = of_property_read_string(pdev->dev.of_node, "linux,name", &wled->cdev.name); if (rc && (rc != -EINVAL)) { dev_err(&pdev->dev, "Unable to read led name\n"); return rc; } wled->cdev.default_trigger = QPNP_WLED_TRIGGER_NONE; rc = of_property_read_string(pdev->dev.of_node, "linux,default-trigger", &wled->cdev.default_trigger); if (rc && (rc != -EINVAL)) { dev_err(&pdev->dev, "Unable to read led trigger\n"); return rc; } if (of_find_property(pdev->dev.of_node, "qcom,wled-brightness-map", NULL)) { size = of_property_count_elems_of_size(pdev->dev.of_node, "qcom,wled-brightness-map", sizeof(u16)); if (size != NUM_DDIC_CODES) { pr_err("Invalid WLED brightness map size:%d\n", size); return rc; } wled->brt_map_table = devm_kcalloc(&pdev->dev, NUM_DDIC_CODES, sizeof(u16), GFP_KERNEL); if (!wled->brt_map_table) return -ENOMEM; rc = of_property_read_u16_array(pdev->dev.of_node, "qcom,wled-brightness-map", wled->brt_map_table, NUM_DDIC_CODES); if (rc < 0) { pr_err("Error in reading WLED brightness map, rc=%d\n", rc); return rc; } for (i = 0; i < NUM_DDIC_CODES; i++) { if (wled->brt_map_table[i] > WLED_MAX_LEVEL_4095) { pr_err("WLED brightness map not in range\n"); return -EDOM; } if ((i > 1) && wled->brt_map_table[i] < wled->brt_map_table[i - 1]) { pr_err("WLED brightness map not in ascending order?\n"); return -EDOM; } } } wled->stepper_en = of_property_read_bool(pdev->dev.of_node, "qcom,wled-stepper-en"); wled->disp_type_amoled = of_property_read_bool(pdev->dev.of_node, "qcom,disp-type-amoled"); if (wled->disp_type_amoled) { wled->vref_psm_mv = QPNP_WLED_VREF_PSM_DFLT_AMOLED_MV; rc = of_property_read_u32(pdev->dev.of_node, "qcom,vref-psm-mv", &temp_val); if (!rc) { wled->vref_psm_mv = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read vref-psm\n"); return rc; } wled->loop_comp_res_kohm = 320; if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) wled->loop_comp_res_kohm = 300; rc = of_property_read_u32(pdev->dev.of_node, "qcom,loop-comp-res-kohm", &temp_val); if (!rc) { wled->loop_comp_res_kohm = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read loop-comp-res-kohm\n"); return rc; } wled->avdd_mode_spmi = of_property_read_bool(pdev->dev.of_node, "qcom,avdd-mode-spmi"); wled->avdd_target_voltage_mv = QPNP_WLED_DFLT_AVDD_MV; rc = of_property_read_u32(pdev->dev.of_node, "qcom,avdd-target-voltage-mv", &temp_val); if (!rc) { wled->avdd_target_voltage_mv = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read avdd target voltage\n"); return rc; } } if (wled->disp_type_amoled) { if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) wled->loop_ea_gm = QPNP_WLED_LOOP_GM_DFLT_AMOLED_PMI8998; else wled->loop_ea_gm = QPNP_WLED_LOOP_EA_GM_DFLT_AMOLED_PMI8994; } else { wled->loop_ea_gm = QPNP_WLED_LOOP_GM_DFLT_WLED; } rc = of_property_read_u32(pdev->dev.of_node, "qcom,loop-ea-gm", &temp_val); if (!rc) { wled->loop_ea_gm = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read loop-ea-gm\n"); return rc; } if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) { wled->loop_auto_gm_en = of_property_read_bool(pdev->dev.of_node, "qcom,loop-auto-gm-en"); wled->loop_auto_gm_thresh = QPNP_WLED_LOOP_AUTO_GM_DFLT_THRESH; rc = of_property_read_u8(pdev->dev.of_node, "qcom,loop-auto-gm-thresh", &wled->loop_auto_gm_thresh); if (rc && rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read loop-auto-gm-thresh\n"); return rc; } } if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) { if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE && wled->pmic_rev_id->rev4 == PMI8998_V2P0_REV4) wled->lcd_auto_pfm_en = false; else wled->lcd_auto_pfm_en = true; wled->lcd_auto_pfm_thresh = QPNP_WLED_LCD_AUTO_PFM_DFLT_THRESH; rc = of_property_read_u8(pdev->dev.of_node, "qcom,lcd-auto-pfm-thresh", &wled->lcd_auto_pfm_thresh); if (rc && rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read lcd-auto-pfm-thresh\n"); return rc; } if (wled->lcd_auto_pfm_thresh > QPNP_WLED_LCD_AUTO_PFM_THRESH_MAX) wled->lcd_auto_pfm_thresh = QPNP_WLED_LCD_AUTO_PFM_THRESH_MAX; } wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_DFLT; rc = of_property_read_u32(pdev->dev.of_node, "qcom,sc-deb-cycles", &temp_val); if (!rc) { wled->sc_deb_cycles = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read sc debounce cycles\n"); return rc; } wled->fdbk_op = QPNP_WLED_FDBK_AUTO; rc = of_property_read_string(pdev->dev.of_node, "qcom,fdbk-output", &temp_str); if (!rc) { if (strcmp(temp_str, "wled1") == 0) wled->fdbk_op = QPNP_WLED_FDBK_WLED1; else if (strcmp(temp_str, "wled2") == 0) wled->fdbk_op = QPNP_WLED_FDBK_WLED2; else if (strcmp(temp_str, "wled3") == 0) wled->fdbk_op = QPNP_WLED_FDBK_WLED3; else if (strcmp(temp_str, "wled4") == 0) wled->fdbk_op = QPNP_WLED_FDBK_WLED4; else wled->fdbk_op = QPNP_WLED_FDBK_AUTO; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read feedback output\n"); return rc; } if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) wled->vref_uv = vref_setting_pmi8998.default_uv; else wled->vref_uv = vref_setting_pmi8994.default_uv; rc = of_property_read_u32(pdev->dev.of_node, "qcom,vref-uv", &temp_val); if (!rc) { wled->vref_uv = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read vref\n"); return rc; } wled->switch_freq_khz = wled->disp_type_amoled ? 1600 : 800; rc = of_property_read_u32(pdev->dev.of_node, "qcom,switch-freq-khz", &temp_val); if (!rc) { wled->switch_freq_khz = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read switch freq\n"); return rc; } if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) wled->ovp_mv = 29600; else wled->ovp_mv = 29500; rc = of_property_read_u32(pdev->dev.of_node, "qcom,ovp-mv", &temp_val); if (!rc) { wled->ovp_mv = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read ovp\n"); return rc; } if (wled->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE || wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) { if (wled->disp_type_amoled) wled->ilim_ma = PMI8998_AMOLED_DFLT_ILIM_MA; else wled->ilim_ma = PMI8998_WLED_DFLT_ILIM_MA; } else { if (wled->disp_type_amoled) wled->ilim_ma = PMI8994_AMOLED_DFLT_ILIM_MA; else wled->ilim_ma = PMI8994_WLED_DFLT_ILIM_MA; } rc = of_property_read_u32(pdev->dev.of_node, "qcom,ilim-ma", &temp_val); if (!rc) { wled->ilim_ma = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read ilim\n"); return rc; } wled->boost_duty_ns = QPNP_WLED_DEF_BOOST_DUTY_NS; rc = of_property_read_u32(pdev->dev.of_node, "qcom,boost-duty-ns", &temp_val); if (!rc) { wled->boost_duty_ns = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read boost duty\n"); return rc; } wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_9600_KHZ; rc = of_property_read_u32(pdev->dev.of_node, "qcom,mod-freq-khz", &temp_val); if (!rc) { wled->mod_freq_khz = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read modulation freq\n"); return rc; } wled->dim_mode = QPNP_WLED_DIM_HYBRID; rc = of_property_read_string(pdev->dev.of_node, "qcom,dim-mode", &temp_str); if (!rc) { if (strcmp(temp_str, "analog") == 0) wled->dim_mode = QPNP_WLED_DIM_ANALOG; else if (strcmp(temp_str, "digital") == 0) wled->dim_mode = QPNP_WLED_DIM_DIGITAL; else wled->dim_mode = QPNP_WLED_DIM_HYBRID; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read dim mode\n"); return rc; } if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) { wled->hyb_thres = QPNP_WLED_DEF_HYB_THRES; rc = of_property_read_u32(pdev->dev.of_node, "qcom,hyb-thres", &temp_val); if (!rc) { wled->hyb_thres = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read hyb threshold\n"); return rc; } } wled->sync_dly_us = QPNP_WLED_DEF_SYNC_DLY_US; rc = of_property_read_u32(pdev->dev.of_node, "qcom,sync-dly-us", &temp_val); if (!rc) { wled->sync_dly_us = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read sync delay\n"); return rc; } wled->fs_curr_ua = QPNP_WLED_FS_CURR_MAX_UA; rc = of_property_read_u32(pdev->dev.of_node, "qcom,fs-curr-ua", &temp_val); if (!rc) { wled->fs_curr_ua = temp_val; } else if (rc != -EINVAL) { dev_err(&pdev->dev, "Unable to read full scale current\n"); return rc; } wled->cons_sync_write_delay_us = 0; rc = of_property_read_u32(pdev->dev.of_node, "qcom,cons-sync-write-delay-us", &temp_val); if (!rc) wled->cons_sync_write_delay_us = temp_val; wled->en_9b_dim_res = of_property_read_bool(pdev->dev.of_node, "qcom,en-9b-dim-res"); wled->en_phase_stag = of_property_read_bool(pdev->dev.of_node, "qcom,en-phase-stag"); wled->en_cabc = of_property_read_bool(pdev->dev.of_node, "qcom,en-cabc"); if (wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) wled->max_strings = QPNP_PM660_WLED_MAX_STRINGS; else wled->max_strings = QPNP_WLED_MAX_STRINGS; prop = of_find_property(pdev->dev.of_node, "qcom,led-strings-list", &temp_val); if (!prop || !temp_val || temp_val > QPNP_WLED_MAX_STRINGS) { dev_err(&pdev->dev, "Invalid strings info, use default"); wled->num_strings = wled->max_strings; for (i = 0; i < wled->num_strings; i++) wled->strings[i] = i; } else { wled->num_strings = temp_val; strings = prop->value; for (i = 0; i < wled->num_strings; ++i) wled->strings[i] = strings[i]; } wled->ovp_irq = platform_get_irq_byname(pdev, "ovp-irq"); if (wled->ovp_irq < 0) dev_dbg(&pdev->dev, "ovp irq is not used\n"); wled->sc_irq = platform_get_irq_byname(pdev, "sc-irq"); if (wled->sc_irq < 0) dev_dbg(&pdev->dev, "sc irq is not used\n"); wled->en_ext_pfet_sc_pro = of_property_read_bool(pdev->dev.of_node, "qcom,en-ext-pfet-sc-pro"); wled->lcd_psm_ctrl = of_property_read_bool(pdev->dev.of_node, "qcom,lcd-psm-ctrl"); wled->auto_calib_enabled = of_property_read_bool(pdev->dev.of_node, "qcom,auto-calibration-enable"); return 0; } static int qpnp_wled_probe(struct platform_device *pdev) { struct qpnp_wled *wled; struct device_node *revid_node; int rc = 0, i; const __be32 *prop; wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL); if (!wled) return -ENOMEM; wled->regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!wled->regmap) { dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); return -EINVAL; } wled->pdev = pdev; revid_node = of_parse_phandle(pdev->dev.of_node, "qcom,pmic-revid", 0); if (!revid_node) { pr_err("Missing qcom,pmic-revid property - driver failed\n"); return -EINVAL; } wled->pmic_rev_id = get_revid_data(revid_node); of_node_put(revid_node); if (IS_ERR_OR_NULL(wled->pmic_rev_id)) { pr_err("Unable to get pmic_revid rc=%ld\n", PTR_ERR(wled->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", wled->pmic_rev_id->pmic_subtype, wled->pmic_rev_id->rev4); wled->wq = alloc_ordered_workqueue("qpnp_wled_wq", WQ_HIGHPRI); if (!wled->wq) { pr_err("Unable to alloc workqueue for WLED\n"); return -ENOMEM; } prop = of_get_address_by_name(pdev->dev.of_node, QPNP_WLED_SINK_BASE, NULL, NULL); if (!prop) { dev_err(&pdev->dev, "Couldnt find sink's addr rc %d\n", rc); return rc; } wled->sink_base = be32_to_cpu(*prop); prop = of_get_address_by_name(pdev->dev.of_node, QPNP_WLED_CTRL_BASE, NULL, NULL); if (!prop) { dev_err(&pdev->dev, "Couldnt find ctrl's addr rc = %d\n", rc); return rc; } wled->ctrl_base = be32_to_cpu(*prop); dev_set_drvdata(&pdev->dev, wled); rc = qpnp_wled_parse_dt(wled); if (rc) { dev_err(&pdev->dev, "DT parsing failed\n"); return rc; } mutex_init(&wled->bus_lock); mutex_init(&wled->lock); rc = qpnp_wled_config(wled); if (rc) { dev_err(&pdev->dev, "wled config failed\n"); return rc; } INIT_WORK(&wled->work, qpnp_wled_work); wled->ramp_ms = QPNP_WLED_RAMP_DLY_MS; wled->ramp_step = 1; wled->cdev.brightness_set = qpnp_wled_set; wled->cdev.brightness_get = qpnp_wled_get; wled->cdev.max_brightness = WLED_MAX_LEVEL_4095; rc = led_classdev_register(&pdev->dev, &wled->cdev); if (rc) { dev_err(&pdev->dev, "wled registration failed(%d)\n", rc); goto wled_register_fail; } for (i = 0; i < ARRAY_SIZE(qpnp_wled_attrs); i++) { rc = sysfs_create_file(&wled->cdev.dev->kobj, &qpnp_wled_attrs[i].attr); if (rc < 0) { dev_err(&pdev->dev, "sysfs creation failed\n"); goto sysfs_fail; } } return 0; sysfs_fail: for (i--; i >= 0; i--) sysfs_remove_file(&wled->cdev.dev->kobj, &qpnp_wled_attrs[i].attr); led_classdev_unregister(&wled->cdev); wled_register_fail: cancel_work_sync(&wled->work); destroy_workqueue(wled->wq); mutex_destroy(&wled->lock); return rc; } static int qpnp_wled_remove(struct platform_device *pdev) { struct qpnp_wled *wled = dev_get_drvdata(&pdev->dev); int i; for (i = 0; i < ARRAY_SIZE(qpnp_wled_attrs); i++) sysfs_remove_file(&wled->cdev.dev->kobj, &qpnp_wled_attrs[i].attr); led_classdev_unregister(&wled->cdev); cancel_work_sync(&wled->work); destroy_workqueue(wled->wq); mutex_destroy(&wled->lock); return 0; } static const struct of_device_id spmi_match_table[] = { { .compatible = "qcom,qpnp-wled",}, { }, }; static struct platform_driver qpnp_wled_driver = { .driver = { .name = "qcom,qpnp-wled", .of_match_table = spmi_match_table, }, .probe = qpnp_wled_probe, .remove = qpnp_wled_remove, }; static int __init qpnp_wled_init(void) { return platform_driver_register(&qpnp_wled_driver); } module_init(qpnp_wled_init); static void __exit qpnp_wled_exit(void) { platform_driver_unregister(&qpnp_wled_driver); } module_exit(qpnp_wled_exit); MODULE_DESCRIPTION("QPNP WLED driver"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("leds:leds-qpnp-wled");