| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2015, Sony Mobile Communications, AB. |
| */ |
| /* |
| * Copyright (c) 2018-2019, 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) |
| |
| /* 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; |
| |
| if (wled->force_mod_disable) |
| return 0; |
| |
| 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; |
| /* |
| * 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] }, |
| { }, |
| }; |
| |
| 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"); |