android_kernel_oneplus_msm8998/drivers/leds/leds-qpnp-wled.c
Subbaraman Narayanamurthy 3615693f74 leds: qpnp-wled: add stepper algorithm using brightness map
Add stepper algorithm support with dynamically calculated step
size or delay based on the brightness level change using the
brightness map table.

To help with running the algorithm efficiently, use a separate
workqueue with high priority to process the brightness levels.

Change-Id: Iea2a8da73b6bee3eaa7b28a12fd82c2a1507db99
Signed-off-by: Subbaraman Narayanamurthy <subbaram@codeaurora.org>
2017-12-20 15:06:21 -08:00

2784 lines
75 KiB
C

/* 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 <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/regmap.h>
#include <linux/errno.h>
#include <linux/leds.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/spmi.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/leds-qpnp-wled.h>
#include <linux/qpnp/qpnp-revid.h>
/* 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], &reg);
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), &reg);
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), &reg);
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), &reg);
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),
&reg);
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), &reg);
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), &reg);
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), &reg);
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),
&reg);
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),
&reg);
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), &reg);
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),
&reg);
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), &reg);
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), &reg);
/* 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), &reg);
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), &reg);
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");