Loading Documentation/devicetree/bindings/leds/leds-qpnp-wled.txt +2 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,8 @@ Optional properties for WLED: - qcom,lcd-psm-ctrl : A boolean property to specify if PSM needs to be controlled dynamically when WLED module is enabled or disabled. - qcom,auto-calibration-enable : A boolean property which enables auto-calibration of the WLED sink configuration. Optional properties if 'qcom,disp-type-amoled' is mentioned in DT: - qcom,loop-comp-res-kohm : control to select the compensation resistor in kohm. default is 320. Loading arch/arm64/boot/dts/qcom/pm660l.dtsi +1 −0 Original line number Diff line number Diff line Loading @@ -269,6 +269,7 @@ qcom,led-strings-list = [00 01 02]; qcom,loop-auto-gm-en; qcom,pmic-revid = <&pm660l_revid>; qcom,auto-calibration-enable; status = "ok"; }; Loading arch/arm64/boot/dts/qcom/pmi8998.dtsi +1 −0 Original line number Diff line number Diff line Loading @@ -563,6 +563,7 @@ qcom,en-ext-pfet-sc-pro; qcom,pmic-revid = <&pmi8998_revid>; qcom,loop-auto-gm-en; qcom,auto-calibration-enable; status = "disabled"; }; Loading drivers/leds/leds-qpnp-wled.c +323 −86 Original line number Diff line number Diff line Loading @@ -160,18 +160,19 @@ #define QPNP_WLED_MOD_EN_SHFT 7 #define QPNP_WLED_MOD_EN 1 #define QPNP_WLED_GATE_DRV_MASK 0xFE #define QPNP_WLED_SYNC_DLY_MASK 0xF8 #define QPNP_WLED_SYNC_DLY_MASK GENMASK(2, 0) #define QPNP_WLED_SYNC_DLY_MIN_US 0 #define QPNP_WLED_SYNC_DLY_MAX_US 1400 #define QPNP_WLED_SYNC_DLY_STEP_US 200 #define QPNP_WLED_DEF_SYNC_DLY_US 400 #define QPNP_WLED_FS_CURR_MASK 0xF0 #define QPNP_WLED_FS_CURR_MASK GENMASK(3, 0) #define QPNP_WLED_FS_CURR_MIN_UA 0 #define QPNP_WLED_FS_CURR_MAX_UA 30000 #define QPNP_WLED_FS_CURR_STEP_UA 2500 #define QPNP_WLED_CABC_MASK 0x7F #define QPNP_WLED_CABC_MASK 0x80 #define QPNP_WLED_CABC_SHIFT 7 #define QPNP_WLED_CURR_SINK_SHIFT 4 #define QPNP_WLED_CURR_SINK_MASK GENMASK(7, 4) #define QPNP_WLED_BRIGHT_LSB_MASK 0xFF #define QPNP_WLED_BRIGHT_MSB_SHIFT 8 #define QPNP_WLED_BRIGHT_MSB_MASK 0x0F Loading Loading @@ -208,12 +209,14 @@ #define QPNP_WLED_SEC_UNLOCK 0xA5 #define QPNP_WLED_MAX_STRINGS 4 #define QPNP_PM660_WLED_MAX_STRINGS 3 #define WLED_MAX_LEVEL_4095 4095 #define QPNP_WLED_RAMP_DLY_MS 20 #define QPNP_WLED_TRIGGER_NONE "none" #define QPNP_WLED_STR_SIZE 20 #define QPNP_WLED_MIN_MSLEEP 20 #define QPNP_WLED_SC_DLY_MS 20 #define QPNP_WLED_SOFT_START_DLY_US 10000 #define NUM_SUPPORTED_AVDD_VOLTAGES 6 #define QPNP_WLED_DFLT_AVDD_MV 7600 Loading Loading @@ -381,6 +384,8 @@ struct qpnp_wled { u16 ramp_ms; u16 ramp_step; u16 cons_sync_write_delay_us; u16 auto_calibration_ovp_count; u16 max_strings; u8 strings[QPNP_WLED_MAX_STRINGS]; u8 num_strings; u8 loop_auto_gm_thresh; Loading @@ -396,6 +401,9 @@ struct qpnp_wled { bool en_ext_pfet_sc_pro; bool prev_state; bool ovp_irq_disabled; bool auto_calib_enabled; bool auto_calib_done; ktime_t start_ovp_fault_time; }; /* helper to read a pmic register */ Loading Loading @@ -531,7 +539,7 @@ static int qpnp_wled_set_level(struct qpnp_wled *wled, int level) u8 reg; /* set brightness registers */ for (i = 0; i < wled->num_strings; i++) { for (i = 0; i < wled->max_strings; i++) { reg = level & QPNP_WLED_BRIGHT_LSB_MASK; rc = qpnp_wled_write_reg(wled, QPNP_WLED_BRIGHT_LSB_REG(wled->sink_base, Loading Loading @@ -600,7 +608,8 @@ static int qpnp_wled_module_en(struct qpnp_wled *wled, * OVP interrupt disabled when the module is disabled. */ if (state) { usleep_range(10000, 11000); usleep_range(QPNP_WLED_SOFT_START_DLY_US, QPNP_WLED_SOFT_START_DLY_US + 1000); rc = qpnp_wled_psm_config(wled, false); if (rc < 0) return rc; Loading Loading @@ -873,32 +882,25 @@ static ssize_t qpnp_wled_fs_curr_ua_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct qpnp_wled *wled = dev_get_drvdata(dev); int data, i, rc, temp; int data, i, rc; u8 reg; rc = kstrtoint(buf, 10, &data); if (rc) return rc; for (i = 0; i < wled->num_strings; i++) { for (i = 0; i < wled->max_strings; i++) { if (data < QPNP_WLED_FS_CURR_MIN_UA) data = QPNP_WLED_FS_CURR_MIN_UA; else if (data > QPNP_WLED_FS_CURR_MAX_UA) data = QPNP_WLED_FS_CURR_MAX_UA; rc = qpnp_wled_read_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, wled->strings[i]), ®); reg = data / QPNP_WLED_FS_CURR_STEP_UA; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, i), QPNP_WLED_FS_CURR_MASK, reg); if (rc < 0) return rc; reg &= QPNP_WLED_FS_CURR_MASK; temp = data / QPNP_WLED_FS_CURR_STEP_UA; reg |= temp; rc = qpnp_wled_write_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, wled->strings[i]), reg); if (rc) return rc; } wled->fs_curr_ua = data; Loading Loading @@ -1090,6 +1092,229 @@ static int qpnp_wled_set_disp(struct qpnp_wled *wled, u16 base_addr) return 0; } #define AUTO_CALIB_BRIGHTNESS 16 static int wled_auto_calibrate(struct qpnp_wled *wled) { int rc = 0, i; u8 reg = 0, sink_config = 0, sink_test = 0, sink_valid = 0, int_sts; mutex_lock(&wled->lock); /* disable OVP IRQ */ if (wled->ovp_irq > 0 && !wled->ovp_irq_disabled) { disable_irq_nosync(wled->ovp_irq); wled->ovp_irq_disabled = true; } /* read configured sink configuration */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), &sink_config); if (rc < 0) { pr_err("Failed to read SINK configuration rc=%d\n", rc); goto failed_calib; } /* disable the module before starting calibration */ rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), QPNP_WLED_MODULE_EN_MASK, 0); if (rc < 0) { pr_err("Failed to disable WLED module rc=%d\n", rc); goto failed_calib; } /* set low brightness across all sinks */ rc = qpnp_wled_set_level(wled, AUTO_CALIB_BRIGHTNESS); if (rc < 0) { pr_err("Failed to set brightness for calibration rc=%d\n", rc); goto failed_calib; } /* disable all sinks */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), 0); if (rc < 0) { pr_err("Failed to disable all sinks rc=%d\n", rc); goto failed_calib; } rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), QPNP_WLED_MODULE_EN_MASK, QPNP_WLED_MODULE_EN_MASK); if (rc < 0) { pr_err("Failed to enable WLED module rc=%d\n", rc); goto failed_calib; } /* * Delay for the WLED soft-start, check the OVP status * only after soft-start is complete */ usleep_range(QPNP_WLED_SOFT_START_DLY_US, QPNP_WLED_SOFT_START_DLY_US + 1000); /* iterate through the strings one by one */ for (i = 0; i < wled->max_strings; i++) { sink_test = 1 << (QPNP_WLED_CURR_SINK_SHIFT + i); /* Enable feedback control */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_FDBK_OP_REG(wled->ctrl_base), i + 1); if (rc < 0) { pr_err("Failed to enable feedback for SINK %d rc = %d\n", i + 1, rc); goto failed_calib; } /* enable the sink */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), sink_test); if (rc < 0) { pr_err("Failed to configure SINK %d rc=%d\n", i + 1, rc); goto failed_calib; } /* delay for WLED soft-start */ usleep_range(QPNP_WLED_SOFT_START_DLY_US, QPNP_WLED_SOFT_START_DLY_US + 1000); rc = qpnp_wled_read_reg(wled, QPNP_WLED_INT_RT_STS(wled->ctrl_base), &int_sts); if (rc < 0) { pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc); goto failed_calib; } if (int_sts & QPNP_WLED_OVP_FAULT_BIT) pr_debug("WLED OVP fault detected with SINK %d\n", i + 1); else sink_valid |= sink_test; } if (sink_valid == sink_config) { pr_debug("WLED auto-calibration complete, default sink-config=%x OK!\n", sink_config); } else { pr_warn("Invalid WLED default sink config=%x changing it to=%x\n", sink_config, sink_valid); sink_config = sink_valid; } if (!sink_config) { pr_warn("No valid WLED sinks found\n"); goto failed_calib; } rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), QPNP_WLED_MODULE_EN_MASK, 0); if (rc < 0) { pr_err("Failed to disable WLED module rc=%d\n", rc); goto failed_calib; } /* write the new sink configuration */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), sink_config); if (rc < 0) { pr_err("Failed to reconfigure the default sink rc=%d\n", rc); goto failed_calib; } /* MODULATOR_EN setting for valid sinks */ for (i = 0; i < wled->max_strings; i++) { if (sink_config & (1 << (QPNP_WLED_CURR_SINK_SHIFT + i))) reg = (QPNP_WLED_MOD_EN << QPNP_WLED_MOD_EN_SHFT); else reg = 0x0; /* disable modulator_en for unused sink */ if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) reg &= QPNP_WLED_GATE_DRV_MASK; else reg |= ~QPNP_WLED_GATE_DRV_MASK; rc = qpnp_wled_write_reg(wled, QPNP_WLED_MOD_EN_REG(wled->sink_base, i), reg); if (rc < 0) { pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc); goto failed_calib; } } /* restore the feedback setting */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_FDBK_OP_REG(wled->ctrl_base), wled->fdbk_op); if (rc < 0) { pr_err("Failed to restore feedback setting rc=%d\n", rc); goto failed_calib; } /* restore brightness */ rc = qpnp_wled_set_level(wled, wled->cdev.brightness); if (rc < 0) { pr_err("Failed to set brightness after calibration rc=%d\n", rc); goto failed_calib; } rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), QPNP_WLED_MODULE_EN_MASK, QPNP_WLED_MODULE_EN_MASK); if (rc < 0) { pr_err("Failed to enable WLED module rc=%d\n", rc); goto failed_calib; } /* delay for WLED soft-start */ usleep_range(QPNP_WLED_SOFT_START_DLY_US, QPNP_WLED_SOFT_START_DLY_US + 1000); failed_calib: if (wled->ovp_irq > 0 && wled->ovp_irq_disabled) { enable_irq(wled->ovp_irq); wled->ovp_irq_disabled = false; } mutex_unlock(&wled->lock); return rc; } #define WLED_AUTO_CAL_OVP_COUNT 5 #define WLED_AUTO_CAL_CNT_DLY_US 1000000 /* 1 second */ static bool qpnp_wled_auto_cal_required(struct qpnp_wled *wled) { s64 elapsed_time_us; /* * Check if the OVP fault was an occasional one * or if its firing continuously, the latter qualifies * for an auto-calibration check. */ if (!wled->auto_calibration_ovp_count) { wled->start_ovp_fault_time = ktime_get(); wled->auto_calibration_ovp_count++; } else { elapsed_time_us = ktime_us_delta(ktime_get(), wled->start_ovp_fault_time); if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US) wled->auto_calibration_ovp_count = 0; else wled->auto_calibration_ovp_count++; if (wled->auto_calibration_ovp_count >= WLED_AUTO_CAL_OVP_COUNT) { wled->auto_calibration_ovp_count = 0; return true; } } return false; } /* ovp irq handler */ static irqreturn_t qpnp_wled_ovp_irq_handler(int irq, void *_wled) { Loading @@ -1114,6 +1339,21 @@ static irqreturn_t qpnp_wled_ovp_irq_handler(int irq, void *_wled) if (fault_sts & (QPNP_WLED_OVP_FAULT_BIT | QPNP_WLED_ILIM_FAULT_BIT)) pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n", int_sts, fault_sts); if (fault_sts & QPNP_WLED_OVP_FAULT_BIT) { if (wled->auto_calib_enabled && !wled->auto_calib_done) { if (qpnp_wled_auto_cal_required(wled)) { rc = wled_auto_calibrate(wled); if (rc < 0) { pr_err("Failed auto-calibration rc=%d\n", rc); return IRQ_HANDLED; } wled->auto_calib_done = true; } } } return IRQ_HANDLED; } Loading Loading @@ -1423,7 +1663,7 @@ static int qpnp_wled_vref_config(struct qpnp_wled *wled) static int qpnp_wled_config(struct qpnp_wled *wled) { int rc, i, temp; u8 reg = 0; u8 reg = 0, sink_en = 0, mask; /* Configure display type */ rc = qpnp_wled_set_disp(wled, wled->ctrl_base); Loading Loading @@ -1622,93 +1862,77 @@ static int qpnp_wled_config(struct qpnp_wled *wled) rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), reg); for (i = 0; i < wled->num_strings; i++) { if (wled->strings[i] >= QPNP_WLED_MAX_STRINGS) { dev_err(&wled->pdev->dev, "Invalid string number\n"); return -EINVAL; } /* MODULATOR */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_MOD_EN_REG(wled->sink_base, wled->strings[i]), ®); if (rc < 0) return rc; reg &= QPNP_WLED_MOD_EN_MASK; reg |= (QPNP_WLED_MOD_EN << QPNP_WLED_MOD_EN_SHFT); if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) reg &= QPNP_WLED_GATE_DRV_MASK; else reg |= ~QPNP_WLED_GATE_DRV_MASK; rc = qpnp_wled_write_reg(wled, QPNP_WLED_MOD_EN_REG(wled->sink_base, wled->strings[i]), reg); if (rc) return rc; for (i = 0; i < wled->max_strings; i++) { /* SYNC DELAY */ if (wled->sync_dly_us > QPNP_WLED_SYNC_DLY_MAX_US) wled->sync_dly_us = QPNP_WLED_SYNC_DLY_MAX_US; rc = qpnp_wled_read_reg(wled, QPNP_WLED_SYNC_DLY_REG(wled->sink_base, wled->strings[i]), ®); reg = wled->sync_dly_us / QPNP_WLED_SYNC_DLY_STEP_US; mask = QPNP_WLED_SYNC_DLY_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_SYNC_DLY_REG(wled->sink_base, i), mask, reg); if (rc < 0) return rc; reg &= QPNP_WLED_SYNC_DLY_MASK; temp = wled->sync_dly_us / QPNP_WLED_SYNC_DLY_STEP_US; reg |= temp; rc = qpnp_wled_write_reg(wled, QPNP_WLED_SYNC_DLY_REG(wled->sink_base, wled->strings[i]), reg); if (rc) return rc; /* FULL SCALE CURRENT */ if (wled->fs_curr_ua > QPNP_WLED_FS_CURR_MAX_UA) wled->fs_curr_ua = QPNP_WLED_FS_CURR_MAX_UA; rc = qpnp_wled_read_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, wled->strings[i]), ®); reg = wled->fs_curr_ua / QPNP_WLED_FS_CURR_STEP_UA; mask = QPNP_WLED_FS_CURR_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, i), mask, reg); if (rc < 0) return rc; reg &= QPNP_WLED_FS_CURR_MASK; temp = wled->fs_curr_ua / QPNP_WLED_FS_CURR_STEP_UA; reg |= temp; rc = qpnp_wled_write_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, wled->strings[i]), reg); if (rc) return rc; /* CABC */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_CABC_REG(wled->sink_base, wled->strings[i]), ®); reg = wled->en_cabc ? (1 << QPNP_WLED_CABC_SHIFT) : 0; mask = QPNP_WLED_CABC_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_CABC_REG(wled->sink_base, i), mask, reg); if (rc < 0) return rc; reg &= QPNP_WLED_CABC_MASK; reg |= (wled->en_cabc << QPNP_WLED_CABC_SHIFT); rc = qpnp_wled_write_reg(wled, QPNP_WLED_CABC_REG(wled->sink_base, wled->strings[i]), reg); if (rc) return rc; } /* Enable CURRENT SINK */ /* Settings specific to valid sinks */ for (i = 0; i < wled->num_strings; i++) { if (wled->strings[i] >= wled->max_strings) { dev_err(&wled->pdev->dev, "Invalid string number\n"); return -EINVAL; } /* MODULATOR */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), ®); QPNP_WLED_MOD_EN_REG(wled->sink_base, i), ®); if (rc < 0) return rc; temp = wled->strings[i] + QPNP_WLED_CURR_SINK_SHIFT; reg |= (1 << temp); reg &= QPNP_WLED_MOD_EN_MASK; reg |= (QPNP_WLED_MOD_EN << QPNP_WLED_MOD_EN_SHFT); if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) reg &= QPNP_WLED_GATE_DRV_MASK; else reg |= ~QPNP_WLED_GATE_DRV_MASK; rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), reg); QPNP_WLED_MOD_EN_REG(wled->sink_base, i), reg); if (rc) return rc; /* SINK EN */ temp = wled->strings[i] + QPNP_WLED_CURR_SINK_SHIFT; sink_en |= (1 << temp); } mask = QPNP_WLED_CURR_SINK_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), mask, sink_en); if (rc < 0) { dev_err(&wled->pdev->dev, "Failed to enable WLED sink config rc = %d\n", rc); return rc; } rc = qpnp_wled_sync_reg_toggle(wled); Loading @@ -1728,9 +1952,14 @@ static int qpnp_wled_config(struct qpnp_wled *wled) wled->ovp_irq, rc); return rc; } rc = qpnp_wled_read_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), ®); /* disable the OVP irq only if the module is not enabled */ if (!rc && !(reg & QPNP_WLED_MODULE_EN_MASK)) { disable_irq(wled->ovp_irq); wled->ovp_irq_disabled = true; } } if (wled->sc_irq >= 0) { wled->sc_cnt = 0; Loading Loading @@ -2091,11 +2320,16 @@ static int qpnp_wled_parse_dt(struct qpnp_wled *wled) wled->en_cabc = of_property_read_bool(pdev->dev.of_node, "qcom,en-cabc"); if (wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) wled->max_strings = QPNP_PM660_WLED_MAX_STRINGS; else wled->max_strings = QPNP_WLED_MAX_STRINGS; prop = of_find_property(pdev->dev.of_node, "qcom,led-strings-list", &temp_val); if (!prop || !temp_val || temp_val > QPNP_WLED_MAX_STRINGS) { dev_err(&pdev->dev, "Invalid strings info, use default"); wled->num_strings = QPNP_WLED_MAX_STRINGS; wled->num_strings = wled->max_strings; for (i = 0; i < wled->num_strings; i++) wled->strings[i] = i; } else { Loading @@ -2118,6 +2352,9 @@ static int qpnp_wled_parse_dt(struct qpnp_wled *wled) wled->lcd_psm_ctrl = of_property_read_bool(pdev->dev.of_node, "qcom,lcd-psm-ctrl"); wled->auto_calib_enabled = of_property_read_bool(pdev->dev.of_node, "qcom,auto-calibration-enable"); return 0; } Loading Loading @@ -2186,13 +2423,13 @@ static int qpnp_wled_probe(struct platform_device *pdev) } mutex_init(&wled->bus_lock); mutex_init(&wled->lock); rc = qpnp_wled_config(wled); if (rc) { dev_err(&pdev->dev, "wled config failed\n"); return rc; } mutex_init(&wled->lock); INIT_WORK(&wled->work, qpnp_wled_work); wled->ramp_ms = QPNP_WLED_RAMP_DLY_MS; wled->ramp_step = 1; Loading Loading
Documentation/devicetree/bindings/leds/leds-qpnp-wled.txt +2 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,8 @@ Optional properties for WLED: - qcom,lcd-psm-ctrl : A boolean property to specify if PSM needs to be controlled dynamically when WLED module is enabled or disabled. - qcom,auto-calibration-enable : A boolean property which enables auto-calibration of the WLED sink configuration. Optional properties if 'qcom,disp-type-amoled' is mentioned in DT: - qcom,loop-comp-res-kohm : control to select the compensation resistor in kohm. default is 320. Loading
arch/arm64/boot/dts/qcom/pm660l.dtsi +1 −0 Original line number Diff line number Diff line Loading @@ -269,6 +269,7 @@ qcom,led-strings-list = [00 01 02]; qcom,loop-auto-gm-en; qcom,pmic-revid = <&pm660l_revid>; qcom,auto-calibration-enable; status = "ok"; }; Loading
arch/arm64/boot/dts/qcom/pmi8998.dtsi +1 −0 Original line number Diff line number Diff line Loading @@ -563,6 +563,7 @@ qcom,en-ext-pfet-sc-pro; qcom,pmic-revid = <&pmi8998_revid>; qcom,loop-auto-gm-en; qcom,auto-calibration-enable; status = "disabled"; }; Loading
drivers/leds/leds-qpnp-wled.c +323 −86 Original line number Diff line number Diff line Loading @@ -160,18 +160,19 @@ #define QPNP_WLED_MOD_EN_SHFT 7 #define QPNP_WLED_MOD_EN 1 #define QPNP_WLED_GATE_DRV_MASK 0xFE #define QPNP_WLED_SYNC_DLY_MASK 0xF8 #define QPNP_WLED_SYNC_DLY_MASK GENMASK(2, 0) #define QPNP_WLED_SYNC_DLY_MIN_US 0 #define QPNP_WLED_SYNC_DLY_MAX_US 1400 #define QPNP_WLED_SYNC_DLY_STEP_US 200 #define QPNP_WLED_DEF_SYNC_DLY_US 400 #define QPNP_WLED_FS_CURR_MASK 0xF0 #define QPNP_WLED_FS_CURR_MASK GENMASK(3, 0) #define QPNP_WLED_FS_CURR_MIN_UA 0 #define QPNP_WLED_FS_CURR_MAX_UA 30000 #define QPNP_WLED_FS_CURR_STEP_UA 2500 #define QPNP_WLED_CABC_MASK 0x7F #define QPNP_WLED_CABC_MASK 0x80 #define QPNP_WLED_CABC_SHIFT 7 #define QPNP_WLED_CURR_SINK_SHIFT 4 #define QPNP_WLED_CURR_SINK_MASK GENMASK(7, 4) #define QPNP_WLED_BRIGHT_LSB_MASK 0xFF #define QPNP_WLED_BRIGHT_MSB_SHIFT 8 #define QPNP_WLED_BRIGHT_MSB_MASK 0x0F Loading Loading @@ -208,12 +209,14 @@ #define QPNP_WLED_SEC_UNLOCK 0xA5 #define QPNP_WLED_MAX_STRINGS 4 #define QPNP_PM660_WLED_MAX_STRINGS 3 #define WLED_MAX_LEVEL_4095 4095 #define QPNP_WLED_RAMP_DLY_MS 20 #define QPNP_WLED_TRIGGER_NONE "none" #define QPNP_WLED_STR_SIZE 20 #define QPNP_WLED_MIN_MSLEEP 20 #define QPNP_WLED_SC_DLY_MS 20 #define QPNP_WLED_SOFT_START_DLY_US 10000 #define NUM_SUPPORTED_AVDD_VOLTAGES 6 #define QPNP_WLED_DFLT_AVDD_MV 7600 Loading Loading @@ -381,6 +384,8 @@ struct qpnp_wled { u16 ramp_ms; u16 ramp_step; u16 cons_sync_write_delay_us; u16 auto_calibration_ovp_count; u16 max_strings; u8 strings[QPNP_WLED_MAX_STRINGS]; u8 num_strings; u8 loop_auto_gm_thresh; Loading @@ -396,6 +401,9 @@ struct qpnp_wled { bool en_ext_pfet_sc_pro; bool prev_state; bool ovp_irq_disabled; bool auto_calib_enabled; bool auto_calib_done; ktime_t start_ovp_fault_time; }; /* helper to read a pmic register */ Loading Loading @@ -531,7 +539,7 @@ static int qpnp_wled_set_level(struct qpnp_wled *wled, int level) u8 reg; /* set brightness registers */ for (i = 0; i < wled->num_strings; i++) { for (i = 0; i < wled->max_strings; i++) { reg = level & QPNP_WLED_BRIGHT_LSB_MASK; rc = qpnp_wled_write_reg(wled, QPNP_WLED_BRIGHT_LSB_REG(wled->sink_base, Loading Loading @@ -600,7 +608,8 @@ static int qpnp_wled_module_en(struct qpnp_wled *wled, * OVP interrupt disabled when the module is disabled. */ if (state) { usleep_range(10000, 11000); usleep_range(QPNP_WLED_SOFT_START_DLY_US, QPNP_WLED_SOFT_START_DLY_US + 1000); rc = qpnp_wled_psm_config(wled, false); if (rc < 0) return rc; Loading Loading @@ -873,32 +882,25 @@ static ssize_t qpnp_wled_fs_curr_ua_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct qpnp_wled *wled = dev_get_drvdata(dev); int data, i, rc, temp; int data, i, rc; u8 reg; rc = kstrtoint(buf, 10, &data); if (rc) return rc; for (i = 0; i < wled->num_strings; i++) { for (i = 0; i < wled->max_strings; i++) { if (data < QPNP_WLED_FS_CURR_MIN_UA) data = QPNP_WLED_FS_CURR_MIN_UA; else if (data > QPNP_WLED_FS_CURR_MAX_UA) data = QPNP_WLED_FS_CURR_MAX_UA; rc = qpnp_wled_read_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, wled->strings[i]), ®); reg = data / QPNP_WLED_FS_CURR_STEP_UA; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, i), QPNP_WLED_FS_CURR_MASK, reg); if (rc < 0) return rc; reg &= QPNP_WLED_FS_CURR_MASK; temp = data / QPNP_WLED_FS_CURR_STEP_UA; reg |= temp; rc = qpnp_wled_write_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, wled->strings[i]), reg); if (rc) return rc; } wled->fs_curr_ua = data; Loading Loading @@ -1090,6 +1092,229 @@ static int qpnp_wled_set_disp(struct qpnp_wled *wled, u16 base_addr) return 0; } #define AUTO_CALIB_BRIGHTNESS 16 static int wled_auto_calibrate(struct qpnp_wled *wled) { int rc = 0, i; u8 reg = 0, sink_config = 0, sink_test = 0, sink_valid = 0, int_sts; mutex_lock(&wled->lock); /* disable OVP IRQ */ if (wled->ovp_irq > 0 && !wled->ovp_irq_disabled) { disable_irq_nosync(wled->ovp_irq); wled->ovp_irq_disabled = true; } /* read configured sink configuration */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), &sink_config); if (rc < 0) { pr_err("Failed to read SINK configuration rc=%d\n", rc); goto failed_calib; } /* disable the module before starting calibration */ rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), QPNP_WLED_MODULE_EN_MASK, 0); if (rc < 0) { pr_err("Failed to disable WLED module rc=%d\n", rc); goto failed_calib; } /* set low brightness across all sinks */ rc = qpnp_wled_set_level(wled, AUTO_CALIB_BRIGHTNESS); if (rc < 0) { pr_err("Failed to set brightness for calibration rc=%d\n", rc); goto failed_calib; } /* disable all sinks */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), 0); if (rc < 0) { pr_err("Failed to disable all sinks rc=%d\n", rc); goto failed_calib; } rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), QPNP_WLED_MODULE_EN_MASK, QPNP_WLED_MODULE_EN_MASK); if (rc < 0) { pr_err("Failed to enable WLED module rc=%d\n", rc); goto failed_calib; } /* * Delay for the WLED soft-start, check the OVP status * only after soft-start is complete */ usleep_range(QPNP_WLED_SOFT_START_DLY_US, QPNP_WLED_SOFT_START_DLY_US + 1000); /* iterate through the strings one by one */ for (i = 0; i < wled->max_strings; i++) { sink_test = 1 << (QPNP_WLED_CURR_SINK_SHIFT + i); /* Enable feedback control */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_FDBK_OP_REG(wled->ctrl_base), i + 1); if (rc < 0) { pr_err("Failed to enable feedback for SINK %d rc = %d\n", i + 1, rc); goto failed_calib; } /* enable the sink */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), sink_test); if (rc < 0) { pr_err("Failed to configure SINK %d rc=%d\n", i + 1, rc); goto failed_calib; } /* delay for WLED soft-start */ usleep_range(QPNP_WLED_SOFT_START_DLY_US, QPNP_WLED_SOFT_START_DLY_US + 1000); rc = qpnp_wled_read_reg(wled, QPNP_WLED_INT_RT_STS(wled->ctrl_base), &int_sts); if (rc < 0) { pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc); goto failed_calib; } if (int_sts & QPNP_WLED_OVP_FAULT_BIT) pr_debug("WLED OVP fault detected with SINK %d\n", i + 1); else sink_valid |= sink_test; } if (sink_valid == sink_config) { pr_debug("WLED auto-calibration complete, default sink-config=%x OK!\n", sink_config); } else { pr_warn("Invalid WLED default sink config=%x changing it to=%x\n", sink_config, sink_valid); sink_config = sink_valid; } if (!sink_config) { pr_warn("No valid WLED sinks found\n"); goto failed_calib; } rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), QPNP_WLED_MODULE_EN_MASK, 0); if (rc < 0) { pr_err("Failed to disable WLED module rc=%d\n", rc); goto failed_calib; } /* write the new sink configuration */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), sink_config); if (rc < 0) { pr_err("Failed to reconfigure the default sink rc=%d\n", rc); goto failed_calib; } /* MODULATOR_EN setting for valid sinks */ for (i = 0; i < wled->max_strings; i++) { if (sink_config & (1 << (QPNP_WLED_CURR_SINK_SHIFT + i))) reg = (QPNP_WLED_MOD_EN << QPNP_WLED_MOD_EN_SHFT); else reg = 0x0; /* disable modulator_en for unused sink */ if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) reg &= QPNP_WLED_GATE_DRV_MASK; else reg |= ~QPNP_WLED_GATE_DRV_MASK; rc = qpnp_wled_write_reg(wled, QPNP_WLED_MOD_EN_REG(wled->sink_base, i), reg); if (rc < 0) { pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc); goto failed_calib; } } /* restore the feedback setting */ rc = qpnp_wled_write_reg(wled, QPNP_WLED_FDBK_OP_REG(wled->ctrl_base), wled->fdbk_op); if (rc < 0) { pr_err("Failed to restore feedback setting rc=%d\n", rc); goto failed_calib; } /* restore brightness */ rc = qpnp_wled_set_level(wled, wled->cdev.brightness); if (rc < 0) { pr_err("Failed to set brightness after calibration rc=%d\n", rc); goto failed_calib; } rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), QPNP_WLED_MODULE_EN_MASK, QPNP_WLED_MODULE_EN_MASK); if (rc < 0) { pr_err("Failed to enable WLED module rc=%d\n", rc); goto failed_calib; } /* delay for WLED soft-start */ usleep_range(QPNP_WLED_SOFT_START_DLY_US, QPNP_WLED_SOFT_START_DLY_US + 1000); failed_calib: if (wled->ovp_irq > 0 && wled->ovp_irq_disabled) { enable_irq(wled->ovp_irq); wled->ovp_irq_disabled = false; } mutex_unlock(&wled->lock); return rc; } #define WLED_AUTO_CAL_OVP_COUNT 5 #define WLED_AUTO_CAL_CNT_DLY_US 1000000 /* 1 second */ static bool qpnp_wled_auto_cal_required(struct qpnp_wled *wled) { s64 elapsed_time_us; /* * Check if the OVP fault was an occasional one * or if its firing continuously, the latter qualifies * for an auto-calibration check. */ if (!wled->auto_calibration_ovp_count) { wled->start_ovp_fault_time = ktime_get(); wled->auto_calibration_ovp_count++; } else { elapsed_time_us = ktime_us_delta(ktime_get(), wled->start_ovp_fault_time); if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US) wled->auto_calibration_ovp_count = 0; else wled->auto_calibration_ovp_count++; if (wled->auto_calibration_ovp_count >= WLED_AUTO_CAL_OVP_COUNT) { wled->auto_calibration_ovp_count = 0; return true; } } return false; } /* ovp irq handler */ static irqreturn_t qpnp_wled_ovp_irq_handler(int irq, void *_wled) { Loading @@ -1114,6 +1339,21 @@ static irqreturn_t qpnp_wled_ovp_irq_handler(int irq, void *_wled) if (fault_sts & (QPNP_WLED_OVP_FAULT_BIT | QPNP_WLED_ILIM_FAULT_BIT)) pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n", int_sts, fault_sts); if (fault_sts & QPNP_WLED_OVP_FAULT_BIT) { if (wled->auto_calib_enabled && !wled->auto_calib_done) { if (qpnp_wled_auto_cal_required(wled)) { rc = wled_auto_calibrate(wled); if (rc < 0) { pr_err("Failed auto-calibration rc=%d\n", rc); return IRQ_HANDLED; } wled->auto_calib_done = true; } } } return IRQ_HANDLED; } Loading Loading @@ -1423,7 +1663,7 @@ static int qpnp_wled_vref_config(struct qpnp_wled *wled) static int qpnp_wled_config(struct qpnp_wled *wled) { int rc, i, temp; u8 reg = 0; u8 reg = 0, sink_en = 0, mask; /* Configure display type */ rc = qpnp_wled_set_disp(wled, wled->ctrl_base); Loading Loading @@ -1622,93 +1862,77 @@ static int qpnp_wled_config(struct qpnp_wled *wled) rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), reg); for (i = 0; i < wled->num_strings; i++) { if (wled->strings[i] >= QPNP_WLED_MAX_STRINGS) { dev_err(&wled->pdev->dev, "Invalid string number\n"); return -EINVAL; } /* MODULATOR */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_MOD_EN_REG(wled->sink_base, wled->strings[i]), ®); if (rc < 0) return rc; reg &= QPNP_WLED_MOD_EN_MASK; reg |= (QPNP_WLED_MOD_EN << QPNP_WLED_MOD_EN_SHFT); if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) reg &= QPNP_WLED_GATE_DRV_MASK; else reg |= ~QPNP_WLED_GATE_DRV_MASK; rc = qpnp_wled_write_reg(wled, QPNP_WLED_MOD_EN_REG(wled->sink_base, wled->strings[i]), reg); if (rc) return rc; for (i = 0; i < wled->max_strings; i++) { /* SYNC DELAY */ if (wled->sync_dly_us > QPNP_WLED_SYNC_DLY_MAX_US) wled->sync_dly_us = QPNP_WLED_SYNC_DLY_MAX_US; rc = qpnp_wled_read_reg(wled, QPNP_WLED_SYNC_DLY_REG(wled->sink_base, wled->strings[i]), ®); reg = wled->sync_dly_us / QPNP_WLED_SYNC_DLY_STEP_US; mask = QPNP_WLED_SYNC_DLY_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_SYNC_DLY_REG(wled->sink_base, i), mask, reg); if (rc < 0) return rc; reg &= QPNP_WLED_SYNC_DLY_MASK; temp = wled->sync_dly_us / QPNP_WLED_SYNC_DLY_STEP_US; reg |= temp; rc = qpnp_wled_write_reg(wled, QPNP_WLED_SYNC_DLY_REG(wled->sink_base, wled->strings[i]), reg); if (rc) return rc; /* FULL SCALE CURRENT */ if (wled->fs_curr_ua > QPNP_WLED_FS_CURR_MAX_UA) wled->fs_curr_ua = QPNP_WLED_FS_CURR_MAX_UA; rc = qpnp_wled_read_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, wled->strings[i]), ®); reg = wled->fs_curr_ua / QPNP_WLED_FS_CURR_STEP_UA; mask = QPNP_WLED_FS_CURR_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, i), mask, reg); if (rc < 0) return rc; reg &= QPNP_WLED_FS_CURR_MASK; temp = wled->fs_curr_ua / QPNP_WLED_FS_CURR_STEP_UA; reg |= temp; rc = qpnp_wled_write_reg(wled, QPNP_WLED_FS_CURR_REG(wled->sink_base, wled->strings[i]), reg); if (rc) return rc; /* CABC */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_CABC_REG(wled->sink_base, wled->strings[i]), ®); reg = wled->en_cabc ? (1 << QPNP_WLED_CABC_SHIFT) : 0; mask = QPNP_WLED_CABC_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_CABC_REG(wled->sink_base, i), mask, reg); if (rc < 0) return rc; reg &= QPNP_WLED_CABC_MASK; reg |= (wled->en_cabc << QPNP_WLED_CABC_SHIFT); rc = qpnp_wled_write_reg(wled, QPNP_WLED_CABC_REG(wled->sink_base, wled->strings[i]), reg); if (rc) return rc; } /* Enable CURRENT SINK */ /* Settings specific to valid sinks */ for (i = 0; i < wled->num_strings; i++) { if (wled->strings[i] >= wled->max_strings) { dev_err(&wled->pdev->dev, "Invalid string number\n"); return -EINVAL; } /* MODULATOR */ rc = qpnp_wled_read_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), ®); QPNP_WLED_MOD_EN_REG(wled->sink_base, i), ®); if (rc < 0) return rc; temp = wled->strings[i] + QPNP_WLED_CURR_SINK_SHIFT; reg |= (1 << temp); reg &= QPNP_WLED_MOD_EN_MASK; reg |= (QPNP_WLED_MOD_EN << QPNP_WLED_MOD_EN_SHFT); if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) reg &= QPNP_WLED_GATE_DRV_MASK; else reg |= ~QPNP_WLED_GATE_DRV_MASK; rc = qpnp_wled_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), reg); QPNP_WLED_MOD_EN_REG(wled->sink_base, i), reg); if (rc) return rc; /* SINK EN */ temp = wled->strings[i] + QPNP_WLED_CURR_SINK_SHIFT; sink_en |= (1 << temp); } mask = QPNP_WLED_CURR_SINK_MASK; rc = qpnp_wled_masked_write_reg(wled, QPNP_WLED_CURR_SINK_REG(wled->sink_base), mask, sink_en); if (rc < 0) { dev_err(&wled->pdev->dev, "Failed to enable WLED sink config rc = %d\n", rc); return rc; } rc = qpnp_wled_sync_reg_toggle(wled); Loading @@ -1728,9 +1952,14 @@ static int qpnp_wled_config(struct qpnp_wled *wled) wled->ovp_irq, rc); return rc; } rc = qpnp_wled_read_reg(wled, QPNP_WLED_MODULE_EN_REG(wled->ctrl_base), ®); /* disable the OVP irq only if the module is not enabled */ if (!rc && !(reg & QPNP_WLED_MODULE_EN_MASK)) { disable_irq(wled->ovp_irq); wled->ovp_irq_disabled = true; } } if (wled->sc_irq >= 0) { wled->sc_cnt = 0; Loading Loading @@ -2091,11 +2320,16 @@ static int qpnp_wled_parse_dt(struct qpnp_wled *wled) wled->en_cabc = of_property_read_bool(pdev->dev.of_node, "qcom,en-cabc"); if (wled->pmic_rev_id->pmic_subtype == PM660L_SUBTYPE) wled->max_strings = QPNP_PM660_WLED_MAX_STRINGS; else wled->max_strings = QPNP_WLED_MAX_STRINGS; prop = of_find_property(pdev->dev.of_node, "qcom,led-strings-list", &temp_val); if (!prop || !temp_val || temp_val > QPNP_WLED_MAX_STRINGS) { dev_err(&pdev->dev, "Invalid strings info, use default"); wled->num_strings = QPNP_WLED_MAX_STRINGS; wled->num_strings = wled->max_strings; for (i = 0; i < wled->num_strings; i++) wled->strings[i] = i; } else { Loading @@ -2118,6 +2352,9 @@ static int qpnp_wled_parse_dt(struct qpnp_wled *wled) wled->lcd_psm_ctrl = of_property_read_bool(pdev->dev.of_node, "qcom,lcd-psm-ctrl"); wled->auto_calib_enabled = of_property_read_bool(pdev->dev.of_node, "qcom,auto-calibration-enable"); return 0; } Loading Loading @@ -2186,13 +2423,13 @@ static int qpnp_wled_probe(struct platform_device *pdev) } mutex_init(&wled->bus_lock); mutex_init(&wled->lock); rc = qpnp_wled_config(wled); if (rc) { dev_err(&pdev->dev, "wled config failed\n"); return rc; } mutex_init(&wled->lock); INIT_WORK(&wled->work, qpnp_wled_work); wled->ramp_ms = QPNP_WLED_RAMP_DLY_MS; wled->ramp_step = 1; Loading