blob: 3dab9d5cf47ac5763ef4505b91dc4c0d89559674 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015, Sony Mobile Communications, AB.
*/
/*
* Copyright (c) 2018-2020, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "WLED: %s: " fmt, __func__
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/ktime.h>
#include <linux/kernel.h>
#include <linux/backlight.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/spinlock.h>
#include <linux/qpnp/qpnp-revid.h>
#include <linux/leds-qpnp-flash.h>
#include "../../leds/leds.h"
/* General definitions */
#define WLED_DEFAULT_BRIGHTNESS 2048
#define WLED_MAX_BRIGHTNESS_12B 4095
#define WLED_MAX_BRIGHTNESS_15B 32767
#define WLED_SOFT_START_DLY_US 10000
/* WLED control registers */
#define WLED_CTRL_FAULT_STATUS 0x08
#define WLED_CTRL_ILIM_FAULT_BIT BIT(0)
#define WLED_CTRL_OVP_FAULT_BIT BIT(1)
#define WLED_CTRL_SC_FAULT_BIT BIT(2)
#define WLED5_CTRL_OVP_PRE_ALARM_BIT BIT(4)
#define WLED_CTRL_INT_RT_STS 0x10
#define WLED_CTRL_OVP_FLT_RT_STS_BIT BIT(1)
#define WLED_CTRL_MOD_ENABLE 0x46
#define WLED_CTRL_MOD_EN_MASK BIT(7)
#define WLED_CTRL_MODULE_EN_SHIFT 7
#define WLED_CTRL_FDBK_OP 0x48
#define WLED_CTRL_SWITCH_FREQ 0x4c
#define WLED_CTRL_SWITCH_FREQ_MASK GENMASK(3, 0)
#define WLED_CTRL_OVP 0x4d
#define WLED_CTRL_OVP_MASK GENMASK(1, 0)
#define WLED5_CTRL_OVP_MASK GENMASK(3, 0)
#define WLED_CTRL_ILIM 0x4e
#define WLED_CTRL_ILIM_MASK GENMASK(2, 0)
#define WLED_CTRL_SHORT_PROTECT 0x5e
#define WLED_CTRL_SHORT_EN_MASK BIT(7)
#define WLED_CTRL_SEC_ACCESS 0xd0
#define WLED_CTRL_SEC_UNLOCK 0xa5
#define WLED_CTRL_TEST1 0xe2
#define WLED_EXT_FET_DTEST2 0x09
/* WLED sink registers */
#define WLED_SINK_CURR_SINK_EN 0x46
#define WLED_SINK_CURR_SINK_MASK GENMASK(7, 4)
#define WLED_SINK_CURR_SINK_SHFT 0x04
#define WLED_SINK_SYNC 0x47
#define WLED_SINK_SYNC_MASK GENMASK(3, 0)
#define WLED_SINK_SYNC_LED1 BIT(0)
#define WLED_SINK_SYNC_LED2 BIT(1)
#define WLED_SINK_SYNC_LED3 BIT(2)
#define WLED_SINK_SYNC_LED4 BIT(3)
#define WLED_SINK_SYNC_CLEAR 0x00
#define WLED_SINK_MOD_EN_REG(n) (0x50 + (n * 0x10))
#define WLED_SINK_REG_STR_MOD_MASK BIT(7)
#define WLED_SINK_REG_STR_MOD_EN BIT(7)
#define WLED_SINK_SYNC_DLY_REG(n) (0x51 + (n * 0x10))
#define WLED_SINK_FS_CURR_REG(n) (0x52 + (n * 0x10))
#define WLED_SINK_FS_MASK GENMASK(3, 0)
#define WLED_SINK_CABC_REG(n) (0x56 + (n * 0x10))
#define WLED_SINK_CABC_MASK BIT(7)
#define WLED_SINK_CABC_EN BIT(7)
#define WLED_SINK_BRIGHT_LSB_REG(n) (0x57 + (n * 0x10))
#define WLED_SINK_BRIGHT_MSB_REG(n) (0x58 + (n * 0x10))
/* WLED5 specific control registers */
#define WLED5_CTRL_STATUS 0x07
#define WLED5_CTRL_SH_FOR_SOFTSTART_REG 0x58
#define WLED5_SOFTSTART_EN_SH_SS BIT(0)
#define WLED5_CTRL_OVP_INT_CTL_REG 0x5f
#define WLED5_OVP_INT_N_MASK GENMASK(6, 4)
#define WLED5_OVP_INT_N_SHIFT 4
#define WLED5_OVP_INT_TIMER_MASK GENMASK(2, 0)
#define WLED5_CTRL_PRE_FLASH_BRT_REG 0x61
#define WLED5_CTRL_PRE_FLASH_SYNC_REG 0x62
#define WLED5_CTRL_FLASH_BRT_REG 0x63
#define WLED5_CTRL_FLASH_SYNC_REG 0x64
#define WLED5_CTRL_FLASH_STEP_CTL_REG 0x65
#define WLED5_CTRL_FLASH_STEP_MASK GENMASK(2, 0)
#define WLED5_CTRL_FLASH_HDRM_REG 0x69
#define WLED5_CTRL_TEST4_REG 0xe5
#define WLED5_TEST4_EN_SH_SS BIT(5)
#define WLED5_CTRL_PBUS_WRITE_SYNC_CTL 0xef
/* WLED5 specific sink registers */
#define WLED5_SINK_MOD_A_EN_REG 0x50
#define WLED5_SINK_MOD_B_EN_REG 0x60
#define WLED5_SINK_MOD_EN BIT(7)
#define WLED5_SINK_MOD_A_SRC_SEL_REG 0x51
#define WLED5_SINK_MOD_B_SRC_SEL_REG 0x61
#define WLED5_SINK_MOD_SRC_SEL_HIGH 0
#define WLED5_SINK_MOD_SRC_SEL_CABC1 BIT(0)
#define WLED5_SINK_MOD_SRC_SEL_CABC2 BIT(1)
#define WLED5_SINK_MOD_SRC_SEL_EXT 0x03
#define WLED5_SINK_MOD_SRC_SEL_MASK GENMASK(1, 0)
#define WLED5_SINK_MOD_A_BR_WID_SEL_REG 0x52
#define WLED5_SINK_MOD_B_BR_WID_SEL_REG 0x62
#define WLED5_SINK_BRT_WIDTH_12B 0
#define WLED5_SINK_BRT_WIDTH_15B 1
#define WLED5_SINK_MOD_A_BRT_LSB_REG 0x53
#define WLED5_SINK_MOD_A_BRT_MSB_REG 0x54
#define WLED5_SINK_MOD_B_BRT_LSB_REG 0x63
#define WLED5_SINK_MOD_B_BRT_MSB_REG 0x64
#define WLED5_SINK_MOD_SYNC_BIT_REG 0x65
#define WLED5_SINK_SYNC_MODA_BIT BIT(0)
#define WLED5_SINK_SYNC_MODB_BIT BIT(1)
#define WLED5_SINK_SYNC_MASK GENMASK(1, 0)
#define WLED5_SINK_FS_CURR_REG(n) (0x72 + (n * 0x10))
#define WLED5_SINK_SRC_SEL_REG(n) (0x73 + (n * 0x10))
#define WLED5_SINK_SRC_SEL_MODA 0
#define WLED5_SINK_SRC_SEL_MODB 1
#define WLED5_SINK_SRC_SEL_MASK GENMASK(1, 0)
#define WLED5_SINK_FLASH_CTL_REG 0xb0
#define WLED5_SINK_FLASH_EN BIT(7)
#define WLED5_SINK_PRE_FLASH_EN BIT(6)
#define WLED5_SINK_FLASH_SINK_EN_REG 0xb1
#define WLED5_SINK_FLASH_FSC_REG 0xb2
#define WLED5_SINK_FLASH_FSC_MASK GENMASK(3, 0)
#define WLED5_SINK_FLASH_SYNC_BIT_REG 0xb3
#define WLED5_SINK_FLASH_FSC_SYNC_EN BIT(0)
#define WLED5_SINK_FLASH_TIMER_CTL_REG 0xb5
#define WLED5_DIS_PRE_FLASH_TIMER BIT(7)
#define WLED5_PRE_FLASH_SAFETY_TIME GENMASK(6, 4)
#define WLED5_PRE_FLASH_SAFETY_SHIFT 4
#define WLED5_DIS_FLASH_TIMER BIT(3)
#define WLED5_FLASH_SAFETY_TIME GENMASK(2, 0)
#define WLED5_SINK_FLASH_SHDN_CLR_REG 0xb6
enum wled_version {
WLED_PMI8998 = 4,
WLED_PM660L,
WLED_PM8150L,
};
enum wled_flash_mode {
WLED_FLASH_OFF,
WLED_PRE_FLASH,
WLED_FLASH,
};
static const int version_table[] = {
[0] = WLED_PMI8998,
[1] = WLED_PM660L,
[2] = WLED_PM8150L,
};
struct wled_config {
int boost_i_limit;
int ovp;
int switch_freq;
int fs_current;
int string_cfg;
int mod_sel;
int cabc_sel;
bool en_cabc;
bool ext_pfet_sc_pro_en;
bool auto_calib_enabled;
};
struct wled_flash_config {
int fs_current;
int step_delay;
int safety_timer;
};
struct wled {
const char *name;
struct platform_device *pdev;
struct regmap *regmap;
struct pmic_revid_data *pmic_rev_id;
struct power_supply *bms_psy;
struct mutex lock;
struct wled_config cfg;
ktime_t last_sc_event_time;
ktime_t start_ovp_fault_time;
u16 sink_addr;
u16 ctrl_addr;
u16 auto_calibration_ovp_count;
u32 brightness;
u32 max_brightness;
u32 sc_count;
const int *version;
int sc_irq;
int ovp_irq;
int flash_irq;
int pre_flash_irq;
bool prev_state;
bool ovp_irq_disabled;
bool auto_calib_done;
bool force_mod_disable;
bool cabc_disabled;
int (*cabc_config)(struct wled *wled, bool enable);
struct led_classdev flash_cdev;
struct led_classdev torch_cdev;
struct led_classdev switch_cdev;
struct wled_flash_config fparams;
struct wled_flash_config tparams;
spinlock_t flash_lock;
enum wled_flash_mode flash_mode;
u8 num_strings;
u32 leds_per_string;
};
enum wled5_mod_sel {
MOD_A,
MOD_B,
MOD_MAX,
};
static const u8 wled5_brt_reg[MOD_MAX] = {
[MOD_A] = WLED5_SINK_MOD_A_BRT_LSB_REG,
[MOD_B] = WLED5_SINK_MOD_B_BRT_LSB_REG,
};
static const u8 wled5_src_sel_reg[MOD_MAX] = {
[MOD_A] = WLED5_SINK_MOD_A_SRC_SEL_REG,
[MOD_B] = WLED5_SINK_MOD_B_SRC_SEL_REG,
};
static const u8 wled5_brt_wid_sel_reg[MOD_MAX] = {
[MOD_A] = WLED5_SINK_MOD_A_BR_WID_SEL_REG,
[MOD_B] = WLED5_SINK_MOD_B_BR_WID_SEL_REG,
};
static int wled_flash_setup(struct wled *wled);
static inline bool is_wled4(struct wled *wled)
{
if (*wled->version == WLED_PMI8998 || *wled->version == WLED_PM660L)
return true;
return false;
}
static inline bool is_wled5(struct wled *wled)
{
if (*wled->version == WLED_PM8150L)
return true;
return false;
}
static int wled_module_enable(struct wled *wled, int val)
{
int rc;
int reg;
if (wled->force_mod_disable)
return 0;
/* Force HFRC off */
if (*wled->version == WLED_PM8150L) {
reg = val ? 0 : 3;
rc = regmap_write(wled->regmap, wled->ctrl_addr +
WLED5_CTRL_PBUS_WRITE_SYNC_CTL, reg);
if (rc < 0)
return rc;
}
rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
WLED_CTRL_MOD_ENABLE, WLED_CTRL_MOD_EN_MASK,
val << WLED_CTRL_MODULE_EN_SHIFT);
if (rc < 0)
return rc;
/* Force HFRC off */
if (*wled->version == WLED_PM8150L && val) {
rc = regmap_write(wled->regmap, wled->sink_addr +
WLED5_SINK_FLASH_SHDN_CLR_REG, 0);
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. Keep the OVP
* interrupt disabled when the module is disabled.
*/
if (val) {
usleep_range(WLED_SOFT_START_DLY_US,
WLED_SOFT_START_DLY_US + 1000);
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;
}
}
return rc;
}
static int wled_get_brightness(struct backlight_device *bl)
{
struct wled *wled = bl_get_data(bl);
return wled->brightness;
}
static int wled_sync_toggle(struct wled *wled)
{
int rc;
rc = regmap_update_bits(wled->regmap,
wled->sink_addr + WLED_SINK_SYNC,
WLED_SINK_SYNC_MASK, WLED_SINK_SYNC_MASK);
if (rc < 0)
return rc;
rc = regmap_update_bits(wled->regmap,
wled->sink_addr + WLED_SINK_SYNC,
WLED_SINK_SYNC_MASK, WLED_SINK_SYNC_CLEAR);
return rc;
}
static int wled5_sample_hold_control(struct wled *wled, u16 brightness,
bool enable)
{
int rc;
u16 offset, threshold;
u8 val, mask;
/*
* Control S_H only when module was disabled and a lower brightness
* of < 1% is set.
*/
if (wled->prev_state)
return 0;
/* If CABC is enabled, then don't do anything for now */
if (!wled->cabc_disabled)
return 0;
/* 1 % threshold to enable the workaround */
threshold = DIV_ROUND_UP(wled->max_brightness, 100);
/* If brightness is > 1%, don't do anything */
if (brightness > threshold)
return 0;
/* Wait for ~5ms before enabling S_H */
if (enable)
usleep_range(5000, 5010);
/* Disable S_H if brightness is < 1% */
if (wled->pmic_rev_id->rev4 == PM8150L_V3P0_REV4) {
offset = WLED5_CTRL_SH_FOR_SOFTSTART_REG;
val = enable ? WLED5_SOFTSTART_EN_SH_SS : 0;
mask = WLED5_SOFTSTART_EN_SH_SS;
} else {
offset = WLED5_CTRL_TEST4_REG;
val = enable ? WLED5_TEST4_EN_SH_SS : 0;
mask = WLED5_TEST4_EN_SH_SS;
}
rc = regmap_update_bits(wled->regmap,
wled->ctrl_addr + offset, mask, val);
if (rc < 0)
pr_err("Error in writing offset 0x%02X rc=%d\n", offset, rc);
return rc;
}
static int wled5_set_brightness(struct wled *wled, u16 brightness)
{
int rc, offset;
u16 low_limit = wled->max_brightness * 1 / 1000;
u8 val, v[2], brightness_msb_mask;
/* WLED5's lower limit is 0.1% */
if (brightness > 0 && brightness < low_limit)
brightness = low_limit;
brightness_msb_mask = 0xf;
if (wled->max_brightness == WLED_MAX_BRIGHTNESS_15B)
brightness_msb_mask = 0x7f;
v[0] = brightness & 0xff;
v[1] = (brightness >> 8) & brightness_msb_mask;
offset = wled5_brt_reg[wled->cfg.mod_sel];
rc = regmap_bulk_write(wled->regmap, wled->sink_addr + offset,
v, 2);
if (rc < 0)
return rc;
/* Update brightness values to modulator in WLED5 */
val = (wled->cfg.mod_sel == MOD_A) ? WLED5_SINK_SYNC_MODA_BIT :
WLED5_SINK_SYNC_MODB_BIT;
rc = regmap_update_bits(wled->regmap,
wled->sink_addr + WLED5_SINK_MOD_SYNC_BIT_REG,
WLED5_SINK_SYNC_MASK, val);
if (rc < 0)
return rc;
val = 0;
rc = regmap_update_bits(wled->regmap,
wled->sink_addr + WLED5_SINK_MOD_SYNC_BIT_REG,
WLED_SINK_SYNC_MASK, val);
return rc;
}
static int wled4_set_brightness(struct wled *wled, u16 brightness)
{
int rc, i;
u16 low_limit = wled->max_brightness * 4 / 1000;
u8 string_cfg = wled->cfg.string_cfg;
u8 v[2];
/* WLED4's lower limit of operation is 0.4% */
if (brightness > 0 && brightness < low_limit)
brightness = low_limit;
v[0] = brightness & 0xff;
v[1] = (brightness >> 8) & 0xf;
for (i = 0; (string_cfg >> i) != 0; i++) {
rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
if (rc < 0)
return rc;
}
return 0;
}
static int wled_set_brightness(struct wled *wled, u16 brightness)
{
if (is_wled4(wled))
return wled4_set_brightness(wled, brightness);
else if (is_wled5(wled))
return wled5_set_brightness(wled, brightness);
return 0;
}
static int wled_update_status(struct backlight_device *bl)
{
struct wled *wled = bl_get_data(bl);
u16 brightness = bl->props.brightness;
int rc;
if (bl->props.power != FB_BLANK_UNBLANK ||
bl->props.fb_blank != FB_BLANK_UNBLANK ||
bl->props.state & BL_CORE_FBBLANK)
brightness = 0;
mutex_lock(&wled->lock);
if (brightness) {
rc = wled_set_brightness(wled, brightness);
if (rc < 0) {
pr_err("wled failed to set brightness rc:%d\n", rc);
goto unlock_mutex;
}
if (is_wled5(wled)) {
rc = wled5_sample_hold_control(wled, brightness, false);
if (rc < 0) {
pr_err("wled disabling sample and hold failed rc:%d\n",
rc);
goto unlock_mutex;
}
}
if (!!brightness != wled->prev_state) {
rc = wled_module_enable(wled, !!brightness);
if (rc < 0) {
pr_err("wled enable failed rc:%d\n", rc);
goto unlock_mutex;
}
}
if (is_wled5(wled)) {
rc = wled5_sample_hold_control(wled, brightness, true);
if (rc < 0) {
pr_err("wled enabling sample and hold failed rc:%d\n",
rc);
goto unlock_mutex;
}
}
} else {
rc = wled_module_enable(wled, brightness);
if (rc < 0) {
pr_err("wled disable failed rc:%d\n", rc);
goto unlock_mutex;
}
}
wled->prev_state = !!brightness;
if (is_wled4(wled)) {
rc = wled_sync_toggle(wled);
if (rc < 0) {
pr_err("wled sync failed rc:%d\n", rc);
goto unlock_mutex;
}
}
wled->brightness = brightness;
unlock_mutex:
mutex_unlock(&wled->lock);
return rc;
}
#define WLED_SC_DLY_MS 20
#define WLED_SC_CNT_MAX 5
#define WLED_SC_RESET_CNT_DLY_US 1000000
static irqreturn_t wled_sc_irq_handler(int irq, void *_wled)
{
struct wled *wled = _wled;
int rc;
u32 val;
s64 elapsed_time;
rc = regmap_read(wled->regmap,
wled->ctrl_addr + WLED_CTRL_FAULT_STATUS, &val);
if (rc < 0) {
pr_err("Error in reading WLED_FAULT_STATUS rc=%d\n", rc);
return IRQ_HANDLED;
}
wled->sc_count++;
pr_err("WLED short circuit detected %d times fault_status=%x\n",
wled->sc_count, val);
mutex_lock(&wled->lock);
rc = wled_module_enable(wled, false);
if (rc < 0) {
pr_err("wled disable failed rc:%d\n", rc);
goto unlock_mutex;
}
elapsed_time = ktime_us_delta(ktime_get(),
wled->last_sc_event_time);
if (elapsed_time > WLED_SC_RESET_CNT_DLY_US) {
wled->sc_count = 0;
} else if (wled->sc_count > WLED_SC_CNT_MAX) {
pr_err("SC trigged %d times, disabling WLED forever!\n",
wled->sc_count);
goto unlock_mutex;
}
wled->last_sc_event_time = ktime_get();
msleep(WLED_SC_DLY_MS);
rc = wled_module_enable(wled, true);
if (rc < 0)
pr_err("wled enable failed rc:%d\n", rc);
unlock_mutex:
mutex_unlock(&wled->lock);
return IRQ_HANDLED;
}
static int wled5_cabc_config(struct wled *wled, bool enable)
{
int rc, offset;
u8 reg;
if (wled->cabc_disabled)
return 0;
reg = enable ? wled->cfg.cabc_sel : 0;
offset = wled5_src_sel_reg[wled->cfg.mod_sel];
rc = regmap_update_bits(wled->regmap, wled->sink_addr + offset,
WLED5_SINK_MOD_SRC_SEL_MASK, reg);
if (rc < 0) {
pr_err("Error in configuring CABC rc=%d\n", rc);
return rc;
}
if (!wled->cfg.cabc_sel)
wled->cabc_disabled = true;
return 0;
}
static int wled4_cabc_config(struct wled *wled, bool enable)
{
int i, rc;
u8 reg;
if (wled->cabc_disabled)
return 0;
for (i = 0; (wled->cfg.string_cfg >> i) != 0; i++) {
reg = enable ? WLED_SINK_CABC_EN : 0;
rc = regmap_update_bits(wled->regmap, wled->sink_addr +
WLED_SINK_CABC_REG(i),
WLED_SINK_CABC_MASK, reg);
if (rc < 0)
return rc;
}
if (!wled->cfg.en_cabc)
wled->cabc_disabled = true;
return 0;
}
static int wled_get_ovp_fault_status(struct wled *wled, bool *fault_set)
{
int rc;
u32 int_rt_sts, fault_sts;
*fault_set = false;
rc = regmap_read(wled->regmap,
wled->ctrl_addr + WLED_CTRL_INT_RT_STS,
&int_rt_sts);
if (rc < 0) {
pr_err("Failed to read INT_RT_STS rc=%d\n", rc);
return rc;
}
rc = regmap_read(wled->regmap,
wled->ctrl_addr + WLED_CTRL_FAULT_STATUS,
&fault_sts);
if (rc < 0) {
pr_err("Failed to read FAULT_STATUS rc=%d\n", rc);
return rc;
}
if (int_rt_sts & WLED_CTRL_OVP_FLT_RT_STS_BIT)
*fault_set = true;
if (is_wled4(wled) && (fault_sts & WLED_CTRL_OVP_FAULT_BIT))
*fault_set = true;
else if (is_wled5(wled) && (fault_sts & (WLED_CTRL_OVP_FAULT_BIT |
WLED5_CTRL_OVP_PRE_ALARM_BIT)))
*fault_set = true;
if (*fault_set)
pr_debug("WLED OVP fault detected, int_rt_sts=0x%x fault_sts=0x%x\n",
int_rt_sts, fault_sts);
return rc;
}
static void wled_get_ovp_delay(struct wled *wled, int *delay_time_us)
{
int rc;
u32 val;
u8 ovp_timer_ms[8] = {1, 2, 4, 8, 12, 16, 20, 24};
if (is_wled4(wled)) {
*delay_time_us = WLED_SOFT_START_DLY_US;
return;
}
/* For WLED5, get the delay based on OVP timer */
rc = regmap_read(wled->regmap, wled->ctrl_addr +
WLED5_CTRL_OVP_INT_CTL_REG, &val);
if (!rc)
*delay_time_us =
ovp_timer_ms[val & WLED5_OVP_INT_TIMER_MASK] * 1000;
else
*delay_time_us = 2 * WLED_SOFT_START_DLY_US;
pr_debug("delay_time_us: %d\n", *delay_time_us);
}
#define AUTO_CALIB_BRIGHTNESS 512
static int wled_auto_calibrate(struct wled *wled)
{
int rc = 0, i, delay_time_us;
u32 sink_config = 0;
u8 reg = 0, sink_test = 0, sink_valid = 0;
u8 string_cfg = wled->cfg.string_cfg;
bool fault_set;
if (wled->auto_calib_done)
return 0;
/* read configured sink configuration */
rc = regmap_read(wled->regmap, wled->sink_addr +
WLED_SINK_CURR_SINK_EN, &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 = regmap_update_bits(wled->regmap,
wled->ctrl_addr + WLED_CTRL_MOD_ENABLE,
WLED_CTRL_MOD_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 = wled_set_brightness(wled, AUTO_CALIB_BRIGHTNESS);
if (rc < 0) {
pr_err("Failed to set brightness for calibration rc=%d\n", rc);
goto failed_calib;
}
/* Disable CABC if it is enabled */
rc = wled->cabc_config(wled, false);
if (rc < 0)
goto failed_calib;
/* disable all sinks */
rc = regmap_write(wled->regmap,
wled->sink_addr + WLED_SINK_CURR_SINK_EN, 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; (string_cfg >> i) != 0; i++) {
sink_test = 1 << (WLED_SINK_CURR_SINK_SHFT + i);
/* Enable feedback control */
rc = regmap_write(wled->regmap, wled->ctrl_addr +
WLED_CTRL_FDBK_OP, 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 = regmap_write(wled->regmap, wled->sink_addr +
WLED_SINK_CURR_SINK_EN, 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 = regmap_update_bits(wled->regmap, wled->ctrl_addr +
WLED_CTRL_MOD_ENABLE,
WLED_CTRL_MOD_EN_MASK,
WLED_CTRL_MOD_EN_MASK);
if (rc < 0) {
pr_err("Failed to enable WLED module rc=%d\n", rc);
goto failed_calib;
}
wled_get_ovp_delay(wled, &delay_time_us);
if (delay_time_us < 20000)
usleep_range(delay_time_us, delay_time_us + 1000);
else
msleep(delay_time_us / 1000);
rc = wled_get_ovp_fault_status(wled, &fault_set);
if (rc < 0) {
pr_err("Error in getting OVP fault_sts, rc=%d\n", rc);
goto failed_calib;
}
if (fault_set)
pr_debug("WLED OVP fault detected with SINK %d\n",
i + 1);
else
sink_valid |= sink_test;
/* Disable the module */
rc = regmap_update_bits(wled->regmap,
wled->ctrl_addr + WLED_CTRL_MOD_ENABLE,
WLED_CTRL_MOD_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_err("No valid WLED sinks found\n");
wled->force_mod_disable = true;
goto failed_calib;
}
/* write the new sink configuration */
rc = regmap_write(wled->regmap,
wled->sink_addr + WLED_SINK_CURR_SINK_EN,
sink_config);
if (rc < 0) {
pr_err("Failed to reconfigure the default sink rc=%d\n", rc);
goto failed_calib;
}
if (is_wled5(wled)) {
/* Update the flash sink configuration as well */
rc = regmap_update_bits(wled->regmap,
wled->sink_addr + WLED5_SINK_FLASH_SINK_EN_REG,
WLED_SINK_CURR_SINK_MASK, sink_config);
if (rc < 0)
return rc;
}
/* MODULATOR_EN setting for valid sinks */
if (is_wled4(wled)) {
for (i = 0; (string_cfg >> i) != 0; i++) {
/* disable modulator_en for unused sink */
if (sink_config & (1 << (WLED_SINK_CURR_SINK_SHFT + i)))
reg = WLED_SINK_REG_STR_MOD_EN;
else
reg = 0x0;
rc = regmap_write(wled->regmap, wled->sink_addr +
WLED_SINK_MOD_EN_REG(i), reg);
if (rc < 0) {
pr_err("Failed to configure MODULATOR_EN rc=%d\n",
rc);
goto failed_calib;
}
}
}
/* Enable CABC if it needs to be enabled */
rc = wled->cabc_config(wled, true);
if (rc < 0)
goto failed_calib;
/* restore the feedback setting */
rc = regmap_write(wled->regmap,
wled->ctrl_addr + WLED_CTRL_FDBK_OP, 0);
if (rc < 0) {
pr_err("Failed to restore feedback setting rc=%d\n", rc);
goto failed_calib;
}
/* restore brightness */
rc = wled_set_brightness(wled, wled->brightness);
if (rc < 0) {
pr_err("Failed to set brightness after calibration rc=%d\n",
rc);
goto failed_calib;
}
rc = regmap_update_bits(wled->regmap,
wled->ctrl_addr + WLED_CTRL_MOD_ENABLE,
WLED_CTRL_MOD_EN_MASK,
WLED_CTRL_MOD_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(WLED_SOFT_START_DLY_US,
WLED_SOFT_START_DLY_US + 1000);
wled->auto_calib_done = true;
failed_calib:
return rc;
}
#define WLED_AUTO_CAL_OVP_COUNT 5
#define WLED_AUTO_CAL_CNT_DLY_US 1000000 /* 1 second */
static bool wled_auto_cal_required(struct 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++;
return false;
}
if (is_wled5(wled)) {
/*
* WLED5 has OVP fault density interrupt configuration i.e. to
* count the number of OVP alarms for a certain duration before
* triggering OVP fault interrupt. By default, number of OVP
* fault events counted before an interrupt is fired is 32 and
* the time interval is 12 ms. If we see more than one OVP fault
* interrupt, then that should qualify for a real OVP fault
* condition to run auto calibration algorithm.
*/
if (wled->auto_calibration_ovp_count > 1) {
elapsed_time_us = ktime_us_delta(ktime_get(),
wled->start_ovp_fault_time);
wled->auto_calibration_ovp_count = 0;
pr_debug("Elapsed time: %lld us\n", elapsed_time_us);
return true;
}
wled->auto_calibration_ovp_count++;
} else if (is_wled4(wled)) {
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 wled_auto_calibrate_at_init(struct wled *wled)
{
int rc;
bool fault_set;
if (!wled->cfg.auto_calib_enabled)
return 0;
rc = wled_get_ovp_fault_status(wled, &fault_set);
if (rc < 0) {
pr_err("Error in getting OVP fault_sts, rc=%d\n", rc);
return rc;
}
if (fault_set) {
mutex_lock(&wled->lock);
rc = wled_auto_calibrate(wled);
mutex_unlock(&wled->lock);
}
return rc;
}
static void handle_ovp_fault(struct wled *wled)
{
int rc;
if (!wled->cfg.auto_calib_enabled)
return;
mutex_lock(&wled->lock);
if (wled->auto_calib_done) {
pr_warn("Disabling module since OVP persists\n");
rc = regmap_update_bits(wled->regmap,
wled->ctrl_addr + WLED_CTRL_MOD_ENABLE,
WLED_CTRL_MOD_EN_MASK, 0);
if (!rc)
wled->force_mod_disable = true;
mutex_unlock(&wled->lock);
return;
}
if (wled->ovp_irq > 0 && !wled->ovp_irq_disabled) {
disable_irq_nosync(wled->ovp_irq);
wled->ovp_irq_disabled = true;
}
if (wled_auto_cal_required(wled))
wled_auto_calibrate(wled);
if (wled->ovp_irq > 0 && wled->ovp_irq_disabled) {
enable_irq(wled->ovp_irq);
wled->ovp_irq_disabled = false;
}
mutex_unlock(&wled->lock);
}
static irqreturn_t wled_ovp_irq_handler(int irq, void *_wled)
{
struct wled *wled = _wled;
int rc;
bool fault_set;
rc = wled_get_ovp_fault_status(wled, &fault_set);
if (rc < 0) {
pr_err("Error in getting OVP fault_sts, rc=%d\n", rc);
return rc;
}
if (fault_set)
handle_ovp_fault(wled);
return IRQ_HANDLED;
}
static irqreturn_t wled_flash_irq_handler(int irq, void *_wled)
{
struct wled *wled = _wled;
int rc;
u32 val;
if (irq == wled->flash_irq)
pr_debug("flash irq fired\n");
else if (irq == wled->pre_flash_irq)
pr_debug("pre_flash irq fired\n");
rc = regmap_read(wled->regmap,
wled->ctrl_addr + WLED_CTRL_FAULT_STATUS, &val);
if (!rc)
pr_debug("WLED_FAULT_STATUS: 0x%x\n", val);
rc = regmap_read(wled->regmap,
wled->ctrl_addr + WLED5_CTRL_STATUS, &val);
if (!rc)
pr_debug("WLED_STATUS: 0x%x\n", val);
return IRQ_HANDLED;
}
static inline u8 get_wled_safety_time(int time_ms)
{
int i, table[8] = {50, 100, 200, 400, 600, 800, 1000, 1200};
for (i = 0; i < ARRAY_SIZE(table); i++) {
if (time_ms == table[i])
return i;
}
return 0;
}
static int wled5_setup(struct wled *wled)
{
int rc, temp, i;
u8 sink_en = 0;
u16 addr;
u32 val;
u8 string_cfg = wled->cfg.string_cfg;
rc = wled_flash_setup(wled);
if (rc < 0)
dev_err(&wled->pdev->dev, "failed to setup WLED flash/torch rc:%d\n",
rc);
rc = regmap_update_bits(wled->regmap,
wled->ctrl_addr + WLED_CTRL_OVP,
WLED5_CTRL_OVP_MASK, wled->cfg.ovp);
if (rc < 0)
return rc;
rc = regmap_update_bits(wled->regmap,
wled->ctrl_addr + WLED_CTRL_ILIM,
WLED_CTRL_ILIM_MASK, wled->cfg.boost_i_limit);
if (rc < 0)
return rc;
if (wled->cfg.switch_freq != -EINVAL) {
rc = regmap_update_bits(wled->regmap,
wled->ctrl_addr + WLED_CTRL_SWITCH_FREQ,
WLED_CTRL_SWITCH_FREQ_MASK,
wled->cfg.switch_freq);
if (rc < 0)
return rc;
}
/* Per sink/string configuration */
for (i = 0; (string_cfg >> i) != 0; i++) {
if (string_cfg & BIT(i)) {
addr = wled->sink_addr +
WLED5_SINK_FS_CURR_REG(i);
rc = regmap_update_bits(wled->regmap, addr,
WLED_SINK_FS_MASK,
wled->cfg.fs_current);
if (rc < 0)
return rc;
addr = wled->sink_addr +
WLED5_SINK_SRC_SEL_REG(i);
rc = regmap_update_bits(wled->regmap, addr,
WLED5_SINK_SRC_SEL_MASK,
wled->cfg.mod_sel == MOD_A ?
WLED5_SINK_SRC_SEL_MODA :
WLED5_SINK_SRC_SEL_MODB);
temp = i + WLED_SINK_CURR_SINK_SHFT;
sink_en |= 1 << temp;
}
}
rc = wled5_cabc_config(wled, wled->cfg.cabc_sel ? true : false);
if (rc < 0)
return rc;
/* Enable one of the modulators A or B based on mod_sel */
addr = wled->sink_addr + WLED5_SINK_MOD_A_EN_REG;
val = (wled->cfg.mod_sel == MOD_A) ? WLED5_SINK_MOD_EN : 0;
rc = regmap_update_bits(wled->regmap, addr,
WLED5_SINK_MOD_EN, val);
if (rc < 0)
return rc;
addr = wled->sink_addr + WLED5_SINK_MOD_B_EN_REG;
val = (wled->cfg.mod_sel == MOD_B) ? WLED5_SINK_MOD_EN : 0;
rc = regmap_update_bits(wled->regmap, addr,
WLED5_SINK_MOD_EN, val);
if (rc < 0)
return rc;
addr = wled->sink_addr + wled5_brt_wid_sel_reg[wled->cfg.mod_sel];
val = (wled->max_brightness == WLED_MAX_BRIGHTNESS_15B)
? WLED5_SINK_BRT_WIDTH_15B : WLED5_SINK_BRT_WIDTH_12B;
rc = regmap_write(wled->regmap, addr, val);
if (rc < 0)
return rc;
rc = regmap_write(wled->regmap,
wled->sink_addr + WLED_SINK_CURR_SINK_EN, sink_en);
if (rc < 0)
return rc;
/* This updates only FSC configuration in WLED5 */
rc = wled_sync_toggle(wled);
if (rc < 0) {
pr_err("Failed to toggle sync reg rc:%d\n", rc);
return rc;
}
rc = wled_auto_calibrate_at_init(wled);
if (rc < 0)
return rc;
if (wled->ovp_irq >= 0) {
rc = devm_request_threaded_irq(&wled->pdev->dev, wled->ovp_irq,
NULL, wled_ovp_irq_handler, IRQF_ONESHOT,
"wled_ovp_irq", wled);
if (rc < 0) {
pr_err("Unable to request ovp(%d) IRQ(err:%d)\n",
wled->ovp_irq, rc);
return rc;
}
rc = regmap_read(wled->regmap, wled->ctrl_addr +
WLED_CTRL_MOD_ENABLE, &val);
/* disable the OVP irq only if the module is not enabled */
if (!rc && !(val & WLED_CTRL_MOD_EN_MASK)) {
disable_irq(wled->ovp_irq);
wled->ovp_irq_disabled = true;
}
}
if (wled->flash_irq >= 0) {
rc = devm_request_threaded_irq(&wled->pdev->dev,
wled->flash_irq, NULL, wled_flash_irq_handler,
IRQF_ONESHOT, "wled_flash_irq", wled);
if (rc < 0)
pr_err("Unable to request flash(%d) IRQ(err:%d)\n",
wled->flash_irq, rc);
}
if (wled->pre_flash_irq >= 0) {
rc = devm_request_threaded_irq(&wled->pdev->dev,
wled->pre_flash_irq, NULL,
wled_flash_irq_handler, IRQF_ONESHOT,
"wled_pre_flash_irq", wled);
if (rc < 0)
pr_err("Unable to request pre_flash(%d) IRQ(err:%d)\n",
wled->pre_flash_irq, rc);
}
return 0;
}
static int wled4_setup(struct wled *wled)
{
int rc, temp, i;
u8 sink_en = 0;
u32 val;
u8 string_cfg = wled->cfg.string_cfg;
rc = regmap_update_bits(wled->regmap,
wled->ctrl_addr + WLED_CTRL_OVP,
WLED_CTRL_OVP_MASK, wled->cfg.ovp);
if (rc < 0)
return rc;
rc = regmap_update_bits(wled->regmap,
wled->ctrl_addr + WLED_CTRL_ILIM,
WLED_CTRL_ILIM_MASK, wled->cfg.boost_i_limit);
if (rc < 0)
return rc;
rc = regmap_update_bits(wled->regmap,
wled->ctrl_addr + WLED_CTRL_SWITCH_FREQ,
WLED_CTRL_SWITCH_FREQ_MASK, wled->cfg.switch_freq);
if (rc < 0)
return rc;
/* Per sink/string configuration */
for (i = 0; (string_cfg >> i) != 0; i++) {
if (string_cfg & BIT(i)) {
u16 addr = wled->sink_addr +
WLED_SINK_MOD_EN_REG(i);
rc = regmap_update_bits(wled->regmap, addr,
WLED_SINK_REG_STR_MOD_MASK,
WLED_SINK_REG_STR_MOD_EN);
if (rc < 0)
return rc;
addr = wled->sink_addr +
WLED_SINK_FS_CURR_REG(i);
rc = regmap_update_bits(wled->regmap, addr,
WLED_SINK_FS_MASK,
wled->cfg.fs_current);
if (rc < 0)
return rc;
temp = i + WLED_SINK_CURR_SINK_SHFT;
sink_en |= 1 << temp;
}
}
rc = wled4_cabc_config(wled, wled->cfg.en_cabc);
if (rc < 0)
return rc;
rc = regmap_write(wled->regmap,
wled->sink_addr + WLED_SINK_CURR_SINK_EN, sink_en);
if (rc < 0)
return rc;
rc = wled_sync_toggle(wled);
if (rc < 0) {
pr_err("Failed to toggle sync reg rc:%d\n", rc);
return rc;
}
rc = wled_auto_calibrate_at_init(wled);
if (rc < 0)
return rc;
if (wled->sc_irq >= 0) {
rc = devm_request_threaded_irq(&wled->pdev->dev, wled->sc_irq,
NULL, wled_sc_irq_handler, IRQF_ONESHOT,
"wled_sc_irq", wled);
if (rc < 0) {
pr_err("Unable to request sc(%d) IRQ(err:%d)\n",
wled->sc_irq, rc);
return rc;
}
rc = regmap_update_bits(wled->regmap,
wled->ctrl_addr + WLED_CTRL_SHORT_PROTECT,
WLED_CTRL_SHORT_EN_MASK,
WLED_CTRL_SHORT_EN_MASK);
if (rc < 0)
return rc;
}
if (wled->cfg.ext_pfet_sc_pro_en) {
/* unlock the secure access register */
rc = regmap_write(wled->regmap, wled->ctrl_addr +
WLED_CTRL_SEC_ACCESS,
WLED_CTRL_SEC_UNLOCK);
if (rc < 0)
return rc;
rc = regmap_write(wled->regmap,
wled->ctrl_addr + WLED_CTRL_TEST1,
WLED_EXT_FET_DTEST2);
if (rc < 0)
return rc;
}
if (wled->ovp_irq >= 0) {
rc = devm_request_threaded_irq(&wled->pdev->dev, wled->ovp_irq,
NULL, wled_ovp_irq_handler, IRQF_ONESHOT,
"wled_ovp_irq", wled);
if (rc < 0) {
pr_err("Unable to request ovp(%d) IRQ(err:%d)\n",
wled->ovp_irq, rc);
return rc;
}
rc = regmap_read(wled->regmap, wled->ctrl_addr +
WLED_CTRL_MOD_ENABLE, &val);
/* disable the OVP irq only if the module is not enabled */
if (!rc && !(val & WLED_CTRL_MOD_EN_MASK)) {
disable_irq(wled->ovp_irq);
wled->ovp_irq_disabled = true;
}
}
return 0;
}
static const struct wled_config wled4_config_defaults = {
.boost_i_limit = 4,
.fs_current = 10,
.ovp = 1,
.switch_freq = 11,
.string_cfg = 0xf,
.mod_sel = -EINVAL,
.cabc_sel = -EINVAL,
.en_cabc = 0,
.ext_pfet_sc_pro_en = 0,
.auto_calib_enabled = 0,
};
static const struct wled_config wled5_config_defaults = {
.boost_i_limit = 5,
.fs_current = 10, /* 25 mA */
.ovp = 4,
.switch_freq = -EINVAL,
.string_cfg = 0xf,
.mod_sel = 0,
.cabc_sel = 0,
.en_cabc = 0,
.ext_pfet_sc_pro_en = 0,
.auto_calib_enabled = 0,
};
struct wled_var_cfg {
const u32 *values;
u32 (*fn)(u32 idx);
int size;
};
static const u32 wled4_boost_i_limit_values[] = {
105, 280, 450, 620, 970, 1150, 1300, 1500,
};
static const struct wled_var_cfg wled4_boost_i_limit_cfg = {
.values = wled4_boost_i_limit_values,
.size = ARRAY_SIZE(wled4_boost_i_limit_values),
};
static inline u32 wled5_boost_i_limit_values_fn(u32 idx)
{
return 525 + (idx * 175);
}
static const struct wled_var_cfg wled5_boost_i_limit_cfg = {
.fn = wled5_boost_i_limit_values_fn,
.size = 8,
};
static const u32 wled_fs_current_values[] = {
0, 2500, 5000, 7500, 10000, 12500, 15000, 17500, 20000,
22500, 25000, 27500, 30000,
};
static const struct wled_var_cfg wled_fs_current_cfg = {
.values = wled_fs_current_values,
.size = ARRAY_SIZE(wled_fs_current_values),
};
static const u32 wled4_ovp_values[] = {
31100, 29600, 19600, 18100,
};
static const struct wled_var_cfg wled4_ovp_cfg = {
.values = wled4_ovp_values,
.size = ARRAY_SIZE(wled4_ovp_values),
};
static inline u32 wled5_ovp_values_fn(u32 idx)
{
/*
* 0000 - 38.5 V
* 0001 - 37 V ..
* 1111 - 16 V
*/
return 38500 - (idx * 1500);
}
static const struct wled_var_cfg wled5_ovp_cfg = {
.fn = wled5_ovp_values_fn,
.size = 16,
};
static inline u32 wled_switch_freq_values_fn(u32 idx)
{
return 9600 / (1 + idx);
}
static const struct wled_var_cfg wled_switch_freq_cfg = {
.fn = wled_switch_freq_values_fn,
.size = 16,
};
static const struct wled_var_cfg wled_string_cfg = {
.size = 16,
};
static const struct wled_var_cfg wled5_mod_sel_cfg = {
.size = 2,
};
static const struct wled_var_cfg wled5_cabc_sel_cfg = {
.size = 4,
};
static u32 wled_values(const struct wled_var_cfg *cfg, u32 idx)
{
if (!cfg)
return UINT_MAX;
if (idx >= cfg->size)
return UINT_MAX;
if (cfg->fn)
return cfg->fn(idx);
if (cfg->values)
return cfg->values[idx];
return idx;
}
static int wled_get_max_current(struct led_classdev *led_cdev,
int *max_current)
{
struct wled *wled;
bool flash;
if (!strcmp(led_cdev->name, "wled_flash")) {
wled = container_of(led_cdev, struct wled, flash_cdev);
flash = true;
} else if (!strcmp(led_cdev->name, "wled_torch")) {
wled = container_of(led_cdev, struct wled, torch_cdev);
flash = false;
} else {
return -ENODEV;
}
if (flash)
*max_current = wled->flash_cdev.max_brightness;
else
*max_current = wled->torch_cdev.max_brightness;
return 0;
}
static int get_property_from_fg(struct wled *wled,
enum power_supply_property prop, int *val)
{
int rc;
union power_supply_propval pval = {0, };
if (!wled->bms_psy)
wled->bms_psy = power_supply_get_by_name("bms");
if (!wled->bms_psy)
return -ENODEV;
rc = power_supply_get_property(wled->bms_psy, prop, &pval);
if (rc < 0) {
pr_err("bms psy doesn't support reading prop %d rc = %d\n",
prop, rc);
return rc;
}
*val = pval.intval;
return rc;
}
#define V_HDRM_MV 400
#define V_DROOP_MV 400
#define V_LED_MV 3100
#define I_FLASH_MAX_MA 60
#define EFF_FACTOR 700
static int wled_get_max_avail_current(struct led_classdev *led_cdev,
int *max_current)
{
struct wled *wled;
int rc, ocv_mv, r_bat_mohms, i_bat_ma, i_sink_ma = 0, max_fsc_ma;
int64_t p_out_string, p_out, p_in, v_safe_mv, i_flash_ma, v_ph_mv;
if (!strcmp(led_cdev->name, "wled_switch"))
wled = container_of(led_cdev, struct wled, switch_cdev);
else
return -ENODEV;
max_fsc_ma = max(wled->flash_cdev.max_brightness,
wled->torch_cdev.max_brightness);
if (!wled->leds_per_string || (wled->num_strings == 2 &&
wled->leds_per_string == 8)) {
/* Allow max for 8s2p */
*max_current = max_fsc_ma;
return 0;
}
rc = get_property_from_fg(wled, POWER_SUPPLY_PROP_VOLTAGE_OCV, &ocv_mv);
if (rc < 0) {
pr_err("Error in getting OCV rc=%d\n", rc);
return rc;
}
ocv_mv /= 1000;
rc = get_property_from_fg(wled, POWER_SUPPLY_PROP_CURRENT_NOW,
&i_bat_ma);
if (rc < 0) {
pr_err("Error in getting I_BAT rc=%d\n", rc);
return rc;
}
i_bat_ma /= 1000;
rc = get_property_from_fg(wled, POWER_SUPPLY_PROP_RESISTANCE,
&r_bat_mohms);
if (rc < 0) {
pr_err("Error in getting R_BAT rc=%d\n", rc);
return rc;
}
r_bat_mohms /= 1000;
pr_debug("ocv: %d i_bat: %d r_bat: %d\n", ocv_mv, i_bat_ma,
r_bat_mohms);
p_out_string = ((wled->leds_per_string * V_LED_MV) + V_HDRM_MV) *
I_FLASH_MAX_MA;
p_out = p_out_string * wled->num_strings;
p_in = (p_out * 1000) / EFF_FACTOR;
pr_debug("p_out_string: %lld, p_out: %lld, p_in: %lld\n", p_out_string,
p_out, p_in);
v_safe_mv = ocv_mv - V_DROOP_MV - ((i_bat_ma * r_bat_mohms) / 1000);
if (v_safe_mv <= 0) {
pr_err("V_safe_mv: %lld, cannot support flash\n", v_safe_mv);
*max_current = 0;
return 0;
}
i_flash_ma = p_in / v_safe_mv;
v_ph_mv = ocv_mv - ((i_bat_ma + i_flash_ma) * r_bat_mohms) / 1000;
pr_debug("v_safe: %lld, i_flash: %lld, v_ph: %lld\n", v_safe_mv,
i_flash_ma, v_ph_mv);
i_sink_ma = max_fsc_ma;
if (wled->num_strings == 3 && wled->leds_per_string == 8) {
if (v_ph_mv < 3410) {
/* For 8s3p, I_sink(mA) = 25.396 * Vph(V) - 26.154 */
i_sink_ma = (((25396 * v_ph_mv) / 1000) - 26154) / 1000;
i_sink_ma *= wled->num_strings;
}
} else if (wled->num_strings == 3 && wled->leds_per_string == 6) {
if (v_ph_mv < 2800) {
/* For 6s3p, I_sink(mA) = 41.311 * Vph(V) - 52.334 */
i_sink_ma = (((41311 * v_ph_mv) / 1000) - 52334) / 1000;
i_sink_ma *= wled->num_strings;
}
} else if (wled->num_strings == 4 && wled->leds_per_string == 6) {
if (v_ph_mv < 3400) {
/* For 6s4p, I_sink(mA) = 26.24 * Vph(V) - 24.834 */
i_sink_ma = (((26240 * v_ph_mv) / 1000) - 24834) / 1000;
i_sink_ma *= wled->num_strings;
}
} else if (v_ph_mv < 3200) {
i_sink_ma = max_fsc_ma / 2;
}
/* Clamp the sink current to maximum FSC */
*max_current = min(i_sink_ma, max_fsc_ma);
pr_debug("i_sink_ma: %d\n", i_sink_ma);
return 0;
}
static ssize_t wled_flash_max_avail_current_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
int rc, max_current = 0;
rc = wled_get_max_avail_current(led_cdev, &max_current);
if (rc < 0)
pr_err("query max current failed, rc=%d\n", rc);
return snprintf(buf, PAGE_SIZE, "%d\n", max_current);
}
static struct device_attribute wled_flash_attrs[] = {
__ATTR(max_avail_current, 0664, wled_flash_max_avail_current_show,
NULL),
};
int wled_flash_led_prepare(struct led_trigger *trig, int options,
int *max_current)
{
struct led_classdev *led_cdev;
int rc;
if (!(options & FLASH_LED_PREPARE_OPTIONS_MASK)) {
pr_err("Invalid options %d\n", options);
return -EINVAL;
}
if (!trig) {
pr_err("Invalid led_trigger provided\n");
return -EINVAL;
}
led_cdev = trigger_to_lcdev(trig);
if (!led_cdev) {
pr_err("Invalid led_cdev in trigger %s\n", trig->name);
return -EINVAL;
}
switch (options) {
case QUERY_MAX_CURRENT:
rc = wled_get_max_current(led_cdev, max_current);
if (rc < 0) {
pr_err("Error in getting max_current for %s\n",
led_cdev->name);
return rc;
}
break;
case QUERY_MAX_AVAIL_CURRENT:
rc = wled_get_max_avail_current(led_cdev, max_current);
if (rc < 0) {
pr_err("Error in getting max_avail_current for %s\n",
led_cdev->name);
return rc;
}
break;
case ENABLE_REGULATOR:
case DISABLE_REGULATOR:
/* Not supported */
return 0;
default:
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL(wled_flash_led_prepare);
static int wled_flash_set_step_delay(struct wled *wled, int step_delay)
{
int rc, table[8] = {50, 100, 150, 200, 250, 300, 350, 400};
u8 val;
if (step_delay < table[0])
val = 0;
else if (step_delay > table[7])
val = 7;
else
val = DIV_ROUND_CLOSEST(step_delay, 50) - 1;
rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
WLED5_CTRL_FLASH_STEP_CTL_REG,
WLED5_CTRL_FLASH_STEP_MASK, val);
if (rc < 0)
pr_err("Error in configuring step delay, rc:%d\n", rc);
return rc;
}
static int wled_flash_set_fsc(struct wled *wled, enum led_brightness brightness,
int fs_current_max)
{
int rc, fs_current;
u8 val;
if (!wled->num_strings) {
pr_err("Incorrect number of strings\n");
return -EINVAL;
}
fs_current = (int)brightness / wled->num_strings;
if (fs_current > fs_current_max)
fs_current = fs_current_max;
/* Each LSB is 5 mA */
val = DIV_ROUND_CLOSEST(fs_current, 5);
rc = regmap_update_bits(wled->regmap,
wled->sink_addr + WLED5_SINK_FLASH_FSC_REG,
WLED5_SINK_FLASH_FSC_MASK, val);
if (rc < 0) {
pr_err("Error in configuring flash_fsc, rc:%d\n", rc);
return rc;
}
/* Write 0 followed by 1 to sync FSC */
rc = regmap_write(wled->regmap,
wled->sink_addr + WLED5_SINK_FLASH_SYNC_BIT_REG, 0);
if (rc < 0) {
pr_err("Error in configuring flash_sync, rc:%d\n", rc);
return rc;
}
rc = regmap_write(wled->regmap,
wled->sink_addr + WLED5_SINK_FLASH_SYNC_BIT_REG,
WLED5_SINK_FLASH_FSC_SYNC_EN);
if (rc < 0)
pr_err("Error in configuring flash_sync, rc:%d\n", rc);
return rc;
}
static void wled_flash_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct wled *wled = container_of(cdev, struct wled, flash_cdev);
int rc;
spin_lock(&wled->flash_lock);
if (brightness) {
rc = wled_flash_set_step_delay(wled, wled->fparams.step_delay);
if (rc < 0)
goto out;
}
rc = wled_flash_set_fsc(wled, brightness, wled->fparams.fs_current);
if (rc < 0)
goto out;
wled->flash_mode = brightness ? WLED_FLASH : WLED_FLASH_OFF;
out:
spin_unlock(&wled->flash_lock);
}
static void wled_torch_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct wled *wled = container_of(cdev, struct wled, torch_cdev);
int rc;
spin_lock(&wled->flash_lock);
if (brightness) {
rc = wled_flash_set_step_delay(wled, wled->tparams.step_delay);
if (rc < 0)
goto out;
}
rc = wled_flash_set_fsc(wled, brightness, wled->tparams.fs_current);
if (rc < 0)
goto out;
wled->flash_mode = brightness ? WLED_PRE_FLASH : WLED_FLASH_OFF;
out:
spin_unlock(&wled->flash_lock);
}
static void wled_switch_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct wled *wled = container_of(cdev, struct wled, switch_cdev);
int rc;
u8 val;
if (brightness && wled->flash_mode == WLED_FLASH_OFF)
return;
spin_lock(&wled->flash_lock);
if (wled->flash_mode == WLED_PRE_FLASH)
val = brightness ? WLED5_SINK_PRE_FLASH_EN : 0;
else
val = brightness ? WLED5_SINK_FLASH_EN : 0;
rc = regmap_write(wled->regmap,
wled->sink_addr + WLED5_SINK_FLASH_CTL_REG, val);
if (rc < 0)
pr_err("Error in configuring flash_ctl, rc:%d\n", rc);
if (!brightness) {
rc = regmap_write(wled->regmap,
wled->sink_addr + WLED5_SINK_FLASH_SHDN_CLR_REG,
1);
if (rc < 0)
pr_err("Error in configuring flash_shdn_clr, rc:%d\n",
rc);
}
spin_unlock(&wled->flash_lock);
}
static int wled_flash_device_register(struct wled *wled)
{
int rc, i, max_brightness = 0;
/* Not supported */
if (is_wled4(wled))
return 0;
spin_lock_init(&wled->flash_lock);
/* flash */
for (i = 0; (wled->cfg.string_cfg >> i) != 0; i++)
max_brightness += wled->fparams.fs_current;
wled->flash_cdev.name = "wled_flash";
wled->flash_cdev.max_brightness = max_brightness;
wled->flash_cdev.brightness_set = wled_flash_brightness_set;
rc = devm_led_classdev_register(&wled->pdev->dev, &wled->flash_cdev);
if (rc < 0)
return rc;
/* torch */
for (max_brightness = 0, i = 0; (wled->cfg.string_cfg >> i) != 0; i++)
max_brightness += wled->tparams.fs_current;
wled->torch_cdev.name = "wled_torch";
wled->torch_cdev.max_brightness = max_brightness;
wled->torch_cdev.brightness_set = wled_torch_brightness_set;
rc = devm_led_classdev_register(&wled->pdev->dev, &wled->torch_cdev);
if (rc < 0)
return rc;
/* switch */
wled->switch_cdev.name = "wled_switch";
wled->switch_cdev.brightness_set = wled_switch_brightness_set;
wled->switch_cdev.flags |= LED_KEEP_TRIGGER;
rc = devm_led_classdev_register(&wled->pdev->dev, &wled->switch_cdev);
if (rc < 0)
return rc;
for (i = 0; i < ARRAY_SIZE(wled_flash_attrs); i++) {
rc = sysfs_create_file(&wled->switch_cdev.dev->kobj,
&wled_flash_attrs[i].attr);
if (rc < 0) {
pr_err("sysfs creation failed, rc=%d\n", rc);
goto sysfs_fail;
}
}
return 0;
sysfs_fail:
for (--i; i >= 0; i--)
sysfs_remove_file(&wled->switch_cdev.dev->kobj,
&wled_flash_attrs[i].attr);
return rc;
}
static int wled_flash_configure(struct wled *wled)
{
int rc;
struct device_node *temp;
struct device *dev = &wled->pdev->dev;
const char *cdev_name;
/* Not supported */
if (is_wled4(wled))
return 0;
of_property_read_u32(wled->pdev->dev.of_node, "qcom,leds-per-string",
&wled->leds_per_string);
for_each_available_child_of_node(wled->pdev->dev.of_node, temp) {
rc = of_property_read_string(temp, "label", &cdev_name);
if (rc < 0)
continue;
if (!strcmp(cdev_name, "flash")) {
/* Value read in mA */
wled->fparams.fs_current = 50;
rc = of_property_read_u32(temp, "qcom,wled-flash-fsc",
&wled->fparams.fs_current);
if (!rc) {
if (wled->fparams.fs_current <= 0 ||
wled->fparams.fs_current > 60) {
dev_err(dev, "Incorrect WLED flash FSC rc:%d\n",
rc);
return rc;
}
}
/*
* As per the hardware recommendation, FS current should
* be limited to 20 mA for PM8150L v1.0.
*/
if (wled->pmic_rev_id->rev4 == PM8150L_V1P0_REV4)
wled->fparams.fs_current = 20;
/* Value read in us */
wled->fparams.step_delay = 200;
rc = of_property_read_u32(temp, "qcom,wled-flash-step",
&wled->fparams.step_delay);
if (!rc) {
if (wled->fparams.step_delay < 50 ||
wled->fparams.step_delay > 400) {
dev_err(dev, "Incorrect WLED flash step delay rc:%d\n",
rc);
return rc;
}
}
/* Value read in ms */
wled->fparams.safety_timer = 100;
rc = of_property_read_u32(temp, "qcom,wled-flash-timer",
&wled->fparams.safety_timer);
if (!rc) {
if (wled->fparams.safety_timer < 50 ||
wled->fparams.safety_timer > 1200) {
dev_err(dev, "Incorrect WLED flash safety time rc:%d\n",
rc);
return rc;
}
}
rc = of_property_read_string(temp,
"qcom,default-led-trigger",
&wled->flash_cdev.default_trigger);
if (rc < 0)
wled->flash_cdev.default_trigger = "wled_flash";
} else if (!strcmp(cdev_name, "torch")) {
/* Value read in mA */
wled->tparams.fs_current = 50;
rc = of_property_read_u32(temp, "qcom,wled-torch-fsc",
&wled->tparams.fs_current);
if (!rc) {
if (wled->tparams.fs_current <= 0 ||
wled->tparams.fs_current > 60) {
dev_err(dev, "Incorrect WLED torch FSC rc:%d\n",
rc);
return rc;
}
}
/*
* As per the hardware recommendation, FS current should
* be limited to 20 mA for PM8150L v1.0.
*/
if (wled->pmic_rev_id->rev4 == PM8150L_V1P0_REV4)
wled->tparams.fs_current = 20;
/* Value read in us */
wled->tparams.step_delay = 200;
rc = of_property_read_u32(temp, "qcom,wled-torch-step",
&wled->tparams.step_delay);
if (!rc) {
if (wled->tparams.step_delay < 50 ||
wled->tparams.step_delay > 400) {
dev_err(dev, "Incorrect WLED torch step delay rc:%d\n",
rc);
return rc;
}
}
/* Value read in ms */
wled->tparams.safety_timer = 600;
rc = of_property_read_u32(temp, "qcom,wled-torch-timer",
&wled->tparams.safety_timer);
if (!rc) {
if (wled->tparams.safety_timer < 50 ||
wled->tparams.safety_timer > 1200) {
dev_err(dev, "Incorrect WLED torch safety time rc:%d\n",
rc);
return rc;
}
}
rc = of_property_read_string(temp,
"qcom,default-led-trigger",
&wled->torch_cdev.default_trigger);
if (rc < 0)
wled->torch_cdev.default_trigger = "wled_torch";
} else if (!strcmp(cdev_name, "switch")) {
rc = of_property_read_string(temp,
"qcom,default-led-trigger",
&wled->switch_cdev.default_trigger);
if (rc < 0)
wled->switch_cdev.default_trigger =
"wled_switch";
} else {
return -EINVAL;
}
}
return 0;
}
static int wled_flash_setup(struct wled *wled)
{
int rc, i;
u8 val;
/* Set FLASH_VREF_ADIM_HDIM to maximum */
rc = regmap_write(wled->regmap,
wled->ctrl_addr + WLED5_CTRL_FLASH_HDRM_REG, 0xF);
if (rc < 0)
return rc;
/* Write a full brightness value */
rc = regmap_write(wled->regmap,
wled->ctrl_addr + WLED5_CTRL_PRE_FLASH_BRT_REG, 0xFF);
if (rc < 0)
return rc;
/* Sync the brightness by writing a 0 followed by 1 */
rc = regmap_write(wled->regmap,
wled->ctrl_addr + WLED5_CTRL_PRE_FLASH_SYNC_REG, 0);
if (rc < 0)
return rc;
rc = regmap_write(wled->regmap,
wled->ctrl_addr + WLED5_CTRL_PRE_FLASH_SYNC_REG, 1);
if (rc < 0)
return rc;
/* Write a full brightness value */
rc = regmap_write(wled->regmap,
wled->ctrl_addr + WLED5_CTRL_FLASH_BRT_REG, 0xFF);
if (rc < 0)
return rc;
/* Sync the brightness by writing a 0 followed by 1 */
rc = regmap_write(wled->regmap,
wled->ctrl_addr + WLED5_CTRL_FLASH_SYNC_REG, 0);
if (rc < 0)
return rc;
rc = regmap_write(wled->regmap,
wled->ctrl_addr + WLED5_CTRL_FLASH_SYNC_REG, 1);
if (rc < 0)
return rc;
for (val = 0, i = 0; (wled->cfg.string_cfg >> i) != 0; i++) {
if (wled->cfg.string_cfg & BIT(i)) {
val |= 1 << (i + WLED_SINK_CURR_SINK_SHFT);
wled->num_strings++;
}
}
/* Enable current sinks for flash */
rc = regmap_update_bits(wled->regmap,
wled->sink_addr + WLED5_SINK_FLASH_SINK_EN_REG,
WLED_SINK_CURR_SINK_MASK, val);
if (rc < 0)
return rc;
/* Enable flash and pre_flash safety timers */
val = get_wled_safety_time(wled->tparams.safety_timer) <<
WLED5_PRE_FLASH_SAFETY_SHIFT;
val |= get_wled_safety_time(wled->fparams.safety_timer);
rc = regmap_write(wled->regmap,
wled->sink_addr + WLED5_SINK_FLASH_TIMER_CTL_REG, val);
if (rc < 0)
return rc;
return 0;
}
static int wled_configure(struct wled *wled, struct device *dev)
{
struct wled_config *cfg = &wled->cfg;
struct device_node *revid_node;
const __be32 *prop_addr;
u32 val, c;
int rc, i, j, size;
struct wled_u32_opts {
const char *name;
u32 *val_ptr;
const struct wled_var_cfg *cfg;
};
const struct wled_u32_opts *u32_opts;
const struct wled_u32_opts wled4_opts[] = {
{
.name = "qcom,boost-current-limit",
.val_ptr = &cfg->boost_i_limit,
.cfg = &wled4_boost_i_limit_cfg,
},
{
.name = "qcom,fs-current-limit",
.val_ptr = &cfg->fs_current,
.cfg = &wled_fs_current_cfg,
},
{
.name = "qcom,ovp",
.val_ptr = &cfg->ovp,
.cfg = &wled4_ovp_cfg,
},
{
.name = "qcom,switching-freq",
.val_ptr = &cfg->switch_freq,
.cfg = &wled_switch_freq_cfg,
},
{
.name = "qcom,string-cfg",
.val_ptr = &cfg->string_cfg,
.cfg = &wled_string_cfg,
},
};
const struct wled_u32_opts wled5_opts[] = {
{
.name = "qcom,boost-current-limit",
.val_ptr = &cfg->boost_i_limit,
.cfg = &wled5_boost_i_limit_cfg,
},
{
.name = "qcom,fs-current-limit",
.val_ptr = &cfg->fs_current,
.cfg = &wled_fs_current_cfg,
},
{
.name = "qcom,ovp",
.val_ptr = &cfg->ovp,
.cfg = &wled5_ovp_cfg,
},
{
.name = "qcom,switching-freq",
.val_ptr = &cfg->switch_freq,
.cfg = &wled_switch_freq_cfg,
},
{
.name = "qcom,string-cfg",
.val_ptr = &cfg->string_cfg,
.cfg = &wled_string_cfg,
},
{
.name = "qcom,modulator-sel",
.val_ptr = &cfg->mod_sel,
.cfg = &wled5_mod_sel_cfg,
},
{
.name = "qcom,cabc-sel",
.val_ptr = &cfg->cabc_sel,
.cfg = &wled5_cabc_sel_cfg,
},
};
const struct {
const char *name;
bool *val_ptr;
} bool_opts[] = {
{ "qcom,en-cabc", &cfg->en_cabc, },
{ "qcom,ext-pfet-sc-pro", &cfg->ext_pfet_sc_pro_en, },
{ "qcom,auto-calibration", &cfg->auto_calib_enabled, },
};
prop_addr = of_get_address(dev->of_node, 0, NULL, NULL);
if (!prop_addr) {
pr_err("invalid IO resources\n");
return -EINVAL;
}
wled->ctrl_addr = be32_to_cpu(*prop_addr);
prop_addr = of_get_address(dev->of_node, 1, NULL, NULL);
if (!prop_addr) {
pr_err("invalid IO resources\n");
return -EINVAL;
}
wled->sink_addr = be32_to_cpu(*prop_addr);
rc = of_property_read_string(dev->of_node, "label", &wled->name);
if (rc < 0)
wled->name = dev->of_node->name;
if (of_find_property(dev->of_node, "qcom,pmic-revid", NULL)) {
revid_node = of_parse_phandle(dev->of_node, "qcom,pmic-revid",
0);
if (!revid_node) {
pr_err("Error in getting revid_node\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));
wled->pmic_rev_id = NULL;
return -EPROBE_DEFER;
}
}
if (is_wled5(wled)) {
u32_opts = wled5_opts;
size = ARRAY_SIZE(wled5_opts);
*cfg = wled5_config_defaults;
wled->cabc_config = wled5_cabc_config;
} else if (is_wled4(wled)) {
u32_opts = wled4_opts;
size = ARRAY_SIZE(wled4_opts);
*cfg = wled4_config_defaults;
wled->cabc_config = wled4_cabc_config;
} else {
pr_err("Unknown WLED version %d\n", *wled->version);
return -EINVAL;
}
for (i = 0; i < size; ++i) {
rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val);
if (rc == -EINVAL) {
continue;
} else if (rc < 0) {
pr_err("error reading '%s'\n", u32_opts[i].name);
return rc;
}
c = UINT_MAX;
for (j = 0; c != val; j++) {
c = wled_values(u32_opts[i].cfg, j);
if (c == UINT_MAX) {
pr_err("invalid value for '%s'\n",
u32_opts[i].name);
return -EINVAL;
}
if (c == val)
break;
}
pr_debug("'%s' = %u\n", u32_opts[i].name, c);
*u32_opts[i].val_ptr = j;
}
for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) {
if (of_property_read_bool(dev->of_node, bool_opts[i].name))
*bool_opts[i].val_ptr = true;
}
wled->sc_irq = platform_get_irq_byname(wled->pdev, "sc-irq");
if (wled->sc_irq < 0)
dev_dbg(&wled->pdev->dev, "sc irq is not used\n");
wled->ovp_irq = platform_get_irq_byname(wled->pdev, "ovp-irq");
if (wled->ovp_irq < 0)
dev_dbg(&wled->pdev->dev, "ovp irq is not used\n");
wled->flash_irq = platform_get_irq_byname(wled->pdev, "flash-irq");
if (wled->flash_irq < 0)
dev_dbg(&wled->pdev->dev, "flash irq is not used\n");
wled->pre_flash_irq = platform_get_irq_byname(wled->pdev,
"pre-flash-irq");
if (wled->pre_flash_irq < 0)
dev_dbg(&wled->pdev->dev, "pre_flash irq is not used\n");
return 0;
}
static const struct backlight_ops wled_ops = {
.update_status = wled_update_status,
.get_brightness = wled_get_brightness,
};
static int wled_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct backlight_device *bl;
struct wled *wled;
struct regmap *regmap;
u32 val;
int rc;
regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!regmap) {
pr_err("Unable to get regmap\n");
return -EINVAL;
}
wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
if (!wled)
return -ENOMEM;
wled->regmap = regmap;
wled->pdev = pdev;
wled->version = of_device_get_match_data(&pdev->dev);
if (!wled->version) {
dev_err(&pdev->dev, "Unknown device version\n");
return -ENODEV;
}
rc = wled_configure(wled, &pdev->dev);
if (rc < 0) {
dev_err(&pdev->dev, "wled configure failed rc:%d\n", rc);
return rc;
}
rc = wled_flash_configure(wled);
if (rc < 0) {
dev_err(&pdev->dev, "wled configure failed rc:%d\n", rc);
return rc;
}
mutex_init(&wled->lock);
val = WLED_DEFAULT_BRIGHTNESS;
of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
wled->brightness = val;
val = WLED_MAX_BRIGHTNESS_12B;
of_property_read_u32(pdev->dev.of_node, "max-brightness", &val);
wled->max_brightness = val;
/* For WLED5, when CABC is enabled, max brightness is 4095. */
if (is_wled5(wled) && wled->cfg.cabc_sel)
wled->max_brightness = WLED_MAX_BRIGHTNESS_12B;
/*
* As per the hardware recommendation, FS current should
* be limited to 20 mA for PM8150L v1.0.
*/
if (wled->pmic_rev_id->rev4 == PM8150L_V1P0_REV4 &&
wled->cfg.fs_current > 8)
wled->cfg.fs_current = 8;
if (is_wled4(wled))
rc = wled4_setup(wled);
else
rc = wled5_setup(wled);
if (rc < 0) {
dev_err(&pdev->dev, "wled setup failed rc:%d\n", rc);
return rc;
}
platform_set_drvdata(pdev, wled);
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.brightness = val;
props.max_brightness = wled->max_brightness;
bl = devm_backlight_device_register(&pdev->dev, wled->name,
&pdev->dev, wled,
&wled_ops, &props);
if (IS_ERR_OR_NULL(bl)) {
rc = PTR_ERR_OR_ZERO(bl);
if (!rc)
rc = -ENODEV;
dev_err(&pdev->dev, "failed to register backlight rc:%d\n", rc);
return rc;
}
rc = wled_flash_device_register(wled);
if (rc < 0) {
dev_err(&pdev->dev, "failed to register WLED flash/torch rc:%d\n",
rc);
return rc;
}
return rc;
}
static const struct of_device_id wled_match_table[] = {
{ .compatible = "qcom,pmi8998-spmi-wled", .data = &version_table[0] },
{ .compatible = "qcom,pm8150l-spmi-wled", .data = &version_table[2] },
{ .compatible = "qcom,pm6150l-spmi-wled", .data = &version_table[2] },
{ .compatible = "qcom,pm660l-spmi-wled", .data = &version_table[1] },
{ },
};
static struct platform_driver wled_driver = {
.probe = wled_probe,
.driver = {
.name = "qcom-spmi-wled",
.of_match_table = wled_match_table,
},
};
module_platform_driver(wled_driver);
MODULE_DESCRIPTION("Qualcomm Technologies, Inc. SPMI PMIC WLED driver");
MODULE_LICENSE("GPL v2");
MODULE_SOFTDEP("pre: devfreq_qcom_fw");