1744 lines
46 KiB
C
1744 lines
46 KiB
C
/* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/leds.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/spmi.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/err.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/leds-qpnp-wled.h>
|
|
|
|
#define QPNP_IRQ_FLAGS (IRQF_TRIGGER_RISING | \
|
|
IRQF_TRIGGER_FALLING | \
|
|
IRQF_ONESHOT)
|
|
|
|
/* 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_INT_EN_SET(b) (b + 0x15)
|
|
#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_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_PSM_CTRL_REG(b) (b + 0x5B)
|
|
#define QPNP_WLED_SC_PRO_REG(b) (b + 0x5E)
|
|
#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_EN_MASK 0x7F
|
|
#define QPNP_WLED_EN_SHIFT 7
|
|
#define QPNP_WLED_FDBK_OP_MASK 0xF8
|
|
#define QPNP_WLED_VREF_MASK 0xF0
|
|
#define QPNP_WLED_VREF_STEP_MV 25
|
|
#define QPNP_WLED_VREF_MIN_MV 300
|
|
#define QPNP_WLED_VREF_MAX_MV 675
|
|
#define QPNP_WLED_DFLT_VREF_MV 350
|
|
|
|
#define QPNP_WLED_VLOOP_COMP_RES_MASK 0xF0
|
|
#define QPNP_WLED_VLOOP_COMP_RES_OVERWRITE 0x80
|
|
#define QPNP_WLED_LOOP_COMP_RES_DFLT_AMOLED_KOHM 320
|
|
#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 0xF0
|
|
#define QPNP_WLED_VLOOP_COMP_GM_OVERWRITE 0x80
|
|
#define QPNP_WLED_LOOP_EA_GM_DFLT_AMOLED 0x03
|
|
#define QPNP_WLED_LOOP_EA_GM_MIN 0x0
|
|
#define QPNP_WLED_LOOP_EA_GM_MAX 0xF
|
|
#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_CTRL_OVERWRITE 0x80
|
|
#define QPNP_WLED_AVDD_MIN_TRIM_VALUE -7
|
|
#define QPNP_WLED_AVDD_MAX_TRIM_VALUE 8
|
|
#define QPNP_WLED_AVDD_TRIM_CENTER_VALUE 7
|
|
|
|
#define QPNP_WLED_ILIM_MASK 0xF8
|
|
#define QPNP_WLED_ILIM_MIN_MA 105
|
|
#define QPNP_WLED_ILIM_MAX_MA 1980
|
|
#define QPNP_WLED_ILIM_STEP_MA 280
|
|
#define QPNP_WLED_DFLT_ILIM_MA 980
|
|
#define QPNP_WLED_ILIM_OVERWRITE 0x80
|
|
#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 0xF0
|
|
#define QPNP_WLED_SWITCH_FREQ_800_KHZ 800
|
|
#define QPNP_WLED_SWITCH_FREQ_1600_KHZ 1600
|
|
#define QPNP_WLED_OVP_MASK 0xFC
|
|
#define QPNP_WLED_OVP_17800_MV 17800
|
|
#define QPNP_WLED_OVP_19400_MV 19400
|
|
#define QPNP_WLED_OVP_29500_MV 29500
|
|
#define QPNP_WLED_OVP_31000_MV 31000
|
|
#define QPNP_WLED_TEST4_EN_VREF_UP 0x32
|
|
#define QPNP_WLED_INT_EN_SET_OVP_DIS 0x00
|
|
#define QPNP_WLED_INT_EN_SET_OVP_EN 0x02
|
|
#define QPNP_WLED_OVP_FLT_SLEEP_US 10
|
|
#define QPNP_WLED_TEST4_EN_IIND_UP 0x1
|
|
|
|
/* 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 0xF8
|
|
#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 0xF0
|
|
#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 0x7F
|
|
#define QPNP_WLED_CABC_SHIFT 7
|
|
#define QPNP_WLED_CURR_SINK_SHIFT 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_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 0x7F
|
|
#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_MASK 0x7F
|
|
#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_SUB 2
|
|
#define QPNP_WLED_SC_DEB_CYCLES_DFLT_AMOLED 4
|
|
#define QPNP_WLED_EXT_FET_DTEST2 0x09
|
|
|
|
#define QPNP_WLED_SEC_ACCESS_REG(b) (b + 0xD0)
|
|
#define QPNP_WLED_SEC_UNLOCK 0xA5
|
|
|
|
#define QPNP_WLED_MAX_STRINGS 4
|
|
#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
|
|
|
|
/* 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,
|
|
};
|
|
|
|
/**
|
|
* qpnp_wled - wed data structure
|
|
* @ cdev - led class device
|
|
* @ spmi - spmi device
|
|
* @ work - worker for led operation
|
|
* @ 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_trim_steps_from_center - number of steps to trim from center value
|
|
* @ ctrl_base - base address for wled ctrl
|
|
* @ sink_base - base address for wled sink
|
|
* @ ibb_base - base address for IBB(Inverting Buck Boost)
|
|
* @ lab_base - base address for LAB(LCD/AMOLED Boost)
|
|
* @ mod_freq_khz - modulator frequency in KHZ
|
|
* @ hyb_thres - threshold for hybrid dimming
|
|
* @ sync_dly_us - sync delay in us
|
|
* @ vref_mv - ref voltage in mv
|
|
* @ 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
|
|
* @ strings - supported list of strings
|
|
* @ num_strings - number of strings
|
|
* @ 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
|
|
* @ ibb_bias_active - activate display bias
|
|
* @ lab_fast_precharge - fast/slow precharge
|
|
* @ en_ext_pfet_sc_pro - enable sc protection on external pfet
|
|
*/
|
|
struct qpnp_wled {
|
|
struct led_classdev cdev;
|
|
struct spmi_device *spmi;
|
|
struct work_struct work;
|
|
struct mutex 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_trim_steps_from_center;
|
|
u16 ctrl_base;
|
|
u16 sink_base;
|
|
u16 mod_freq_khz;
|
|
u16 hyb_thres;
|
|
u16 sync_dly_us;
|
|
u16 vref_mv;
|
|
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;
|
|
u8 strings[QPNP_WLED_MAX_STRINGS];
|
|
u8 num_strings;
|
|
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;
|
|
};
|
|
|
|
/* helper to read a pmic register */
|
|
static int qpnp_wled_read_reg(struct qpnp_wled *wled, u8 *data, u16 addr)
|
|
{
|
|
int rc;
|
|
|
|
rc = spmi_ext_register_readl(wled->spmi->ctrl, wled->spmi->sid,
|
|
addr, data, 1);
|
|
if (rc < 0)
|
|
dev_err(&wled->spmi->dev,
|
|
"Error reading address: %x(%d)\n", addr, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* helper to write a pmic register */
|
|
static int qpnp_wled_write_reg(struct qpnp_wled *wled, u8 *data, u16 addr)
|
|
{
|
|
int rc;
|
|
|
|
rc = spmi_ext_register_writel(wled->spmi->ctrl, wled->spmi->sid,
|
|
addr, data, 1);
|
|
if (rc < 0)
|
|
dev_err(&wled->spmi->dev,
|
|
"Error writing address: %x(%d)\n", addr, rc);
|
|
|
|
dev_dbg(&wled->spmi->dev, "write: WLED_0x%x = 0x%x\n", addr, *data);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_wled_sec_access(struct qpnp_wled *wled, u16 base_addr)
|
|
{
|
|
int rc;
|
|
u8 reg = QPNP_WLED_SEC_UNLOCK;
|
|
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_SEC_ACCESS_REG(base_addr));
|
|
if (rc)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
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));
|
|
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));
|
|
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;
|
|
|
|
/* set brightness registers */
|
|
for (i = 0; i < wled->num_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]));
|
|
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]));
|
|
if (rc < 0)
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_wled_sync_reg_toggle(wled);
|
|
if (rc < 0) {
|
|
dev_err(&wled->spmi->dev, "Failed to toggle sync reg %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qpnp_wled_module_en(struct qpnp_wled *wled,
|
|
u16 base_addr, bool state)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
|
|
/* disable OVP fault interrupt */
|
|
if (state) {
|
|
reg = QPNP_WLED_INT_EN_SET_OVP_DIS;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_INT_EN_SET(base_addr));
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_wled_read_reg(wled, ®,
|
|
QPNP_WLED_MODULE_EN_REG(base_addr));
|
|
if (rc < 0)
|
|
return rc;
|
|
reg &= QPNP_WLED_MODULE_EN_MASK;
|
|
reg |= (state << QPNP_WLED_MODULE_EN_SHIFT);
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_MODULE_EN_REG(base_addr));
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* enable OVP fault interrupt */
|
|
if (state) {
|
|
udelay(QPNP_WLED_OVP_FLT_SLEEP_US);
|
|
reg = QPNP_WLED_INT_EN_SET_OVP_EN;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_INT_EN_SET(base_addr));
|
|
if (rc)
|
|
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->spmi->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->spmi->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->spmi->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->spmi->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->spmi->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;
|
|
|
|
if (sscanf(buf, "%d", &data) != 1)
|
|
return -EINVAL;
|
|
|
|
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;
|
|
|
|
if (sscanf(buf, "%d", &data) != 1)
|
|
return -EINVAL;
|
|
|
|
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));
|
|
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, temp;
|
|
u8 reg;
|
|
|
|
if (sscanf(buf, "%d", &data) != 1)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < wled->num_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;
|
|
|
|
rc = qpnp_wled_read_reg(wled, ®,
|
|
QPNP_WLED_FS_CURR_REG(wled->sink_base,
|
|
wled->strings[i]));
|
|
if (rc < 0)
|
|
return rc;
|
|
reg &= QPNP_WLED_FS_CURR_MASK;
|
|
temp = data / QPNP_WLED_FS_CURR_STEP_UA;
|
|
reg |= temp;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_FS_CURR_REG(wled->sink_base,
|
|
wled->strings[i]));
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
wled->fs_curr_ua = data;
|
|
|
|
rc = qpnp_wled_sync_reg_toggle(wled);
|
|
if (rc < 0) {
|
|
dev_err(&wled->spmi->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, (S_IRUGO | S_IWUSR | S_IWGRP),
|
|
qpnp_wled_dump_regs_show,
|
|
NULL),
|
|
__ATTR(dim_mode, (S_IRUGO | S_IWUSR | S_IWGRP),
|
|
qpnp_wled_dim_mode_show,
|
|
qpnp_wled_dim_mode_store),
|
|
__ATTR(fs_curr_ua, (S_IRUGO | S_IWUSR | S_IWGRP),
|
|
qpnp_wled_fs_curr_ua_show,
|
|
qpnp_wled_fs_curr_ua_store),
|
|
__ATTR(start_ramp, (S_IRUGO | S_IWUSR | S_IWGRP),
|
|
NULL,
|
|
qpnp_wled_ramp_store),
|
|
__ATTR(ramp_ms, (S_IRUGO | S_IWUSR | S_IWGRP),
|
|
qpnp_wled_ramp_ms_show,
|
|
qpnp_wled_ramp_ms_store),
|
|
__ATTR(ramp_step, (S_IRUGO | S_IWUSR | S_IWGRP),
|
|
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, rc;
|
|
|
|
wled = container_of(work, struct qpnp_wled, work);
|
|
|
|
level = wled->cdev.brightness;
|
|
|
|
mutex_lock(&wled->lock);
|
|
|
|
if (level) {
|
|
rc = qpnp_wled_set_level(wled, level);
|
|
if (rc) {
|
|
dev_err(&wled->spmi->dev, "wled set level failed\n");
|
|
goto unlock_mutex;
|
|
}
|
|
}
|
|
|
|
if (!!level != wled->prev_state) {
|
|
rc = qpnp_wled_module_en(wled, wled->ctrl_base, !!level);
|
|
|
|
if (rc) {
|
|
dev_err(&wled->spmi->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;
|
|
schedule_work(&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_access(wled, base_addr);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_DISP_SEL_REG(base_addr));
|
|
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_CTRL_OVERWRITE;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_PSM_CTRL_REG(wled->ctrl_base));
|
|
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));
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Configure the LOOP COMP GM register for AMOLED */
|
|
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;
|
|
|
|
rc = qpnp_wled_read_reg(wled, ®,
|
|
QPNP_WLED_VLOOP_COMP_GM_REG(wled->ctrl_base));
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
reg &= QPNP_WLED_VLOOP_COMP_GM_MASK;
|
|
reg |= (wled->loop_ea_gm | QPNP_WLED_VLOOP_COMP_GM_OVERWRITE);
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_VLOOP_COMP_GM_REG(wled->ctrl_base));
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Configure the Soft start Ramp delay for AMOLED */
|
|
reg = 0;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_SOFTSTART_RAMP_DLY(base_addr));
|
|
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;
|
|
|
|
rc = qpnp_wled_sec_access(wled, base_addr);
|
|
if (rc)
|
|
return rc;
|
|
|
|
reg |= QPNP_WLED_TEST4_EN_IIND_UP;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_TEST4_REG(base_addr));
|
|
if (rc)
|
|
return rc;
|
|
} else {
|
|
/*
|
|
* enable VREF_UP to avoid false ovp on low brightness for LCD
|
|
*/
|
|
rc = qpnp_wled_sec_access(wled, base_addr);
|
|
if (rc)
|
|
return rc;
|
|
|
|
reg = QPNP_WLED_TEST4_EN_VREF_UP;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_TEST4_REG(base_addr));
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ovp irq handler */
|
|
static irqreturn_t qpnp_wled_ovp_irq(int irq, void *_wled)
|
|
{
|
|
struct qpnp_wled *wled = _wled;
|
|
|
|
dev_dbg(&wled->spmi->dev, "ovp detected\n");
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* short circuit irq handler */
|
|
static irqreturn_t qpnp_wled_sc_irq(int irq, void *_wled)
|
|
{
|
|
struct qpnp_wled *wled = _wled;
|
|
|
|
dev_err(&wled->spmi->dev,
|
|
"Short circuit detected %d times\n", ++wled->sc_cnt);
|
|
|
|
qpnp_wled_module_en(wled, wled->ctrl_base, false);
|
|
msleep(QPNP_WLED_SC_DLY_MS);
|
|
qpnp_wled_module_en(wled, wled->ctrl_base, true);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* Configure WLED registers */
|
|
static int qpnp_wled_config(struct qpnp_wled *wled)
|
|
{
|
|
int rc, i, temp;
|
|
u8 reg = 0;
|
|
|
|
/* 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));
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Configure the VREF register */
|
|
if (wled->vref_mv < QPNP_WLED_VREF_MIN_MV)
|
|
wled->vref_mv = QPNP_WLED_VREF_MIN_MV;
|
|
else if (wled->vref_mv > QPNP_WLED_VREF_MAX_MV)
|
|
wled->vref_mv = QPNP_WLED_VREF_MAX_MV;
|
|
|
|
rc = qpnp_wled_read_reg(wled, ®,
|
|
QPNP_WLED_VREF_REG(wled->ctrl_base));
|
|
if (rc < 0)
|
|
return rc;
|
|
reg &= QPNP_WLED_VREF_MASK;
|
|
temp = wled->vref_mv - QPNP_WLED_VREF_MIN_MV;
|
|
reg |= (temp / QPNP_WLED_VREF_STEP_MV);
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_VREF_REG(wled->ctrl_base));
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Configure the ILIM register */
|
|
if (wled->ilim_ma < QPNP_WLED_ILIM_MIN_MA)
|
|
wled->ilim_ma = QPNP_WLED_ILIM_MIN_MA;
|
|
else if (wled->ilim_ma > QPNP_WLED_ILIM_MAX_MA)
|
|
wled->ilim_ma = QPNP_WLED_ILIM_MAX_MA;
|
|
|
|
rc = qpnp_wled_read_reg(wled, ®,
|
|
QPNP_WLED_ILIM_REG(wled->ctrl_base));
|
|
if (rc < 0)
|
|
return rc;
|
|
temp = (wled->ilim_ma / QPNP_WLED_ILIM_STEP_MA);
|
|
if (temp != (reg & ~QPNP_WLED_ILIM_MASK)) {
|
|
reg &= QPNP_WLED_ILIM_MASK;
|
|
reg |= temp;
|
|
reg |= QPNP_WLED_ILIM_OVERWRITE;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_ILIM_REG(wled->ctrl_base));
|
|
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));
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Configure the SWITCHING FREQ register */
|
|
if (wled->switch_freq_khz == QPNP_WLED_SWITCH_FREQ_1600_KHZ)
|
|
temp = QPNP_WLED_SWITCH_FREQ_1600_KHZ_CODE;
|
|
else
|
|
temp = QPNP_WLED_SWITCH_FREQ_800_KHZ_CODE;
|
|
|
|
rc = qpnp_wled_read_reg(wled, ®,
|
|
QPNP_WLED_SWITCH_FREQ_REG(wled->ctrl_base));
|
|
if (rc < 0)
|
|
return rc;
|
|
reg &= QPNP_WLED_SWITCH_FREQ_MASK;
|
|
reg |= temp;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_SWITCH_FREQ_REG(wled->ctrl_base));
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Configure the OVP register */
|
|
if (wled->ovp_mv <= QPNP_WLED_OVP_17800_MV) {
|
|
wled->ovp_mv = QPNP_WLED_OVP_17800_MV;
|
|
temp = 3;
|
|
} else if (wled->ovp_mv <= QPNP_WLED_OVP_19400_MV) {
|
|
wled->ovp_mv = QPNP_WLED_OVP_19400_MV;
|
|
temp = 2;
|
|
} else if (wled->ovp_mv <= QPNP_WLED_OVP_29500_MV) {
|
|
wled->ovp_mv = QPNP_WLED_OVP_29500_MV;
|
|
temp = 1;
|
|
} else {
|
|
wled->ovp_mv = QPNP_WLED_OVP_31000_MV;
|
|
temp = 0;
|
|
}
|
|
|
|
rc = qpnp_wled_read_reg(wled, ®,
|
|
QPNP_WLED_OVP_REG(wled->ctrl_base));
|
|
if (rc < 0)
|
|
return rc;
|
|
reg &= QPNP_WLED_OVP_MASK;
|
|
reg |= temp;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_OVP_REG(wled->ctrl_base));
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (wled->disp_type_amoled) {
|
|
/* Configure avdd trim register */
|
|
rc = qpnp_wled_sec_access(wled, wled->ctrl_base);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Check if wled->avdd_trim_steps_from_center is negative */
|
|
if ((s32)wled->avdd_trim_steps_from_center <
|
|
QPNP_WLED_AVDD_MIN_TRIM_VALUE) {
|
|
wled->avdd_trim_steps_from_center =
|
|
QPNP_WLED_AVDD_MIN_TRIM_VALUE;
|
|
} else if ((s32)wled->avdd_trim_steps_from_center >
|
|
QPNP_WLED_AVDD_MAX_TRIM_VALUE) {
|
|
wled->avdd_trim_steps_from_center =
|
|
QPNP_WLED_AVDD_MAX_TRIM_VALUE;
|
|
}
|
|
reg = wled->avdd_trim_steps_from_center +
|
|
QPNP_WLED_AVDD_TRIM_CENTER_VALUE;
|
|
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_REF_7P7_TRIM_REG(wled->ctrl_base));
|
|
if (rc)
|
|
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));
|
|
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));
|
|
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;
|
|
|
|
rc = qpnp_wled_sec_access(wled, wled->sink_base);
|
|
if (rc)
|
|
return rc;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_SINK_TEST5_REG(wled->sink_base));
|
|
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));
|
|
|
|
for (i = 0; i < wled->num_strings; i++) {
|
|
if (wled->strings[i] >= QPNP_WLED_MAX_STRINGS) {
|
|
dev_err(&wled->spmi->dev, "Invalid string number\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* MODULATOR */
|
|
rc = qpnp_wled_read_reg(wled, ®,
|
|
QPNP_WLED_MOD_EN_REG(wled->sink_base,
|
|
wled->strings[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,
|
|
wled->strings[i]));
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* SYNC DELAY */
|
|
if (wled->sync_dly_us > QPNP_WLED_SYNC_DLY_MAX_US)
|
|
wled->sync_dly_us = QPNP_WLED_SYNC_DLY_MAX_US;
|
|
|
|
rc = qpnp_wled_read_reg(wled, ®,
|
|
QPNP_WLED_SYNC_DLY_REG(wled->sink_base,
|
|
wled->strings[i]));
|
|
if (rc < 0)
|
|
return rc;
|
|
reg &= QPNP_WLED_SYNC_DLY_MASK;
|
|
temp = wled->sync_dly_us / QPNP_WLED_SYNC_DLY_STEP_US;
|
|
reg |= temp;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_SYNC_DLY_REG(wled->sink_base,
|
|
wled->strings[i]));
|
|
if (rc)
|
|
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;
|
|
|
|
rc = qpnp_wled_read_reg(wled, ®,
|
|
QPNP_WLED_FS_CURR_REG(wled->sink_base,
|
|
wled->strings[i]));
|
|
if (rc < 0)
|
|
return rc;
|
|
reg &= QPNP_WLED_FS_CURR_MASK;
|
|
temp = wled->fs_curr_ua / QPNP_WLED_FS_CURR_STEP_UA;
|
|
reg |= temp;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_FS_CURR_REG(wled->sink_base,
|
|
wled->strings[i]));
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* CABC */
|
|
rc = qpnp_wled_read_reg(wled, ®,
|
|
QPNP_WLED_CABC_REG(wled->sink_base,
|
|
wled->strings[i]));
|
|
if (rc < 0)
|
|
return rc;
|
|
reg &= QPNP_WLED_CABC_MASK;
|
|
reg |= (wled->en_cabc << QPNP_WLED_CABC_SHIFT);
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_CABC_REG(wled->sink_base,
|
|
wled->strings[i]));
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Enable CURRENT SINK */
|
|
rc = qpnp_wled_read_reg(wled, ®,
|
|
QPNP_WLED_CURR_SINK_REG(wled->sink_base));
|
|
if (rc < 0)
|
|
return rc;
|
|
temp = wled->strings[i] + QPNP_WLED_CURR_SINK_SHIFT;
|
|
reg |= (1 << temp);
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_CURR_SINK_REG(wled->sink_base));
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_wled_sync_reg_toggle(wled);
|
|
if (rc < 0) {
|
|
dev_err(&wled->spmi->dev, "Failed to toggle sync reg %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* setup ovp and sc irqs */
|
|
if (wled->ovp_irq >= 0) {
|
|
rc = devm_request_threaded_irq(&wled->spmi->dev, wled->ovp_irq,
|
|
NULL, qpnp_wled_ovp_irq,
|
|
QPNP_IRQ_FLAGS,
|
|
"qpnp_wled_ovp_irq", wled);
|
|
if (rc < 0) {
|
|
dev_err(&wled->spmi->dev,
|
|
"Unable to request ovp(%d) IRQ(err:%d)\n",
|
|
wled->ovp_irq, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (wled->sc_irq >= 0) {
|
|
wled->sc_cnt = 0;
|
|
rc = devm_request_threaded_irq(&wled->spmi->dev, wled->sc_irq,
|
|
NULL, qpnp_wled_sc_irq,
|
|
QPNP_IRQ_FLAGS,
|
|
"qpnp_wled_sc_irq", wled);
|
|
if (rc < 0) {
|
|
dev_err(&wled->spmi->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_MASK;
|
|
reg |= 1 << QPNP_WLED_EN_SC_SHIFT;
|
|
|
|
if (wled->disp_type_amoled) {
|
|
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_SUB;
|
|
reg |= ((temp << 1) | QPNP_WLED_SC_PRO_EN_DSCHGR);
|
|
}
|
|
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_SC_PRO_REG(wled->ctrl_base));
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (wled->en_ext_pfet_sc_pro) {
|
|
rc = qpnp_wled_sec_access(wled, wled->ctrl_base);
|
|
if (rc)
|
|
return rc;
|
|
|
|
reg = QPNP_WLED_EXT_FET_DTEST2;
|
|
rc = qpnp_wled_write_reg(wled, ®,
|
|
QPNP_WLED_TEST1_REG(wled->ctrl_base));
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* parse wled dtsi parameters */
|
|
static int qpnp_wled_parse_dt(struct qpnp_wled *wled)
|
|
{
|
|
struct spmi_device *spmi = wled->spmi;
|
|
struct property *prop;
|
|
const char *temp_str;
|
|
u32 temp_val;
|
|
int rc, i;
|
|
u8 *strings;
|
|
|
|
wled->cdev.name = "wled";
|
|
rc = of_property_read_string(spmi->dev.of_node,
|
|
"linux,name", &wled->cdev.name);
|
|
if (rc && (rc != -EINVAL)) {
|
|
dev_err(&spmi->dev, "Unable to read led name\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->cdev.default_trigger = QPNP_WLED_TRIGGER_NONE;
|
|
rc = of_property_read_string(spmi->dev.of_node, "linux,default-trigger",
|
|
&wled->cdev.default_trigger);
|
|
if (rc && (rc != -EINVAL)) {
|
|
dev_err(&spmi->dev, "Unable to read led trigger\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->disp_type_amoled = of_property_read_bool(spmi->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(spmi->dev.of_node,
|
|
"qcom,vref-psm-mv", &temp_val);
|
|
if (!rc) {
|
|
wled->vref_psm_mv = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read vref-psm\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->loop_comp_res_kohm =
|
|
QPNP_WLED_LOOP_COMP_RES_DFLT_AMOLED_KOHM;
|
|
rc = of_property_read_u32(spmi->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(&spmi->dev, "Unable to read loop-comp-res-kohm\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->loop_ea_gm = QPNP_WLED_LOOP_EA_GM_DFLT_AMOLED;
|
|
rc = of_property_read_u32(spmi->dev.of_node,
|
|
"qcom,loop-ea-gm", &temp_val);
|
|
if (!rc) {
|
|
wled->loop_ea_gm = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read loop-ea-gm\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->sc_deb_cycles = QPNP_WLED_SC_DEB_CYCLES_DFLT_AMOLED;
|
|
rc = of_property_read_u32(spmi->dev.of_node,
|
|
"qcom,sc-deb-cycles", &temp_val);
|
|
if (!rc) {
|
|
wled->sc_deb_cycles = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read sc debounce cycles\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->avdd_trim_steps_from_center = 0;
|
|
rc = of_property_read_u32(spmi->dev.of_node,
|
|
"qcom,avdd-trim-steps-from-center", &temp_val);
|
|
if (!rc) {
|
|
wled->avdd_trim_steps_from_center = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read avdd trim steps from center value\n");
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
wled->fdbk_op = QPNP_WLED_FDBK_AUTO;
|
|
rc = of_property_read_string(spmi->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(&spmi->dev, "Unable to read feedback output\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->vref_mv = QPNP_WLED_DFLT_VREF_MV;
|
|
rc = of_property_read_u32(spmi->dev.of_node,
|
|
"qcom,vref-mv", &temp_val);
|
|
if (!rc) {
|
|
wled->vref_mv = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read vref\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->switch_freq_khz = QPNP_WLED_SWITCH_FREQ_800_KHZ;
|
|
rc = of_property_read_u32(spmi->dev.of_node,
|
|
"qcom,switch-freq-khz", &temp_val);
|
|
if (!rc) {
|
|
wled->switch_freq_khz = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read switch freq\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->ovp_mv = QPNP_WLED_OVP_29500_MV;
|
|
rc = of_property_read_u32(spmi->dev.of_node,
|
|
"qcom,ovp-mv", &temp_val);
|
|
if (!rc) {
|
|
wled->ovp_mv = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read vref\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->ilim_ma = QPNP_WLED_DFLT_ILIM_MA;
|
|
rc = of_property_read_u32(spmi->dev.of_node,
|
|
"qcom,ilim-ma", &temp_val);
|
|
if (!rc) {
|
|
wled->ilim_ma = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read ilim\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->boost_duty_ns = QPNP_WLED_DEF_BOOST_DUTY_NS;
|
|
rc = of_property_read_u32(spmi->dev.of_node,
|
|
"qcom,boost-duty-ns", &temp_val);
|
|
if (!rc) {
|
|
wled->boost_duty_ns = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read boost duty\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_9600_KHZ;
|
|
rc = of_property_read_u32(spmi->dev.of_node,
|
|
"qcom,mod-freq-khz", &temp_val);
|
|
if (!rc) {
|
|
wled->mod_freq_khz = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read modulation freq\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->dim_mode = QPNP_WLED_DIM_HYBRID;
|
|
rc = of_property_read_string(spmi->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(&spmi->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(spmi->dev.of_node,
|
|
"qcom,hyb-thres", &temp_val);
|
|
if (!rc) {
|
|
wled->hyb_thres = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read hyb threshold\n");
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
wled->sync_dly_us = QPNP_WLED_DEF_SYNC_DLY_US;
|
|
rc = of_property_read_u32(spmi->dev.of_node,
|
|
"qcom,sync-dly-us", &temp_val);
|
|
if (!rc) {
|
|
wled->sync_dly_us = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read sync delay\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->fs_curr_ua = QPNP_WLED_FS_CURR_MAX_UA;
|
|
rc = of_property_read_u32(spmi->dev.of_node,
|
|
"qcom,fs-curr-ua", &temp_val);
|
|
if (!rc) {
|
|
wled->fs_curr_ua = temp_val;
|
|
} else if (rc != -EINVAL) {
|
|
dev_err(&spmi->dev, "Unable to read full scale current\n");
|
|
return rc;
|
|
}
|
|
|
|
wled->cons_sync_write_delay_us = 0;
|
|
rc = of_property_read_u32(spmi->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(spmi->dev.of_node,
|
|
"qcom,en-9b-dim-res");
|
|
wled->en_phase_stag = of_property_read_bool(spmi->dev.of_node,
|
|
"qcom,en-phase-stag");
|
|
wled->en_cabc = of_property_read_bool(spmi->dev.of_node,
|
|
"qcom,en-cabc");
|
|
|
|
prop = of_find_property(spmi->dev.of_node,
|
|
"qcom,led-strings-list", &temp_val);
|
|
if (!prop || !temp_val || temp_val > QPNP_WLED_MAX_STRINGS) {
|
|
dev_err(&spmi->dev, "Invalid strings info, use default");
|
|
wled->num_strings = QPNP_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 = spmi_get_irq_byname(spmi, NULL, "ovp-irq");
|
|
if (wled->ovp_irq < 0)
|
|
dev_dbg(&spmi->dev, "ovp irq is not used\n");
|
|
|
|
wled->sc_irq = spmi_get_irq_byname(spmi, NULL, "sc-irq");
|
|
if (wled->sc_irq < 0)
|
|
dev_dbg(&spmi->dev, "sc irq is not used\n");
|
|
|
|
wled->en_ext_pfet_sc_pro = of_property_read_bool(spmi->dev.of_node,
|
|
"qcom,en-ext-pfet-sc-pro");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qpnp_wled_probe(struct spmi_device *spmi)
|
|
{
|
|
struct qpnp_wled *wled;
|
|
struct resource *wled_resource;
|
|
int rc, i;
|
|
|
|
wled = devm_kzalloc(&spmi->dev, sizeof(*wled), GFP_KERNEL);
|
|
if (!wled)
|
|
return -ENOMEM;
|
|
|
|
wled->spmi = spmi;
|
|
|
|
wled_resource = spmi_get_resource_byname(spmi, NULL, IORESOURCE_MEM,
|
|
QPNP_WLED_SINK_BASE);
|
|
if (!wled_resource) {
|
|
dev_err(&spmi->dev, "Unable to get wled sink base address\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
wled->sink_base = wled_resource->start;
|
|
|
|
wled_resource = spmi_get_resource_byname(spmi, NULL, IORESOURCE_MEM,
|
|
QPNP_WLED_CTRL_BASE);
|
|
if (!wled_resource) {
|
|
dev_err(&spmi->dev, "Unable to get wled ctrl base address\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
wled->ctrl_base = wled_resource->start;
|
|
|
|
dev_set_drvdata(&spmi->dev, wled);
|
|
|
|
rc = qpnp_wled_parse_dt(wled);
|
|
if (rc) {
|
|
dev_err(&spmi->dev, "DT parsing failed\n");
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_wled_config(wled);
|
|
if (rc) {
|
|
dev_err(&spmi->dev, "wled config failed\n");
|
|
return rc;
|
|
}
|
|
|
|
mutex_init(&wled->lock);
|
|
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(&spmi->dev, &wled->cdev);
|
|
if (rc) {
|
|
dev_err(&spmi->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(&spmi->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);
|
|
mutex_destroy(&wled->lock);
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_wled_remove(struct spmi_device *spmi)
|
|
{
|
|
struct qpnp_wled *wled = dev_get_drvdata(&spmi->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);
|
|
mutex_destroy(&wled->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct of_device_id spmi_match_table[] = {
|
|
{ .compatible = "qcom,qpnp-wled",},
|
|
{ },
|
|
};
|
|
|
|
static struct spmi_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 spmi_driver_register(&qpnp_wled_driver);
|
|
}
|
|
module_init(qpnp_wled_init);
|
|
|
|
static void __exit qpnp_wled_exit(void)
|
|
{
|
|
spmi_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");
|