diff mbox

[V1,4/4] qcom: spmi-wled: Add auto-calibration logic support

Message ID 1510834717-21765-5-git-send-email-kgunda@codeaurora.org (mailing list archive)
State New, archived
Headers show

Commit Message

Kiran Gunda Nov. 16, 2017, 12:18 p.m. UTC
The auto-calibration algorithm checks if the current WLED sink
configuration is valid. It tries enabling every sink and checks
if the OVP fault is observed. Based on this information it
detects and enables the valid sink configuration. Auto calibration
will be triggered when the OVP fault interrupts are seen frequently
thereby it tries to fix the sink configuration.

Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
---
 .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
 drivers/video/backlight/qcom-spmi-wled.c           | 304 ++++++++++++++++++++-
 2 files changed, 306 insertions(+), 3 deletions(-)

Comments

Bjorn Andersson Dec. 5, 2017, 5:40 a.m. UTC | #1
On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:

> The auto-calibration algorithm checks if the current WLED sink
> configuration is valid. It tries enabling every sink and checks
> if the OVP fault is observed. Based on this information it
> detects and enables the valid sink configuration. Auto calibration
> will be triggered when the OVP fault interrupts are seen frequently
> thereby it tries to fix the sink configuration.
> 

So it's not auto "calibration" it's auto "detection" of strings?

When is this feature needed?

> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> ---
>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
>  drivers/video/backlight/qcom-spmi-wled.c           | 304 ++++++++++++++++++++-
>  2 files changed, 306 insertions(+), 3 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> index d39ee93..f06c0cd 100644
> --- a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> +++ b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via SPMI bus.
>  	Definition: Interrupt names associated with the interrupts.
>  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
>  
> +- qcom,auto-calibration

qcom,auto-string-detect?

> +	Usage:      optional
> +	Value type: <bool>
> +	Definition: Enables auto-calibration of the WLED sink configuration.
> +
>  Example:
>  
>  qcom-wled@d800 {
> diff --git a/drivers/video/backlight/qcom-spmi-wled.c b/drivers/video/backlight/qcom-spmi-wled.c
> index 8b2a77a..aee5c56 100644
> --- a/drivers/video/backlight/qcom-spmi-wled.c
> +++ b/drivers/video/backlight/qcom-spmi-wled.c
> @@ -38,11 +38,14 @@
>  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
>  
>  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
> +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)

The use of BIT() makes this a mask and not a bit number, so if you just
drop that you can afford to spell out the "FAULT" like the data sheet
does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?

>  
>  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
>  
> +#define QCOM_WLED_CTRL_FDBK_OP			0x48

This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
unreadable?

> +
>  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
>  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
>  
> @@ -99,6 +102,7 @@ struct qcom_wled_config {
>  	int ovp_irq;
>  	bool en_cabc;
>  	bool ext_pfet_sc_pro_en;
> +	bool auto_calib_enabled;
>  };
>  
>  struct qcom_wled {
> @@ -108,18 +112,25 @@ struct qcom_wled {
>  	struct mutex lock;
>  	struct qcom_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 sc_count;
>  	bool prev_state;
>  	bool ovp_irq_disabled;
> +	bool auto_calib_done;
> +	bool force_mod_disable;
>  };
>  
>  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>  {
>  	int rc;
>  
> +	if (wled->force_mod_disable)
> +		return 0;
> +
>  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
> @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct qcom_wled *wled, u16 brightness)
>  	v[1] = (brightness >> 8) & 0xf;
>  
>  	for (i = 0; (string_cfg >> i) != 0; i++) {
> -		if (string_cfg & BIT(i)) {

Why was this check here in the first place, if it's now fine to
configure the brightness of all strings?

Also, a single-string config of 0b0001 will only set brightness on the
first string, while 0b1000 will set brightness on all strings.

>  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
>  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
>  			if (rc < 0)
>  				return rc;
> -		}
>  	}
>  
>  	return 0;
> @@ -294,6 +303,262 @@ static irqreturn_t qcom_wled_sc_irq_handler(int irq, void *_wled)
>  	return IRQ_HANDLED;
>  }
>  
> +#define AUTO_CALIB_BRIGHTNESS		200
> +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
> +{
> +	int rc = 0, i;
> +	u32 sink_config = 0, int_sts;
> +	u8 reg = 0, sink_test = 0, sink_valid = 0;
> +	u8 string_cfg = wled->cfg.string_cfg;
> +
> +	/* read configured sink configuration */
> +	rc = regmap_read(wled->regmap, wled->sink_addr +
> +			QCOM_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 + QCOM_WLED_CTRL_MOD_ENABLE,
> +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
> +	if (rc < 0) {
> +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
> +		goto failed_calib;
> +	}

Any error handling beyond this point seems to leave the backlight off
(indefinitely?), this does seem like potentially bad user experience...

In particular I wonder about the case when this would happen at some
random time, minutes, hours, days, months after the device was booted.

> +
> +	/* set low brightness across all sinks */
> +	rc = qcom_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;
> +	}
> +
> +	if (wled->cfg.en_cabc) {
> +		for (i = 0; (string_cfg >> i) != 0; i++) {
> +			reg = 0;
> +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
> +					QCOM_WLED_SINK_CABC_REG(i),
> +					QCOM_WLED_SINK_CABC_MASK, reg);

Just replace "reg" with 0.

> +			if (rc < 0)
> +				goto failed_calib;
> +		}
> +	}
> +
> +	/* disable all sinks */
> +	rc = regmap_write(wled->regmap,
> +			wled->sink_addr + QCOM_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 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);

BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);

> +
> +		/* Enable feedback control */
> +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
> +				QCOM_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 +
> +				QCOM_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 +
> +				QCOM_WLED_CTRL_MOD_ENABLE,
> +				QCOM_WLED_CTRL_MOD_EN_MASK,
> +				QCOM_WLED_CTRL_MOD_EN_MASK);

I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
makes me wonder why it's done differently in qcom_wled_module_enable().

> +		if (rc < 0) {
> +			pr_err("Failed to enable WLED module rc=%d\n", rc);
> +			goto failed_calib;
> +		}
> +
> +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> +				QCOM_WLED_SOFT_START_DLY_US + 1000);
> +
> +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
> +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
> +		if (rc < 0) {
> +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
> +			goto failed_calib;
> +		}
> +
> +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
> +			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 + QCOM_WLED_CTRL_MOD_ENABLE,
> +				QCOM_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_warn("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 + QCOM_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;
> +	}
> +
> +	/* MODULATOR_EN setting for valid sinks */

"Enable valid sinks"

> +	for (i = 0; (string_cfg >> i) != 0; i++) {
> +		if (wled->cfg.en_cabc) {
> +			reg = QCOM_WLED_SINK_CABC_EN;

"reg" is a bad name of a variable holding the "value" to be written to a
register.

> +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
> +					QCOM_WLED_SINK_CABC_REG(i),
> +					QCOM_WLED_SINK_CABC_MASK, reg);

Again, just inline the value in the function call.

> +			if (rc < 0)
> +				goto failed_calib;
> +		}
> +
> +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))

BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)

> +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
> +		else
> +			reg = 0x0; /* disable modulator_en for unused sink */
> +
> +		rc = regmap_write(wled->regmap, wled->sink_addr +
> +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
> +		if (rc < 0) {
> +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
> +			goto failed_calib;
> +		}
> +	}
> +
> +	/* restore the feedback setting */
> +	rc = regmap_write(wled->regmap,
> +			wled->ctrl_addr + QCOM_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 = qcom_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 + QCOM_WLED_CTRL_MOD_ENABLE,
> +			QCOM_WLED_CTRL_MOD_EN_MASK,
> +			QCOM_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 */

What comes after this that you want to delay?

This delay is used to make the OVP IRQ not fire immediately, but as
we've now successfully executed the string auto detection run we're
never going to do anything in the OVP handler.

> +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> +			QCOM_WLED_SOFT_START_DLY_US + 1000);
> +
> +failed_calib:
> +	return rc;
> +}
> +
> +#define WLED_AUTO_CAL_OVP_COUNT		5
> +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
> +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
> +{
> +	s64 elapsed_time_us;
> +
> +	/*
> +	 * Check if the OVP fault was an occasional one
> +	 * or if its firing continuously, the latter qualifies
> +	 * for an auto-calibration check.
> +	 */
> +	if (!wled->auto_calibration_ovp_count) {
> +		wled->start_ovp_fault_time = ktime_get();
> +		wled->auto_calibration_ovp_count++;
> +	} else {
> +		elapsed_time_us = ktime_us_delta(ktime_get(),
> +				wled->start_ovp_fault_time);
> +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
> +			wled->auto_calibration_ovp_count = 0;
> +		else
> +			wled->auto_calibration_ovp_count++;
> +
> +		if (wled->auto_calibration_ovp_count >=
> +				WLED_AUTO_CAL_OVP_COUNT) {
> +			wled->auto_calibration_ovp_count = 0;
> +			return true;
> +		}
> +	}
> +
> +	return false;
> +}
> +
> +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)

I presume this function is expected to detect if there is a invalid
configuration at boot and try to figure out which strings are actually
wired.

> +{
> +	int rc;
> +	u32 fault_status = 0, rt_status = 0;
> +
> +	if (!wled->cfg.auto_calib_enabled)
> +		return 0;
> +
> +	rc = regmap_read(wled->regmap,
> +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
> +			&rt_status);
> +	if (rc < 0)
> +		pr_err("Failed to read RT status rc=%d\n", rc);
> +
> +	rc = regmap_read(wled->regmap,
> +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
> +			&fault_status);
> +	if (rc < 0)
> +		pr_err("Failed to read fault status rc=%d\n", rc);
> +
> +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
> +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {

You should be able to drop the extra () around these.

> +		mutex_lock(&wled->lock);
> +		rc = qcom_wled_auto_calibrate(wled);
> +		if (rc < 0)
> +			pr_err("Failed auto-calibration rc=%d\n", rc);

qcom_wled_auto_calibrate() did already print, no need to repeat this.

> +		else
> +			wled->auto_calib_done = true;
> +		mutex_unlock(&wled->lock);
> +	}
> +
> +	return rc;
> +}
> +
>  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>  {
>  	struct qcom_wled *wled = _wled;
> @@ -319,6 +584,33 @@ static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
>  			int_sts, fault_sts);
>  
> +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
> +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
> +			if (qcom_wled_auto_cal_required(wled)) {

So this will be invoked only once, iff we didn't boot with a faulty
configuration in which case the qcom_wled_auto_calibrate_at_init() has
already done this step and set auto_calib_done.


Which also would mean that all logic in this handler, beyond the
printouts, are only ever going to be executed zero or one times.

Why don't you just do auto-detection during probe (iff the flag is set
in DT) and you can remove all this extra logic?

> +				mutex_lock(&wled->lock);
> +				if (wled->cfg.ovp_irq > 0 &&
> +						!wled->ovp_irq_disabled) {
> +					disable_irq_nosync(wled->cfg.ovp_irq);
> +					wled->ovp_irq_disabled = true;
> +				}
> +
> +				rc = qcom_wled_auto_calibrate(wled);
> +				if (rc < 0)
> +					pr_err("Failed auto-calibration rc=%d\n",
> +						rc);

qcom_wled_auto_calibrate() did already print.

> +				else
> +					wled->auto_calib_done = true;
> +
> +				if (wled->cfg.ovp_irq > 0 &&
> +						wled->ovp_irq_disabled) {
> +					enable_irq(wled->cfg.ovp_irq);
> +					wled->ovp_irq_disabled = false;
> +				}
> +				mutex_unlock(&wled->lock);
> +			}
> +		}
> +	}
> +

Regards,
Bjorn
--
To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Kiran Gunda April 19, 2018, 10:45 a.m. UTC | #2
On 2017-12-05 11:10, Bjorn Andersson wrote:
> On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
> 
>> The auto-calibration algorithm checks if the current WLED sink
>> configuration is valid. It tries enabling every sink and checks
>> if the OVP fault is observed. Based on this information it
>> detects and enables the valid sink configuration. Auto calibration
>> will be triggered when the OVP fault interrupts are seen frequently
>> thereby it tries to fix the sink configuration.
>> 
> 
> So it's not auto "calibration" it's auto "detection" of strings?
> 
Hi Bjorn,
Sorry for late response. Please find my answers.

Correct. This is the auto detection, This is the name given by the 
HW/systems team.
> When is this feature needed?
> 
This feature is needed if the string configuration is given wrong in the 
DT node by the
user.
>> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
>> ---
>>  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
>>  drivers/video/backlight/qcom-spmi-wled.c           | 304 
>> ++++++++++++++++++++-
>>  2 files changed, 306 insertions(+), 3 deletions(-)
>> 
>> diff --git 
>> a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt 
>> b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> index d39ee93..f06c0cd 100644
>> --- 
>> a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> +++ 
>> b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via 
>> SPMI bus.
>>  	Definition: Interrupt names associated with the interrupts.
>>  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
>> 
>> +- qcom,auto-calibration
> 
> qcom,auto-string-detect?
> 
ok. Will address in the next patch.
>> +	Usage:      optional
>> +	Value type: <bool>
>> +	Definition: Enables auto-calibration of the WLED sink configuration.
>> +
>>  Example:
>> 
>>  qcom-wled@d800 {
>> diff --git a/drivers/video/backlight/qcom-spmi-wled.c 
>> b/drivers/video/backlight/qcom-spmi-wled.c
>> index 8b2a77a..aee5c56 100644
>> --- a/drivers/video/backlight/qcom-spmi-wled.c
>> +++ b/drivers/video/backlight/qcom-spmi-wled.c
>> @@ -38,11 +38,14 @@
>>  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
>> 
>>  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
>> +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
> 
> The use of BIT() makes this a mask and not a bit number, so if you just
> drop that you can afford to spell out the "FAULT" like the data sheet
> does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?
> 
ok. Will change it in the next series.
>> 
>>  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>>  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>>  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
>> 
>> +#define QCOM_WLED_CTRL_FDBK_OP			0x48
> 
> This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
> unreadable?
> 
Ok. Will address it in next series.
>> +
>>  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
>>  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
>> 
>> @@ -99,6 +102,7 @@ struct qcom_wled_config {
>>  	int ovp_irq;
>>  	bool en_cabc;
>>  	bool ext_pfet_sc_pro_en;
>> +	bool auto_calib_enabled;
>>  };
>> 
>>  struct qcom_wled {
>> @@ -108,18 +112,25 @@ struct qcom_wled {
>>  	struct mutex lock;
>>  	struct qcom_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 sc_count;
>>  	bool prev_state;
>>  	bool ovp_irq_disabled;
>> +	bool auto_calib_done;
>> +	bool force_mod_disable;
>>  };
>> 
>>  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>>  {
>>  	int rc;
>> 
>> +	if (wled->force_mod_disable)
>> +		return 0;
>> +
>>  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>>  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>>  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
>> @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct 
>> qcom_wled *wled, u16 brightness)
>>  	v[1] = (brightness >> 8) & 0xf;
>> 
>>  	for (i = 0; (string_cfg >> i) != 0; i++) {
>> -		if (string_cfg & BIT(i)) {
> 
> Why was this check here in the first place, if it's now fine to
> configure the brightness of all strings?
> 
> Also, a single-string config of 0b0001 will only set brightness on the
> first string, while 0b1000 will set brightness on all strings.
> 
I will correct/remove it next series.
>>  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
>>  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
>>  			if (rc < 0)
>>  				return rc;
>> -		}
>>  	}
>> 
>>  	return 0;
>> @@ -294,6 +303,262 @@ static irqreturn_t qcom_wled_sc_irq_handler(int 
>> irq, void *_wled)
>>  	return IRQ_HANDLED;
>>  }
>> 
>> +#define AUTO_CALIB_BRIGHTNESS		200
>> +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
>> +{
>> +	int rc = 0, i;
>> +	u32 sink_config = 0, int_sts;
>> +	u8 reg = 0, sink_test = 0, sink_valid = 0;
>> +	u8 string_cfg = wled->cfg.string_cfg;
>> +
>> +	/* read configured sink configuration */
>> +	rc = regmap_read(wled->regmap, wled->sink_addr +
>> +			QCOM_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 + QCOM_WLED_CTRL_MOD_ENABLE,
>> +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
>> +	if (rc < 0) {
>> +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
>> +		goto failed_calib;
>> +	}
> 
> Any error handling beyond this point seems to leave the backlight off
> (indefinitely?), this does seem like potentially bad user experience...
Ok. will address in next series.
> 
> In particular I wonder about the case when this would happen at some
> random time, minutes, hours, days, months after the device was booted.
> 
This will happen for every reboot.
>> +
>> +	/* set low brightness across all sinks */
>> +	rc = qcom_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;
>> +	}
>> +
>> +	if (wled->cfg.en_cabc) {
>> +		for (i = 0; (string_cfg >> i) != 0; i++) {
>> +			reg = 0;
>> +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>> +					QCOM_WLED_SINK_CABC_REG(i),
>> +					QCOM_WLED_SINK_CABC_MASK, reg);
> 
> Just replace "reg" with 0.
Ok. will address in next series.
> 
>> +			if (rc < 0)
>> +				goto failed_calib;
>> +		}
>> +	}
>> +
>> +	/* disable all sinks */
>> +	rc = regmap_write(wled->regmap,
>> +			wled->sink_addr + QCOM_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 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
> 
> BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);
> 
Will address in next series.
>> +
>> +		/* Enable feedback control */
>> +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
>> +				QCOM_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 +
>> +				QCOM_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 +
>> +				QCOM_WLED_CTRL_MOD_ENABLE,
>> +				QCOM_WLED_CTRL_MOD_EN_MASK,
>> +				QCOM_WLED_CTRL_MOD_EN_MASK);
> 
> I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
> makes me wonder why it's done differently in qcom_wled_module_enable().
> 
will address it in the next series.
>> +		if (rc < 0) {
>> +			pr_err("Failed to enable WLED module rc=%d\n", rc);
>> +			goto failed_calib;
>> +		}
>> +
>> +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> +				QCOM_WLED_SOFT_START_DLY_US + 1000);
>> +
>> +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
>> +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
>> +		if (rc < 0) {
>> +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
>> +			goto failed_calib;
>> +		}
>> +
>> +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
>> +			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 + QCOM_WLED_CTRL_MOD_ENABLE,
>> +				QCOM_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_warn("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 + QCOM_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;
>> +	}
>> +
>> +	/* MODULATOR_EN setting for valid sinks */
> 
> "Enable valid sinks"
> 
Will address it in the next series.
>> +	for (i = 0; (string_cfg >> i) != 0; i++) {
>> +		if (wled->cfg.en_cabc) {
>> +			reg = QCOM_WLED_SINK_CABC_EN;
> 
> "reg" is a bad name of a variable holding the "value" to be written to 
> a
> register.
> 
Will address it in the next series.
>> +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>> +					QCOM_WLED_SINK_CABC_REG(i),
>> +					QCOM_WLED_SINK_CABC_MASK, reg);
> 
> Again, just inline the value in the function call.
> 
Will address it in the next series.
>> +			if (rc < 0)
>> +				goto failed_calib;
>> +		}
>> +
>> +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
> 
> BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)
> 
Will address it in the next series.
>> +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
>> +		else
>> +			reg = 0x0; /* disable modulator_en for unused sink */
>> +
>> +		rc = regmap_write(wled->regmap, wled->sink_addr +
>> +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
>> +		if (rc < 0) {
>> +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
>> +			goto failed_calib;
>> +		}
>> +	}
>> +
>> +	/* restore the feedback setting */
>> +	rc = regmap_write(wled->regmap,
>> +			wled->ctrl_addr + QCOM_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 = qcom_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 + QCOM_WLED_CTRL_MOD_ENABLE,
>> +			QCOM_WLED_CTRL_MOD_EN_MASK,
>> +			QCOM_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 */
> 
> What comes after this that you want to delay?
> 
> This delay is used to make the OVP IRQ not fire immediately, but as
> we've now successfully executed the string auto detection run we're
> never going to do anything in the OVP handler.
> 
Will correct it in the next series.
>> +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> +			QCOM_WLED_SOFT_START_DLY_US + 1000);
>> +
>> +failed_calib:
>> +	return rc;
>> +}
>> +
>> +#define WLED_AUTO_CAL_OVP_COUNT		5
>> +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
>> +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
>> +{
>> +	s64 elapsed_time_us;
>> +
>> +	/*
>> +	 * Check if the OVP fault was an occasional one
>> +	 * or if its firing continuously, the latter qualifies
>> +	 * for an auto-calibration check.
>> +	 */
>> +	if (!wled->auto_calibration_ovp_count) {
>> +		wled->start_ovp_fault_time = ktime_get();
>> +		wled->auto_calibration_ovp_count++;
>> +	} else {
>> +		elapsed_time_us = ktime_us_delta(ktime_get(),
>> +				wled->start_ovp_fault_time);
>> +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
>> +			wled->auto_calibration_ovp_count = 0;
>> +		else
>> +			wled->auto_calibration_ovp_count++;
>> +
>> +		if (wled->auto_calibration_ovp_count >=
>> +				WLED_AUTO_CAL_OVP_COUNT) {
>> +			wled->auto_calibration_ovp_count = 0;
>> +			return true;
>> +		}
>> +	}
>> +
>> +	return false;
>> +}
>> +
>> +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
> 
> I presume this function is expected to detect if there is a invalid
> configuration at boot and try to figure out which strings are actually
> wired.
> 
Correct.
>> +{
>> +	int rc;
>> +	u32 fault_status = 0, rt_status = 0;
>> +
>> +	if (!wled->cfg.auto_calib_enabled)
>> +		return 0;
>> +
>> +	rc = regmap_read(wled->regmap,
>> +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
>> +			&rt_status);
>> +	if (rc < 0)
>> +		pr_err("Failed to read RT status rc=%d\n", rc);
>> +
>> +	rc = regmap_read(wled->regmap,
>> +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
>> +			&fault_status);
>> +	if (rc < 0)
>> +		pr_err("Failed to read fault status rc=%d\n", rc);
>> +
>> +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
>> +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
> 
> You should be able to drop the extra () around these.
> 
Ok. Will remove it in the next series.
>> +		mutex_lock(&wled->lock);
>> +		rc = qcom_wled_auto_calibrate(wled);
>> +		if (rc < 0)
>> +			pr_err("Failed auto-calibration rc=%d\n", rc);
> 
> qcom_wled_auto_calibrate() did already print, no need to repeat this.
> 
Ok. Will remove this in the next series.
>> +		else
>> +			wled->auto_calib_done = true;
>> +		mutex_unlock(&wled->lock);
>> +	}
>> +
>> +	return rc;
>> +}
>> +
>>  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>>  {
>>  	struct qcom_wled *wled = _wled;
>> @@ -319,6 +584,33 @@ static irqreturn_t qcom_wled_ovp_irq_handler(int 
>> irq, void *_wled)
>>  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
>>  			int_sts, fault_sts);
>> 
>> +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
>> +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
>> +			if (qcom_wled_auto_cal_required(wled)) {
> 
> So this will be invoked only once, iff we didn't boot with a faulty
> configuration in which case the qcom_wled_auto_calibrate_at_init() has
> already done this step and set auto_calib_done.
> 
> 
> Which also would mean that all logic in this handler, beyond the
> printouts, are only ever going to be executed zero or one times.
> 
> Why don't you just do auto-detection during probe (iff the flag is set
> in DT) and you can remove all this extra logic?
> 
I think we have seen a issue, where the OVP interrupt is not getting set
some times during the execution of this function at boot. In that case 
the auto calibration is
done bit later. That's why this code is added.
>> +				mutex_lock(&wled->lock);
>> +				if (wled->cfg.ovp_irq > 0 &&
>> +						!wled->ovp_irq_disabled) {
>> +					disable_irq_nosync(wled->cfg.ovp_irq);
>> +					wled->ovp_irq_disabled = true;
>> +				}
>> +
>> +				rc = qcom_wled_auto_calibrate(wled);
>> +				if (rc < 0)
>> +					pr_err("Failed auto-calibration rc=%d\n",
>> +						rc);
> 
> qcom_wled_auto_calibrate() did already print.
> 
Ok. I will remove it in the next series.
>> +				else
>> +					wled->auto_calib_done = true;
>> +
>> +				if (wled->cfg.ovp_irq > 0 &&
>> +						wled->ovp_irq_disabled) {
>> +					enable_irq(wled->cfg.ovp_irq);
>> +					wled->ovp_irq_disabled = false;
>> +				}
>> +				mutex_unlock(&wled->lock);
>> +			}
>> +		}
>> +	}
>> +
> 
> Regards,
> Bjorn
> --
> To unsubscribe from this list: send the line "unsubscribe 
> linux-arm-msm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Bjorn Andersson April 19, 2018, 3:58 p.m. UTC | #3
On Thu 19 Apr 03:45 PDT 2018, kgunda@codeaurora.org wrote:

> 
> On 2017-12-05 11:10, Bjorn Andersson wrote:
> > On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
> > 
> > > The auto-calibration algorithm checks if the current WLED sink
> > > configuration is valid. It tries enabling every sink and checks
> > > if the OVP fault is observed. Based on this information it
> > > detects and enables the valid sink configuration. Auto calibration
> > > will be triggered when the OVP fault interrupts are seen frequently
> > > thereby it tries to fix the sink configuration.
> > > 
> > 
> > So it's not auto "calibration" it's auto "detection" of strings?
> > 
> Hi Bjorn,
> Sorry for late response. Please find my answers.
> 

No worries, happy to hear back from you!

> Correct. This is the auto detection, This is the name given by the
> HW/systems team.

I think the name should be considered a "hardware bug", that we can work
around in software (give it a useful name and document what the original
name was).

> > When is this feature needed?
> > 
> This feature is needed if the string configuration is given wrong in
> the DT node by the user.

DT describes the hardware and for all other nodes it must do so
accurately.

For cases where the hardware supports auto detection of functionality we
remove information from DT and rely on that logic to figure out the
hardware. We do not use it to reconfigure the hardware once we detect an
error. So when auto-detection is enabled it should always be used to
probe the hardware.

Regards,
Bjorn

> > > Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> > > ---
> > >  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
> > >  drivers/video/backlight/qcom-spmi-wled.c           | 304
> > > ++++++++++++++++++++-
> > >  2 files changed, 306 insertions(+), 3 deletions(-)
> > > 
> > > diff --git
> > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > index d39ee93..f06c0cd 100644
> > > ---
> > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > +++
> > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via
> > > SPMI bus.
> > >  	Definition: Interrupt names associated with the interrupts.
> > >  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
> > > 
> > > +- qcom,auto-calibration
> > 
> > qcom,auto-string-detect?
> > 
> ok. Will address in the next patch.
> > > +	Usage:      optional
> > > +	Value type: <bool>
> > > +	Definition: Enables auto-calibration of the WLED sink configuration.
> > > +
> > >  Example:
> > > 
> > >  qcom-wled@d800 {
> > > diff --git a/drivers/video/backlight/qcom-spmi-wled.c
> > > b/drivers/video/backlight/qcom-spmi-wled.c
> > > index 8b2a77a..aee5c56 100644
> > > --- a/drivers/video/backlight/qcom-spmi-wled.c
> > > +++ b/drivers/video/backlight/qcom-spmi-wled.c
> > > @@ -38,11 +38,14 @@
> > >  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
> > > 
> > >  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
> > > +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
> > 
> > The use of BIT() makes this a mask and not a bit number, so if you just
> > drop that you can afford to spell out the "FAULT" like the data sheet
> > does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?
> > 
> ok. Will change it in the next series.
> > > 
> > >  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
> > >  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
> > >  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
> > > 
> > > +#define QCOM_WLED_CTRL_FDBK_OP			0x48
> > 
> > This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
> > unreadable?
> > 
> Ok. Will address it in next series.
> > > +
> > >  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
> > >  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
> > > 
> > > @@ -99,6 +102,7 @@ struct qcom_wled_config {
> > >  	int ovp_irq;
> > >  	bool en_cabc;
> > >  	bool ext_pfet_sc_pro_en;
> > > +	bool auto_calib_enabled;
> > >  };
> > > 
> > >  struct qcom_wled {
> > > @@ -108,18 +112,25 @@ struct qcom_wled {
> > >  	struct mutex lock;
> > >  	struct qcom_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 sc_count;
> > >  	bool prev_state;
> > >  	bool ovp_irq_disabled;
> > > +	bool auto_calib_done;
> > > +	bool force_mod_disable;
> > >  };
> > > 
> > >  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
> > >  {
> > >  	int rc;
> > > 
> > > +	if (wled->force_mod_disable)
> > > +		return 0;
> > > +
> > >  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
> > >  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
> > >  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
> > > @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct
> > > qcom_wled *wled, u16 brightness)
> > >  	v[1] = (brightness >> 8) & 0xf;
> > > 
> > >  	for (i = 0; (string_cfg >> i) != 0; i++) {
> > > -		if (string_cfg & BIT(i)) {
> > 
> > Why was this check here in the first place, if it's now fine to
> > configure the brightness of all strings?
> > 
> > Also, a single-string config of 0b0001 will only set brightness on the
> > first string, while 0b1000 will set brightness on all strings.
> > 
> I will correct/remove it next series.
> > >  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
> > >  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
> > >  			if (rc < 0)
> > >  				return rc;
> > > -		}
> > >  	}
> > > 
> > >  	return 0;
> > > @@ -294,6 +303,262 @@ static irqreturn_t
> > > qcom_wled_sc_irq_handler(int irq, void *_wled)
> > >  	return IRQ_HANDLED;
> > >  }
> > > 
> > > +#define AUTO_CALIB_BRIGHTNESS		200
> > > +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
> > > +{
> > > +	int rc = 0, i;
> > > +	u32 sink_config = 0, int_sts;
> > > +	u8 reg = 0, sink_test = 0, sink_valid = 0;
> > > +	u8 string_cfg = wled->cfg.string_cfg;
> > > +
> > > +	/* read configured sink configuration */
> > > +	rc = regmap_read(wled->regmap, wled->sink_addr +
> > > +			QCOM_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 + QCOM_WLED_CTRL_MOD_ENABLE,
> > > +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
> > > +	if (rc < 0) {
> > > +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
> > > +		goto failed_calib;
> > > +	}
> > 
> > Any error handling beyond this point seems to leave the backlight off
> > (indefinitely?), this does seem like potentially bad user experience...
> Ok. will address in next series.
> > 
> > In particular I wonder about the case when this would happen at some
> > random time, minutes, hours, days, months after the device was booted.
> > 
> This will happen for every reboot.
> > > +
> > > +	/* set low brightness across all sinks */
> > > +	rc = qcom_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;
> > > +	}
> > > +
> > > +	if (wled->cfg.en_cabc) {
> > > +		for (i = 0; (string_cfg >> i) != 0; i++) {
> > > +			reg = 0;
> > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
> > > +					QCOM_WLED_SINK_CABC_REG(i),
> > > +					QCOM_WLED_SINK_CABC_MASK, reg);
> > 
> > Just replace "reg" with 0.
> Ok. will address in next series.
> > 
> > > +			if (rc < 0)
> > > +				goto failed_calib;
> > > +		}
> > > +	}
> > > +
> > > +	/* disable all sinks */
> > > +	rc = regmap_write(wled->regmap,
> > > +			wled->sink_addr + QCOM_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 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
> > 
> > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);
> > 
> Will address in next series.
> > > +
> > > +		/* Enable feedback control */
> > > +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
> > > +				QCOM_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 +
> > > +				QCOM_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 +
> > > +				QCOM_WLED_CTRL_MOD_ENABLE,
> > > +				QCOM_WLED_CTRL_MOD_EN_MASK,
> > > +				QCOM_WLED_CTRL_MOD_EN_MASK);
> > 
> > I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
> > makes me wonder why it's done differently in qcom_wled_module_enable().
> > 
> will address it in the next series.
> > > +		if (rc < 0) {
> > > +			pr_err("Failed to enable WLED module rc=%d\n", rc);
> > > +			goto failed_calib;
> > > +		}
> > > +
> > > +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> > > +				QCOM_WLED_SOFT_START_DLY_US + 1000);
> > > +
> > > +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
> > > +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
> > > +		if (rc < 0) {
> > > +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
> > > +			goto failed_calib;
> > > +		}
> > > +
> > > +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
> > > +			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 + QCOM_WLED_CTRL_MOD_ENABLE,
> > > +				QCOM_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_warn("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 + QCOM_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;
> > > +	}
> > > +
> > > +	/* MODULATOR_EN setting for valid sinks */
> > 
> > "Enable valid sinks"
> > 
> Will address it in the next series.
> > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
> > > +		if (wled->cfg.en_cabc) {
> > > +			reg = QCOM_WLED_SINK_CABC_EN;
> > 
> > "reg" is a bad name of a variable holding the "value" to be written to a
> > register.
> > 
> Will address it in the next series.
> > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
> > > +					QCOM_WLED_SINK_CABC_REG(i),
> > > +					QCOM_WLED_SINK_CABC_MASK, reg);
> > 
> > Again, just inline the value in the function call.
> > 
> Will address it in the next series.
> > > +			if (rc < 0)
> > > +				goto failed_calib;
> > > +		}
> > > +
> > > +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
> > 
> > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)
> > 
> Will address it in the next series.
> > > +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
> > > +		else
> > > +			reg = 0x0; /* disable modulator_en for unused sink */
> > > +
> > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
> > > +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
> > > +		if (rc < 0) {
> > > +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
> > > +			goto failed_calib;
> > > +		}
> > > +	}
> > > +
> > > +	/* restore the feedback setting */
> > > +	rc = regmap_write(wled->regmap,
> > > +			wled->ctrl_addr + QCOM_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 = qcom_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 + QCOM_WLED_CTRL_MOD_ENABLE,
> > > +			QCOM_WLED_CTRL_MOD_EN_MASK,
> > > +			QCOM_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 */
> > 
> > What comes after this that you want to delay?
> > 
> > This delay is used to make the OVP IRQ not fire immediately, but as
> > we've now successfully executed the string auto detection run we're
> > never going to do anything in the OVP handler.
> > 
> Will correct it in the next series.
> > > +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> > > +			QCOM_WLED_SOFT_START_DLY_US + 1000);
> > > +
> > > +failed_calib:
> > > +	return rc;
> > > +}
> > > +
> > > +#define WLED_AUTO_CAL_OVP_COUNT		5
> > > +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
> > > +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
> > > +{
> > > +	s64 elapsed_time_us;
> > > +
> > > +	/*
> > > +	 * Check if the OVP fault was an occasional one
> > > +	 * or if its firing continuously, the latter qualifies
> > > +	 * for an auto-calibration check.
> > > +	 */
> > > +	if (!wled->auto_calibration_ovp_count) {
> > > +		wled->start_ovp_fault_time = ktime_get();
> > > +		wled->auto_calibration_ovp_count++;
> > > +	} else {
> > > +		elapsed_time_us = ktime_us_delta(ktime_get(),
> > > +				wled->start_ovp_fault_time);
> > > +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
> > > +			wled->auto_calibration_ovp_count = 0;
> > > +		else
> > > +			wled->auto_calibration_ovp_count++;
> > > +
> > > +		if (wled->auto_calibration_ovp_count >=
> > > +				WLED_AUTO_CAL_OVP_COUNT) {
> > > +			wled->auto_calibration_ovp_count = 0;
> > > +			return true;
> > > +		}
> > > +	}
> > > +
> > > +	return false;
> > > +}
> > > +
> > > +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
> > 
> > I presume this function is expected to detect if there is a invalid
> > configuration at boot and try to figure out which strings are actually
> > wired.
> > 
> Correct.
> > > +{
> > > +	int rc;
> > > +	u32 fault_status = 0, rt_status = 0;
> > > +
> > > +	if (!wled->cfg.auto_calib_enabled)
> > > +		return 0;
> > > +
> > > +	rc = regmap_read(wled->regmap,
> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
> > > +			&rt_status);
> > > +	if (rc < 0)
> > > +		pr_err("Failed to read RT status rc=%d\n", rc);
> > > +
> > > +	rc = regmap_read(wled->regmap,
> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
> > > +			&fault_status);
> > > +	if (rc < 0)
> > > +		pr_err("Failed to read fault status rc=%d\n", rc);
> > > +
> > > +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
> > > +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
> > 
> > You should be able to drop the extra () around these.
> > 
> Ok. Will remove it in the next series.
> > > +		mutex_lock(&wled->lock);
> > > +		rc = qcom_wled_auto_calibrate(wled);
> > > +		if (rc < 0)
> > > +			pr_err("Failed auto-calibration rc=%d\n", rc);
> > 
> > qcom_wled_auto_calibrate() did already print, no need to repeat this.
> > 
> Ok. Will remove this in the next series.
> > > +		else
> > > +			wled->auto_calib_done = true;
> > > +		mutex_unlock(&wled->lock);
> > > +	}
> > > +
> > > +	return rc;
> > > +}
> > > +
> > >  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
> > >  {
> > >  	struct qcom_wled *wled = _wled;
> > > @@ -319,6 +584,33 @@ static irqreturn_t
> > > qcom_wled_ovp_irq_handler(int irq, void *_wled)
> > >  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
> > >  			int_sts, fault_sts);
> > > 
> > > +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
> > > +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
> > > +			if (qcom_wled_auto_cal_required(wled)) {
> > 
> > So this will be invoked only once, iff we didn't boot with a faulty
> > configuration in which case the qcom_wled_auto_calibrate_at_init() has
> > already done this step and set auto_calib_done.
> > 
> > 
> > Which also would mean that all logic in this handler, beyond the
> > printouts, are only ever going to be executed zero or one times.
> > 
> > Why don't you just do auto-detection during probe (iff the flag is set
> > in DT) and you can remove all this extra logic?
> > 
> I think we have seen a issue, where the OVP interrupt is not getting set
> some times during the execution of this function at boot. In that case the
> auto calibration is
> done bit later. That's why this code is added.
> > > +				mutex_lock(&wled->lock);
> > > +				if (wled->cfg.ovp_irq > 0 &&
> > > +						!wled->ovp_irq_disabled) {
> > > +					disable_irq_nosync(wled->cfg.ovp_irq);
> > > +					wled->ovp_irq_disabled = true;
> > > +				}
> > > +
> > > +				rc = qcom_wled_auto_calibrate(wled);
> > > +				if (rc < 0)
> > > +					pr_err("Failed auto-calibration rc=%d\n",
> > > +						rc);
> > 
> > qcom_wled_auto_calibrate() did already print.
> > 
> Ok. I will remove it in the next series.
> > > +				else
> > > +					wled->auto_calib_done = true;
> > > +
> > > +				if (wled->cfg.ovp_irq > 0 &&
> > > +						wled->ovp_irq_disabled) {
> > > +					enable_irq(wled->cfg.ovp_irq);
> > > +					wled->ovp_irq_disabled = false;
> > > +				}
> > > +				mutex_unlock(&wled->lock);
> > > +			}
> > > +		}
> > > +	}
> > > +
> > 
> > Regards,
> > Bjorn
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
> > in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Kiran Gunda April 20, 2018, 5:43 a.m. UTC | #4
On 2018-04-19 21:28, Bjorn Andersson wrote:
> On Thu 19 Apr 03:45 PDT 2018, kgunda@codeaurora.org wrote:
> 
>> 
>> On 2017-12-05 11:10, Bjorn Andersson wrote:
>> > On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
>> >
>> > > The auto-calibration algorithm checks if the current WLED sink
>> > > configuration is valid. It tries enabling every sink and checks
>> > > if the OVP fault is observed. Based on this information it
>> > > detects and enables the valid sink configuration. Auto calibration
>> > > will be triggered when the OVP fault interrupts are seen frequently
>> > > thereby it tries to fix the sink configuration.
>> > >
>> >
>> > So it's not auto "calibration" it's auto "detection" of strings?
>> >
>> Hi Bjorn,
>> Sorry for late response. Please find my answers.
>> 
> 
> No worries, happy to hear back from you!
> 
Thanks!
>> Correct. This is the auto detection, This is the name given by the
>> HW/systems team.
> 
> I think the name should be considered a "hardware bug", that we can 
> work
> around in software (give it a useful name and document what the 
> original
> name was).
> 
I don't think this is the "hardware bug". Rather we can say HW doesn't 
support it.
Hence, we are implementing it as a SW feature to detect the strings 
present on the
display panel, if the user fails to give the correct strings. As you 
suggested I will
rename this to "auto detection" instead of "auto calibration".

>> > When is this feature needed?
>> >
>> This feature is needed if the string configuration is given wrong in
>> the DT node by the user.
> 
> DT describes the hardware and for all other nodes it must do so
> accurately.
> 
But the user may not be aware of the strings present on the display 
panel or
may be using the same software on different devices which have different 
strings
present.
> For cases where the hardware supports auto detection of functionality 
> we
> remove information from DT and rely on that logic to figure out the
> hardware. We do not use it to reconfigure the hardware once we detect 
> an
> error. So when auto-detection is enabled it should always be used to
> probe the hardware.
> 
The auto string detection is not supported in any qcom hardware and i 
don't
think there is a plan to introduce in new hardware also.

> Regards,
> Bjorn
> 
>> > > Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
>> > > ---
>> > >  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
>> > >  drivers/video/backlight/qcom-spmi-wled.c           | 304
>> > > ++++++++++++++++++++-
>> > >  2 files changed, 306 insertions(+), 3 deletions(-)
>> > >
>> > > diff --git
>> > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > index d39ee93..f06c0cd 100644
>> > > ---
>> > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > +++
>> > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via
>> > > SPMI bus.
>> > >  	Definition: Interrupt names associated with the interrupts.
>> > >  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
>> > >
>> > > +- qcom,auto-calibration
>> >
>> > qcom,auto-string-detect?
>> >
>> ok. Will address in the next patch.
>> > > +	Usage:      optional
>> > > +	Value type: <bool>
>> > > +	Definition: Enables auto-calibration of the WLED sink configuration.
>> > > +
>> > >  Example:
>> > >
>> > >  qcom-wled@d800 {
>> > > diff --git a/drivers/video/backlight/qcom-spmi-wled.c
>> > > b/drivers/video/backlight/qcom-spmi-wled.c
>> > > index 8b2a77a..aee5c56 100644
>> > > --- a/drivers/video/backlight/qcom-spmi-wled.c
>> > > +++ b/drivers/video/backlight/qcom-spmi-wled.c
>> > > @@ -38,11 +38,14 @@
>> > >  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
>> > >
>> > >  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
>> > > +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
>> >
>> > The use of BIT() makes this a mask and not a bit number, so if you just
>> > drop that you can afford to spell out the "FAULT" like the data sheet
>> > does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?
>> >
>> ok. Will change it in the next series.
>> > >
>> > >  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>> > >  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>> > >  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
>> > >
>> > > +#define QCOM_WLED_CTRL_FDBK_OP			0x48
>> >
>> > This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
>> > unreadable?
>> >
>> Ok. Will address it in next series.
>> > > +
>> > >  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
>> > >  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
>> > >
>> > > @@ -99,6 +102,7 @@ struct qcom_wled_config {
>> > >  	int ovp_irq;
>> > >  	bool en_cabc;
>> > >  	bool ext_pfet_sc_pro_en;
>> > > +	bool auto_calib_enabled;
>> > >  };
>> > >
>> > >  struct qcom_wled {
>> > > @@ -108,18 +112,25 @@ struct qcom_wled {
>> > >  	struct mutex lock;
>> > >  	struct qcom_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 sc_count;
>> > >  	bool prev_state;
>> > >  	bool ovp_irq_disabled;
>> > > +	bool auto_calib_done;
>> > > +	bool force_mod_disable;
>> > >  };
>> > >
>> > >  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>> > >  {
>> > >  	int rc;
>> > >
>> > > +	if (wled->force_mod_disable)
>> > > +		return 0;
>> > > +
>> > >  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>> > >  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>> > >  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
>> > > @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct
>> > > qcom_wled *wled, u16 brightness)
>> > >  	v[1] = (brightness >> 8) & 0xf;
>> > >
>> > >  	for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > -		if (string_cfg & BIT(i)) {
>> >
>> > Why was this check here in the first place, if it's now fine to
>> > configure the brightness of all strings?
>> >
>> > Also, a single-string config of 0b0001 will only set brightness on the
>> > first string, while 0b1000 will set brightness on all strings.
>> >
>> I will correct/remove it next series.
>> > >  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
>> > >  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
>> > >  			if (rc < 0)
>> > >  				return rc;
>> > > -		}
>> > >  	}
>> > >
>> > >  	return 0;
>> > > @@ -294,6 +303,262 @@ static irqreturn_t
>> > > qcom_wled_sc_irq_handler(int irq, void *_wled)
>> > >  	return IRQ_HANDLED;
>> > >  }
>> > >
>> > > +#define AUTO_CALIB_BRIGHTNESS		200
>> > > +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
>> > > +{
>> > > +	int rc = 0, i;
>> > > +	u32 sink_config = 0, int_sts;
>> > > +	u8 reg = 0, sink_test = 0, sink_valid = 0;
>> > > +	u8 string_cfg = wled->cfg.string_cfg;
>> > > +
>> > > +	/* read configured sink configuration */
>> > > +	rc = regmap_read(wled->regmap, wled->sink_addr +
>> > > +			QCOM_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 + QCOM_WLED_CTRL_MOD_ENABLE,
>> > > +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
>> > > +	if (rc < 0) {
>> > > +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
>> > > +		goto failed_calib;
>> > > +	}
>> >
>> > Any error handling beyond this point seems to leave the backlight off
>> > (indefinitely?), this does seem like potentially bad user experience...
>> Ok. will address in next series.
>> >
>> > In particular I wonder about the case when this would happen at some
>> > random time, minutes, hours, days, months after the device was booted.
>> >
>> This will happen for every reboot.
>> > > +
>> > > +	/* set low brightness across all sinks */
>> > > +	rc = qcom_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;
>> > > +	}
>> > > +
>> > > +	if (wled->cfg.en_cabc) {
>> > > +		for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > +			reg = 0;
>> > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>> > > +					QCOM_WLED_SINK_CABC_REG(i),
>> > > +					QCOM_WLED_SINK_CABC_MASK, reg);
>> >
>> > Just replace "reg" with 0.
>> Ok. will address in next series.
>> >
>> > > +			if (rc < 0)
>> > > +				goto failed_calib;
>> > > +		}
>> > > +	}
>> > > +
>> > > +	/* disable all sinks */
>> > > +	rc = regmap_write(wled->regmap,
>> > > +			wled->sink_addr + QCOM_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 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
>> >
>> > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);
>> >
>> Will address in next series.
>> > > +
>> > > +		/* Enable feedback control */
>> > > +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
>> > > +				QCOM_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 +
>> > > +				QCOM_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 +
>> > > +				QCOM_WLED_CTRL_MOD_ENABLE,
>> > > +				QCOM_WLED_CTRL_MOD_EN_MASK,
>> > > +				QCOM_WLED_CTRL_MOD_EN_MASK);
>> >
>> > I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
>> > makes me wonder why it's done differently in qcom_wled_module_enable().
>> >
>> will address it in the next series.
>> > > +		if (rc < 0) {
>> > > +			pr_err("Failed to enable WLED module rc=%d\n", rc);
>> > > +			goto failed_calib;
>> > > +		}
>> > > +
>> > > +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> > > +				QCOM_WLED_SOFT_START_DLY_US + 1000);
>> > > +
>> > > +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
>> > > +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
>> > > +		if (rc < 0) {
>> > > +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
>> > > +			goto failed_calib;
>> > > +		}
>> > > +
>> > > +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
>> > > +			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 + QCOM_WLED_CTRL_MOD_ENABLE,
>> > > +				QCOM_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_warn("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 + QCOM_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;
>> > > +	}
>> > > +
>> > > +	/* MODULATOR_EN setting for valid sinks */
>> >
>> > "Enable valid sinks"
>> >
>> Will address it in the next series.
>> > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > +		if (wled->cfg.en_cabc) {
>> > > +			reg = QCOM_WLED_SINK_CABC_EN;
>> >
>> > "reg" is a bad name of a variable holding the "value" to be written to a
>> > register.
>> >
>> Will address it in the next series.
>> > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>> > > +					QCOM_WLED_SINK_CABC_REG(i),
>> > > +					QCOM_WLED_SINK_CABC_MASK, reg);
>> >
>> > Again, just inline the value in the function call.
>> >
>> Will address it in the next series.
>> > > +			if (rc < 0)
>> > > +				goto failed_calib;
>> > > +		}
>> > > +
>> > > +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
>> >
>> > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)
>> >
>> Will address it in the next series.
>> > > +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
>> > > +		else
>> > > +			reg = 0x0; /* disable modulator_en for unused sink */
>> > > +
>> > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
>> > > +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
>> > > +		if (rc < 0) {
>> > > +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
>> > > +			goto failed_calib;
>> > > +		}
>> > > +	}
>> > > +
>> > > +	/* restore the feedback setting */
>> > > +	rc = regmap_write(wled->regmap,
>> > > +			wled->ctrl_addr + QCOM_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 = qcom_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 + QCOM_WLED_CTRL_MOD_ENABLE,
>> > > +			QCOM_WLED_CTRL_MOD_EN_MASK,
>> > > +			QCOM_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 */
>> >
>> > What comes after this that you want to delay?
>> >
>> > This delay is used to make the OVP IRQ not fire immediately, but as
>> > we've now successfully executed the string auto detection run we're
>> > never going to do anything in the OVP handler.
>> >
>> Will correct it in the next series.
>> > > +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> > > +			QCOM_WLED_SOFT_START_DLY_US + 1000);
>> > > +
>> > > +failed_calib:
>> > > +	return rc;
>> > > +}
>> > > +
>> > > +#define WLED_AUTO_CAL_OVP_COUNT		5
>> > > +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
>> > > +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
>> > > +{
>> > > +	s64 elapsed_time_us;
>> > > +
>> > > +	/*
>> > > +	 * Check if the OVP fault was an occasional one
>> > > +	 * or if its firing continuously, the latter qualifies
>> > > +	 * for an auto-calibration check.
>> > > +	 */
>> > > +	if (!wled->auto_calibration_ovp_count) {
>> > > +		wled->start_ovp_fault_time = ktime_get();
>> > > +		wled->auto_calibration_ovp_count++;
>> > > +	} else {
>> > > +		elapsed_time_us = ktime_us_delta(ktime_get(),
>> > > +				wled->start_ovp_fault_time);
>> > > +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
>> > > +			wled->auto_calibration_ovp_count = 0;
>> > > +		else
>> > > +			wled->auto_calibration_ovp_count++;
>> > > +
>> > > +		if (wled->auto_calibration_ovp_count >=
>> > > +				WLED_AUTO_CAL_OVP_COUNT) {
>> > > +			wled->auto_calibration_ovp_count = 0;
>> > > +			return true;
>> > > +		}
>> > > +	}
>> > > +
>> > > +	return false;
>> > > +}
>> > > +
>> > > +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
>> >
>> > I presume this function is expected to detect if there is a invalid
>> > configuration at boot and try to figure out which strings are actually
>> > wired.
>> >
>> Correct.
>> > > +{
>> > > +	int rc;
>> > > +	u32 fault_status = 0, rt_status = 0;
>> > > +
>> > > +	if (!wled->cfg.auto_calib_enabled)
>> > > +		return 0;
>> > > +
>> > > +	rc = regmap_read(wled->regmap,
>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
>> > > +			&rt_status);
>> > > +	if (rc < 0)
>> > > +		pr_err("Failed to read RT status rc=%d\n", rc);
>> > > +
>> > > +	rc = regmap_read(wled->regmap,
>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
>> > > +			&fault_status);
>> > > +	if (rc < 0)
>> > > +		pr_err("Failed to read fault status rc=%d\n", rc);
>> > > +
>> > > +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
>> > > +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
>> >
>> > You should be able to drop the extra () around these.
>> >
>> Ok. Will remove it in the next series.
>> > > +		mutex_lock(&wled->lock);
>> > > +		rc = qcom_wled_auto_calibrate(wled);
>> > > +		if (rc < 0)
>> > > +			pr_err("Failed auto-calibration rc=%d\n", rc);
>> >
>> > qcom_wled_auto_calibrate() did already print, no need to repeat this.
>> >
>> Ok. Will remove this in the next series.
>> > > +		else
>> > > +			wled->auto_calib_done = true;
>> > > +		mutex_unlock(&wled->lock);
>> > > +	}
>> > > +
>> > > +	return rc;
>> > > +}
>> > > +
>> > >  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>> > >  {
>> > >  	struct qcom_wled *wled = _wled;
>> > > @@ -319,6 +584,33 @@ static irqreturn_t
>> > > qcom_wled_ovp_irq_handler(int irq, void *_wled)
>> > >  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
>> > >  			int_sts, fault_sts);
>> > >
>> > > +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
>> > > +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
>> > > +			if (qcom_wled_auto_cal_required(wled)) {
>> >
>> > So this will be invoked only once, iff we didn't boot with a faulty
>> > configuration in which case the qcom_wled_auto_calibrate_at_init() has
>> > already done this step and set auto_calib_done.
>> >
>> >
>> > Which also would mean that all logic in this handler, beyond the
>> > printouts, are only ever going to be executed zero or one times.
>> >
>> > Why don't you just do auto-detection during probe (iff the flag is set
>> > in DT) and you can remove all this extra logic?
>> >
>> I think we have seen a issue, where the OVP interrupt is not getting 
>> set
>> some times during the execution of this function at boot. In that case 
>> the
>> auto calibration is
>> done bit later. That's why this code is added.
>> > > +				mutex_lock(&wled->lock);
>> > > +				if (wled->cfg.ovp_irq > 0 &&
>> > > +						!wled->ovp_irq_disabled) {
>> > > +					disable_irq_nosync(wled->cfg.ovp_irq);
>> > > +					wled->ovp_irq_disabled = true;
>> > > +				}
>> > > +
>> > > +				rc = qcom_wled_auto_calibrate(wled);
>> > > +				if (rc < 0)
>> > > +					pr_err("Failed auto-calibration rc=%d\n",
>> > > +						rc);
>> >
>> > qcom_wled_auto_calibrate() did already print.
>> >
>> Ok. I will remove it in the next series.
>> > > +				else
>> > > +					wled->auto_calib_done = true;
>> > > +
>> > > +				if (wled->cfg.ovp_irq > 0 &&
>> > > +						wled->ovp_irq_disabled) {
>> > > +					enable_irq(wled->cfg.ovp_irq);
>> > > +					wled->ovp_irq_disabled = false;
>> > > +				}
>> > > +				mutex_unlock(&wled->lock);
>> > > +			}
>> > > +		}
>> > > +	}
>> > > +
>> >
>> > Regards,
>> > Bjorn
>> > --
>> > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
>> > in
>> > the body of a message to majordomo@vger.kernel.org
>> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> --
> To unsubscribe from this list: send the line "unsubscribe 
> linux-arm-msm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Bjorn Andersson April 20, 2018, 4:03 p.m. UTC | #5
On Thu 19 Apr 22:43 PDT 2018, kgunda@codeaurora.org wrote:

> On 2018-04-19 21:28, Bjorn Andersson wrote:
> > On Thu 19 Apr 03:45 PDT 2018, kgunda@codeaurora.org wrote:
> > > On 2017-12-05 11:10, Bjorn Andersson wrote:
[..]
> > > > When is this feature needed?
> > > >
> > > This feature is needed if the string configuration is given wrong in
> > > the DT node by the user.
> > 
> > DT describes the hardware and for all other nodes it must do so
> > accurately.
> > 
> But the user may not be aware of the strings present on the display panel or
> may be using the same software on different devices which have different
> strings present.

Swapping display board would still require an update to the DTS, to
support the new panel. But I think that if you're implementing auto
string detection, it should be used as the mechanism to detect the
strings, not something that is run at some later point in time when we
detect a failure.

> > For cases where the hardware supports auto detection of functionality we
> > remove information from DT and rely on that logic to figure out the
> > hardware. We do not use it to reconfigure the hardware once we detect an
> > error. So when auto-detection is enabled it should always be used to
> > probe the hardware.
> > 
> The auto string detection is not supported in any qcom hardware and i don't
> think there is a plan to introduce in new hardware also.
> 

Sorry, I don't follow. Who is using auto string detection then?

Regards,
Bjorn

> > Regards,
> > Bjorn
> > 
> > > > > Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> > > > > ---
> > > > >  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
> > > > >  drivers/video/backlight/qcom-spmi-wled.c           | 304
> > > > > ++++++++++++++++++++-
> > > > >  2 files changed, 306 insertions(+), 3 deletions(-)
> > > > >
> > > > > diff --git
> > > > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > > > index d39ee93..f06c0cd 100644
> > > > > ---
> > > > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > > > +++
> > > > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
> > > > > @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via
> > > > > SPMI bus.
> > > > >  	Definition: Interrupt names associated with the interrupts.
> > > > >  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
> > > > >
> > > > > +- qcom,auto-calibration
> > > >
> > > > qcom,auto-string-detect?
> > > >
> > > ok. Will address in the next patch.
> > > > > +	Usage:      optional
> > > > > +	Value type: <bool>
> > > > > +	Definition: Enables auto-calibration of the WLED sink configuration.
> > > > > +
> > > > >  Example:
> > > > >
> > > > >  qcom-wled@d800 {
> > > > > diff --git a/drivers/video/backlight/qcom-spmi-wled.c
> > > > > b/drivers/video/backlight/qcom-spmi-wled.c
> > > > > index 8b2a77a..aee5c56 100644
> > > > > --- a/drivers/video/backlight/qcom-spmi-wled.c
> > > > > +++ b/drivers/video/backlight/qcom-spmi-wled.c
> > > > > @@ -38,11 +38,14 @@
> > > > >  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
> > > > >
> > > > >  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
> > > > > +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
> > > >
> > > > The use of BIT() makes this a mask and not a bit number, so if you just
> > > > drop that you can afford to spell out the "FAULT" like the data sheet
> > > > does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?
> > > >
> > > ok. Will change it in the next series.
> > > > >
> > > > >  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
> > > > >  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
> > > > >  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
> > > > >
> > > > > +#define QCOM_WLED_CTRL_FDBK_OP			0x48
> > > >
> > > > This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
> > > > unreadable?
> > > >
> > > Ok. Will address it in next series.
> > > > > +
> > > > >  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
> > > > >  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
> > > > >
> > > > > @@ -99,6 +102,7 @@ struct qcom_wled_config {
> > > > >  	int ovp_irq;
> > > > >  	bool en_cabc;
> > > > >  	bool ext_pfet_sc_pro_en;
> > > > > +	bool auto_calib_enabled;
> > > > >  };
> > > > >
> > > > >  struct qcom_wled {
> > > > > @@ -108,18 +112,25 @@ struct qcom_wled {
> > > > >  	struct mutex lock;
> > > > >  	struct qcom_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 sc_count;
> > > > >  	bool prev_state;
> > > > >  	bool ovp_irq_disabled;
> > > > > +	bool auto_calib_done;
> > > > > +	bool force_mod_disable;
> > > > >  };
> > > > >
> > > > >  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
> > > > >  {
> > > > >  	int rc;
> > > > >
> > > > > +	if (wled->force_mod_disable)
> > > > > +		return 0;
> > > > > +
> > > > >  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
> > > > >  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
> > > > >  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
> > > > > @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct
> > > > > qcom_wled *wled, u16 brightness)
> > > > >  	v[1] = (brightness >> 8) & 0xf;
> > > > >
> > > > >  	for (i = 0; (string_cfg >> i) != 0; i++) {
> > > > > -		if (string_cfg & BIT(i)) {
> > > >
> > > > Why was this check here in the first place, if it's now fine to
> > > > configure the brightness of all strings?
> > > >
> > > > Also, a single-string config of 0b0001 will only set brightness on the
> > > > first string, while 0b1000 will set brightness on all strings.
> > > >
> > > I will correct/remove it next series.
> > > > >  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
> > > > >  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
> > > > >  			if (rc < 0)
> > > > >  				return rc;
> > > > > -		}
> > > > >  	}
> > > > >
> > > > >  	return 0;
> > > > > @@ -294,6 +303,262 @@ static irqreturn_t
> > > > > qcom_wled_sc_irq_handler(int irq, void *_wled)
> > > > >  	return IRQ_HANDLED;
> > > > >  }
> > > > >
> > > > > +#define AUTO_CALIB_BRIGHTNESS		200
> > > > > +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
> > > > > +{
> > > > > +	int rc = 0, i;
> > > > > +	u32 sink_config = 0, int_sts;
> > > > > +	u8 reg = 0, sink_test = 0, sink_valid = 0;
> > > > > +	u8 string_cfg = wled->cfg.string_cfg;
> > > > > +
> > > > > +	/* read configured sink configuration */
> > > > > +	rc = regmap_read(wled->regmap, wled->sink_addr +
> > > > > +			QCOM_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 + QCOM_WLED_CTRL_MOD_ENABLE,
> > > > > +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
> > > > > +	if (rc < 0) {
> > > > > +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
> > > > > +		goto failed_calib;
> > > > > +	}
> > > >
> > > > Any error handling beyond this point seems to leave the backlight off
> > > > (indefinitely?), this does seem like potentially bad user experience...
> > > Ok. will address in next series.
> > > >
> > > > In particular I wonder about the case when this would happen at some
> > > > random time, minutes, hours, days, months after the device was booted.
> > > >
> > > This will happen for every reboot.
> > > > > +
> > > > > +	/* set low brightness across all sinks */
> > > > > +	rc = qcom_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;
> > > > > +	}
> > > > > +
> > > > > +	if (wled->cfg.en_cabc) {
> > > > > +		for (i = 0; (string_cfg >> i) != 0; i++) {
> > > > > +			reg = 0;
> > > > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
> > > > > +					QCOM_WLED_SINK_CABC_REG(i),
> > > > > +					QCOM_WLED_SINK_CABC_MASK, reg);
> > > >
> > > > Just replace "reg" with 0.
> > > Ok. will address in next series.
> > > >
> > > > > +			if (rc < 0)
> > > > > +				goto failed_calib;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	/* disable all sinks */
> > > > > +	rc = regmap_write(wled->regmap,
> > > > > +			wled->sink_addr + QCOM_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 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
> > > >
> > > > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);
> > > >
> > > Will address in next series.
> > > > > +
> > > > > +		/* Enable feedback control */
> > > > > +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
> > > > > +				QCOM_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 +
> > > > > +				QCOM_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 +
> > > > > +				QCOM_WLED_CTRL_MOD_ENABLE,
> > > > > +				QCOM_WLED_CTRL_MOD_EN_MASK,
> > > > > +				QCOM_WLED_CTRL_MOD_EN_MASK);
> > > >
> > > > I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
> > > > makes me wonder why it's done differently in qcom_wled_module_enable().
> > > >
> > > will address it in the next series.
> > > > > +		if (rc < 0) {
> > > > > +			pr_err("Failed to enable WLED module rc=%d\n", rc);
> > > > > +			goto failed_calib;
> > > > > +		}
> > > > > +
> > > > > +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> > > > > +				QCOM_WLED_SOFT_START_DLY_US + 1000);
> > > > > +
> > > > > +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
> > > > > +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
> > > > > +		if (rc < 0) {
> > > > > +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
> > > > > +			goto failed_calib;
> > > > > +		}
> > > > > +
> > > > > +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
> > > > > +			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 + QCOM_WLED_CTRL_MOD_ENABLE,
> > > > > +				QCOM_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_warn("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 + QCOM_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;
> > > > > +	}
> > > > > +
> > > > > +	/* MODULATOR_EN setting for valid sinks */
> > > >
> > > > "Enable valid sinks"
> > > >
> > > Will address it in the next series.
> > > > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
> > > > > +		if (wled->cfg.en_cabc) {
> > > > > +			reg = QCOM_WLED_SINK_CABC_EN;
> > > >
> > > > "reg" is a bad name of a variable holding the "value" to be written to a
> > > > register.
> > > >
> > > Will address it in the next series.
> > > > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
> > > > > +					QCOM_WLED_SINK_CABC_REG(i),
> > > > > +					QCOM_WLED_SINK_CABC_MASK, reg);
> > > >
> > > > Again, just inline the value in the function call.
> > > >
> > > Will address it in the next series.
> > > > > +			if (rc < 0)
> > > > > +				goto failed_calib;
> > > > > +		}
> > > > > +
> > > > > +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
> > > >
> > > > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)
> > > >
> > > Will address it in the next series.
> > > > > +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
> > > > > +		else
> > > > > +			reg = 0x0; /* disable modulator_en for unused sink */
> > > > > +
> > > > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
> > > > > +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
> > > > > +		if (rc < 0) {
> > > > > +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
> > > > > +			goto failed_calib;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	/* restore the feedback setting */
> > > > > +	rc = regmap_write(wled->regmap,
> > > > > +			wled->ctrl_addr + QCOM_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 = qcom_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 + QCOM_WLED_CTRL_MOD_ENABLE,
> > > > > +			QCOM_WLED_CTRL_MOD_EN_MASK,
> > > > > +			QCOM_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 */
> > > >
> > > > What comes after this that you want to delay?
> > > >
> > > > This delay is used to make the OVP IRQ not fire immediately, but as
> > > > we've now successfully executed the string auto detection run we're
> > > > never going to do anything in the OVP handler.
> > > >
> > > Will correct it in the next series.
> > > > > +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
> > > > > +			QCOM_WLED_SOFT_START_DLY_US + 1000);
> > > > > +
> > > > > +failed_calib:
> > > > > +	return rc;
> > > > > +}
> > > > > +
> > > > > +#define WLED_AUTO_CAL_OVP_COUNT		5
> > > > > +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
> > > > > +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
> > > > > +{
> > > > > +	s64 elapsed_time_us;
> > > > > +
> > > > > +	/*
> > > > > +	 * Check if the OVP fault was an occasional one
> > > > > +	 * or if its firing continuously, the latter qualifies
> > > > > +	 * for an auto-calibration check.
> > > > > +	 */
> > > > > +	if (!wled->auto_calibration_ovp_count) {
> > > > > +		wled->start_ovp_fault_time = ktime_get();
> > > > > +		wled->auto_calibration_ovp_count++;
> > > > > +	} else {
> > > > > +		elapsed_time_us = ktime_us_delta(ktime_get(),
> > > > > +				wled->start_ovp_fault_time);
> > > > > +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
> > > > > +			wled->auto_calibration_ovp_count = 0;
> > > > > +		else
> > > > > +			wled->auto_calibration_ovp_count++;
> > > > > +
> > > > > +		if (wled->auto_calibration_ovp_count >=
> > > > > +				WLED_AUTO_CAL_OVP_COUNT) {
> > > > > +			wled->auto_calibration_ovp_count = 0;
> > > > > +			return true;
> > > > > +		}
> > > > > +	}
> > > > > +
> > > > > +	return false;
> > > > > +}
> > > > > +
> > > > > +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
> > > >
> > > > I presume this function is expected to detect if there is a invalid
> > > > configuration at boot and try to figure out which strings are actually
> > > > wired.
> > > >
> > > Correct.
> > > > > +{
> > > > > +	int rc;
> > > > > +	u32 fault_status = 0, rt_status = 0;
> > > > > +
> > > > > +	if (!wled->cfg.auto_calib_enabled)
> > > > > +		return 0;
> > > > > +
> > > > > +	rc = regmap_read(wled->regmap,
> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
> > > > > +			&rt_status);
> > > > > +	if (rc < 0)
> > > > > +		pr_err("Failed to read RT status rc=%d\n", rc);
> > > > > +
> > > > > +	rc = regmap_read(wled->regmap,
> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
> > > > > +			&fault_status);
> > > > > +	if (rc < 0)
> > > > > +		pr_err("Failed to read fault status rc=%d\n", rc);
> > > > > +
> > > > > +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
> > > > > +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
> > > >
> > > > You should be able to drop the extra () around these.
> > > >
> > > Ok. Will remove it in the next series.
> > > > > +		mutex_lock(&wled->lock);
> > > > > +		rc = qcom_wled_auto_calibrate(wled);
> > > > > +		if (rc < 0)
> > > > > +			pr_err("Failed auto-calibration rc=%d\n", rc);
> > > >
> > > > qcom_wled_auto_calibrate() did already print, no need to repeat this.
> > > >
> > > Ok. Will remove this in the next series.
> > > > > +		else
> > > > > +			wled->auto_calib_done = true;
> > > > > +		mutex_unlock(&wled->lock);
> > > > > +	}
> > > > > +
> > > > > +	return rc;
> > > > > +}
> > > > > +
> > > > >  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
> > > > >  {
> > > > >  	struct qcom_wled *wled = _wled;
> > > > > @@ -319,6 +584,33 @@ static irqreturn_t
> > > > > qcom_wled_ovp_irq_handler(int irq, void *_wled)
> > > > >  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
> > > > >  			int_sts, fault_sts);
> > > > >
> > > > > +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
> > > > > +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
> > > > > +			if (qcom_wled_auto_cal_required(wled)) {
> > > >
> > > > So this will be invoked only once, iff we didn't boot with a faulty
> > > > configuration in which case the qcom_wled_auto_calibrate_at_init() has
> > > > already done this step and set auto_calib_done.
> > > >
> > > >
> > > > Which also would mean that all logic in this handler, beyond the
> > > > printouts, are only ever going to be executed zero or one times.
> > > >
> > > > Why don't you just do auto-detection during probe (iff the flag is set
> > > > in DT) and you can remove all this extra logic?
> > > >
> > > I think we have seen a issue, where the OVP interrupt is not getting
> > > set
> > > some times during the execution of this function at boot. In that
> > > case the
> > > auto calibration is
> > > done bit later. That's why this code is added.
> > > > > +				mutex_lock(&wled->lock);
> > > > > +				if (wled->cfg.ovp_irq > 0 &&
> > > > > +						!wled->ovp_irq_disabled) {
> > > > > +					disable_irq_nosync(wled->cfg.ovp_irq);
> > > > > +					wled->ovp_irq_disabled = true;
> > > > > +				}
> > > > > +
> > > > > +				rc = qcom_wled_auto_calibrate(wled);
> > > > > +				if (rc < 0)
> > > > > +					pr_err("Failed auto-calibration rc=%d\n",
> > > > > +						rc);
> > > >
> > > > qcom_wled_auto_calibrate() did already print.
> > > >
> > > Ok. I will remove it in the next series.
> > > > > +				else
> > > > > +					wled->auto_calib_done = true;
> > > > > +
> > > > > +				if (wled->cfg.ovp_irq > 0 &&
> > > > > +						wled->ovp_irq_disabled) {
> > > > > +					enable_irq(wled->cfg.ovp_irq);
> > > > > +					wled->ovp_irq_disabled = false;
> > > > > +				}
> > > > > +				mutex_unlock(&wled->lock);
> > > > > +			}
> > > > > +		}
> > > > > +	}
> > > > > +
> > > >
> > > > Regards,
> > > > Bjorn
> > > > --
> > > > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
> > > > in
> > > > the body of a message to majordomo@vger.kernel.org
> > > > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
> > in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Kiran Gunda April 23, 2018, 10:35 a.m. UTC | #6
On 2018-04-20 11:13, kgunda@codeaurora.org wrote:
> On 2018-04-19 21:28, Bjorn Andersson wrote:
>> On Thu 19 Apr 03:45 PDT 2018, kgunda@codeaurora.org wrote:
>> 
>>> 
>>> On 2017-12-05 11:10, Bjorn Andersson wrote:
>>> > On Thu 16 Nov 04:18 PST 2017, Kiran Gunda wrote:
>>> >
>>> > > The auto-calibration algorithm checks if the current WLED sink
>>> > > configuration is valid. It tries enabling every sink and checks
>>> > > if the OVP fault is observed. Based on this information it
>>> > > detects and enables the valid sink configuration. Auto calibration
>>> > > will be triggered when the OVP fault interrupts are seen frequently
>>> > > thereby it tries to fix the sink configuration.
>>> > >
>>> >
>>> > So it's not auto "calibration" it's auto "detection" of strings?
>>> >
>>> Hi Bjorn,
>>> Sorry for late response. Please find my answers.
>>> 
>> 
>> No worries, happy to hear back from you!
>> 
> Thanks!
>>> Correct. This is the auto detection, This is the name given by the
>>> HW/systems team.
>> 
>> I think the name should be considered a "hardware bug", that we can 
>> work
>> around in software (give it a useful name and document what the 
>> original
>> name was).
>> 
> I don't think this is the "hardware bug". Rather we can say HW doesn't
> support it.
> Hence, we are implementing it as a SW feature to detect the strings
> present on the
> display panel, if the user fails to give the correct strings. As you
> suggested I will
> rename this to "auto detection" instead of "auto calibration".
> 
>>> > When is this feature needed?
>>> >
>>> This feature is needed if the string configuration is given wrong in
>>> the DT node by the user.
>> 
>> DT describes the hardware and for all other nodes it must do so
>> accurately.
>> 
> But the user may not be aware of the strings present on the display 
> panel or
> may be using the same software on different devices which have 
> different strings
> present.
>> For cases where the hardware supports auto detection of functionality 
>> we
>> remove information from DT and rely on that logic to figure out the
>> hardware. We do not use it to reconfigure the hardware once we detect 
>> an
>> error. So when auto-detection is enabled it should always be used to
>> probe the hardware.
>> 
> The auto string detection is not supported in any qcom hardware and i 
> don't
> think there is a plan to introduce in new hardware also.
> 
>> Regards,
>> Bjorn
>> 
>>> > > Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
>>> > > ---
>>> > >  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
>>> > >  drivers/video/backlight/qcom-spmi-wled.c           | 304
>>> > > ++++++++++++++++++++-
>>> > >  2 files changed, 306 insertions(+), 3 deletions(-)
>>> > >
>>> > > diff --git
>>> > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>>> > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>>> > > index d39ee93..f06c0cd 100644
>>> > > ---
>>> > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>>> > > +++
>>> > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>>> > > @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via
>>> > > SPMI bus.
>>> > >  	Definition: Interrupt names associated with the interrupts.
>>> > >  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
>>> > >
>>> > > +- qcom,auto-calibration
>>> >
>>> > qcom,auto-string-detect?
>>> >
>>> ok. Will address in the next patch.
>>> > > +	Usage:      optional
>>> > > +	Value type: <bool>
>>> > > +	Definition: Enables auto-calibration of the WLED sink configuration.
>>> > > +
>>> > >  Example:
>>> > >
>>> > >  qcom-wled@d800 {
>>> > > diff --git a/drivers/video/backlight/qcom-spmi-wled.c
>>> > > b/drivers/video/backlight/qcom-spmi-wled.c
>>> > > index 8b2a77a..aee5c56 100644
>>> > > --- a/drivers/video/backlight/qcom-spmi-wled.c
>>> > > +++ b/drivers/video/backlight/qcom-spmi-wled.c
>>> > > @@ -38,11 +38,14 @@
>>> > >  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
>>> > >
>>> > >  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
>>> > > +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
>>> >
>>> > The use of BIT() makes this a mask and not a bit number, so if you just
>>> > drop that you can afford to spell out the "FAULT" like the data sheet
>>> > does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?
>>> >
>>> ok. Will change it in the next series.
>>> > >
>>> > >  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>>> > >  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>>> > >  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
>>> > >
>>> > > +#define QCOM_WLED_CTRL_FDBK_OP			0x48
>>> >
>>> > This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
>>> > unreadable?
>>> >
>>> Ok. Will address it in next series.
>>> > > +
>>> > >  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
>>> > >  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
>>> > >
>>> > > @@ -99,6 +102,7 @@ struct qcom_wled_config {
>>> > >  	int ovp_irq;
>>> > >  	bool en_cabc;
>>> > >  	bool ext_pfet_sc_pro_en;
>>> > > +	bool auto_calib_enabled;
>>> > >  };
>>> > >
>>> > >  struct qcom_wled {
>>> > > @@ -108,18 +112,25 @@ struct qcom_wled {
>>> > >  	struct mutex lock;
>>> > >  	struct qcom_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 sc_count;
>>> > >  	bool prev_state;
>>> > >  	bool ovp_irq_disabled;
>>> > > +	bool auto_calib_done;
>>> > > +	bool force_mod_disable;
>>> > >  };
>>> > >
>>> > >  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>>> > >  {
>>> > >  	int rc;
>>> > >
>>> > > +	if (wled->force_mod_disable)
>>> > > +		return 0;
>>> > > +
>>> > >  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>>> > >  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>>> > >  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
>>> > > @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct
>>> > > qcom_wled *wled, u16 brightness)
>>> > >  	v[1] = (brightness >> 8) & 0xf;
>>> > >
>>> > >  	for (i = 0; (string_cfg >> i) != 0; i++) {
>>> > > -		if (string_cfg & BIT(i)) {
>>> >
>>> > Why was this check here in the first place, if it's now fine to
>>> > configure the brightness of all strings?
>>> >
>>> > Also, a single-string config of 0b0001 will only set brightness on the
>>> > first string, while 0b1000 will set brightness on all strings.
>>> >
>>> I will correct/remove it next series.
>>> > >  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
>>> > >  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
>>> > >  			if (rc < 0)
>>> > >  				return rc;
>>> > > -		}
>>> > >  	}
>>> > >
>>> > >  	return 0;
>>> > > @@ -294,6 +303,262 @@ static irqreturn_t
>>> > > qcom_wled_sc_irq_handler(int irq, void *_wled)
>>> > >  	return IRQ_HANDLED;
>>> > >  }
>>> > >
>>> > > +#define AUTO_CALIB_BRIGHTNESS		200
>>> > > +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
>>> > > +{
>>> > > +	int rc = 0, i;
>>> > > +	u32 sink_config = 0, int_sts;
>>> > > +	u8 reg = 0, sink_test = 0, sink_valid = 0;
>>> > > +	u8 string_cfg = wled->cfg.string_cfg;
>>> > > +
>>> > > +	/* read configured sink configuration */
>>> > > +	rc = regmap_read(wled->regmap, wled->sink_addr +
>>> > > +			QCOM_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 + QCOM_WLED_CTRL_MOD_ENABLE,
>>> > > +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
>>> > > +	if (rc < 0) {
>>> > > +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
>>> > > +		goto failed_calib;
>>> > > +	}
>>> >
>>> > Any error handling beyond this point seems to leave the backlight off
>>> > (indefinitely?), this does seem like potentially bad user experience...
>>> Ok. will address in next series.
>>> >
It is very unlikely to hit the error path in the below code. As the 
below code
just deals with the registers read/writes through the SPMI interface, 
which is a stable
interface. In worst case if see the errors, there is no use of bypassing 
the error handling
as the register write itself doesn't go through and the complete system 
gets effected. Having
error handling at-least gives some information.
>>> > In particular I wonder about the case when this would happen at some
>>> > random time, minutes, hours, days, months after the device was booted.
>>> >
>>> This will happen for every reboot.
>>> > > +
>>> > > +	/* set low brightness across all sinks */
>>> > > +	rc = qcom_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;
>>> > > +	}
>>> > > +
>>> > > +	if (wled->cfg.en_cabc) {
>>> > > +		for (i = 0; (string_cfg >> i) != 0; i++) {
>>> > > +			reg = 0;
>>> > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>>> > > +					QCOM_WLED_SINK_CABC_REG(i),
>>> > > +					QCOM_WLED_SINK_CABC_MASK, reg);
>>> >
>>> > Just replace "reg" with 0.
>>> Ok. will address in next series.
>>> >
>>> > > +			if (rc < 0)
>>> > > +				goto failed_calib;
>>> > > +		}
>>> > > +	}
>>> > > +
>>> > > +	/* disable all sinks */
>>> > > +	rc = regmap_write(wled->regmap,
>>> > > +			wled->sink_addr + QCOM_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 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
>>> >
>>> > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);
>>> >
>>> Will address in next series.
>>> > > +
>>> > > +		/* Enable feedback control */
>>> > > +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
>>> > > +				QCOM_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 +
>>> > > +				QCOM_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 +
>>> > > +				QCOM_WLED_CTRL_MOD_ENABLE,
>>> > > +				QCOM_WLED_CTRL_MOD_EN_MASK,
>>> > > +				QCOM_WLED_CTRL_MOD_EN_MASK);
>>> >
>>> > I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
>>> > makes me wonder why it's done differently in qcom_wled_module_enable().
>>> >
>>> will address it in the next series.
>>> > > +		if (rc < 0) {
>>> > > +			pr_err("Failed to enable WLED module rc=%d\n", rc);
>>> > > +			goto failed_calib;
>>> > > +		}
>>> > > +
>>> > > +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>>> > > +				QCOM_WLED_SOFT_START_DLY_US + 1000);
>>> > > +
>>> > > +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
>>> > > +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
>>> > > +		if (rc < 0) {
>>> > > +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
>>> > > +			goto failed_calib;
>>> > > +		}
>>> > > +
>>> > > +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
>>> > > +			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 + QCOM_WLED_CTRL_MOD_ENABLE,
>>> > > +				QCOM_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_warn("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 + QCOM_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;
>>> > > +	}
>>> > > +
>>> > > +	/* MODULATOR_EN setting for valid sinks */
>>> >
>>> > "Enable valid sinks"
>>> >
>>> Will address it in the next series.
>>> > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
>>> > > +		if (wled->cfg.en_cabc) {
>>> > > +			reg = QCOM_WLED_SINK_CABC_EN;
>>> >
>>> > "reg" is a bad name of a variable holding the "value" to be written to a
>>> > register.
>>> >
>>> Will address it in the next series.
>>> > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>>> > > +					QCOM_WLED_SINK_CABC_REG(i),
>>> > > +					QCOM_WLED_SINK_CABC_MASK, reg);
>>> >
>>> > Again, just inline the value in the function call.
>>> >
>>> Will address it in the next series.
>>> > > +			if (rc < 0)
>>> > > +				goto failed_calib;
>>> > > +		}
>>> > > +
>>> > > +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
>>> >
>>> > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)
>>> >
>>> Will address it in the next series.
>>> > > +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
>>> > > +		else
>>> > > +			reg = 0x0; /* disable modulator_en for unused sink */
>>> > > +
>>> > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
>>> > > +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
>>> > > +		if (rc < 0) {
>>> > > +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
>>> > > +			goto failed_calib;
>>> > > +		}
>>> > > +	}
>>> > > +
>>> > > +	/* restore the feedback setting */
>>> > > +	rc = regmap_write(wled->regmap,
>>> > > +			wled->ctrl_addr + QCOM_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 = qcom_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 + QCOM_WLED_CTRL_MOD_ENABLE,
>>> > > +			QCOM_WLED_CTRL_MOD_EN_MASK,
>>> > > +			QCOM_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 */
>>> >
>>> > What comes after this that you want to delay?
>>> >
>>> > This delay is used to make the OVP IRQ not fire immediately, but as
>>> > we've now successfully executed the string auto detection run we're
>>> > never going to do anything in the OVP handler.
>>> >
>>> Will correct it in the next series.
>>> > > +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>>> > > +			QCOM_WLED_SOFT_START_DLY_US + 1000);
>>> > > +
>>> > > +failed_calib:
>>> > > +	return rc;
>>> > > +}
>>> > > +
>>> > > +#define WLED_AUTO_CAL_OVP_COUNT		5
>>> > > +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
>>> > > +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
>>> > > +{
>>> > > +	s64 elapsed_time_us;
>>> > > +
>>> > > +	/*
>>> > > +	 * Check if the OVP fault was an occasional one
>>> > > +	 * or if its firing continuously, the latter qualifies
>>> > > +	 * for an auto-calibration check.
>>> > > +	 */
>>> > > +	if (!wled->auto_calibration_ovp_count) {
>>> > > +		wled->start_ovp_fault_time = ktime_get();
>>> > > +		wled->auto_calibration_ovp_count++;
>>> > > +	} else {
>>> > > +		elapsed_time_us = ktime_us_delta(ktime_get(),
>>> > > +				wled->start_ovp_fault_time);
>>> > > +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
>>> > > +			wled->auto_calibration_ovp_count = 0;
>>> > > +		else
>>> > > +			wled->auto_calibration_ovp_count++;
>>> > > +
>>> > > +		if (wled->auto_calibration_ovp_count >=
>>> > > +				WLED_AUTO_CAL_OVP_COUNT) {
>>> > > +			wled->auto_calibration_ovp_count = 0;
>>> > > +			return true;
>>> > > +		}
>>> > > +	}
>>> > > +
>>> > > +	return false;
>>> > > +}
>>> > > +
>>> > > +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
>>> >
>>> > I presume this function is expected to detect if there is a invalid
>>> > configuration at boot and try to figure out which strings are actually
>>> > wired.
>>> >
>>> Correct.
>>> > > +{
>>> > > +	int rc;
>>> > > +	u32 fault_status = 0, rt_status = 0;
>>> > > +
>>> > > +	if (!wled->cfg.auto_calib_enabled)
>>> > > +		return 0;
>>> > > +
>>> > > +	rc = regmap_read(wled->regmap,
>>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
>>> > > +			&rt_status);
>>> > > +	if (rc < 0)
>>> > > +		pr_err("Failed to read RT status rc=%d\n", rc);
>>> > > +
>>> > > +	rc = regmap_read(wled->regmap,
>>> > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
>>> > > +			&fault_status);
>>> > > +	if (rc < 0)
>>> > > +		pr_err("Failed to read fault status rc=%d\n", rc);
>>> > > +
>>> > > +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
>>> > > +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
>>> >
>>> > You should be able to drop the extra () around these.
>>> >
>>> Ok. Will remove it in the next series.
>>> > > +		mutex_lock(&wled->lock);
>>> > > +		rc = qcom_wled_auto_calibrate(wled);
>>> > > +		if (rc < 0)
>>> > > +			pr_err("Failed auto-calibration rc=%d\n", rc);
>>> >
>>> > qcom_wled_auto_calibrate() did already print, no need to repeat this.
>>> >
>>> Ok. Will remove this in the next series.
>>> > > +		else
>>> > > +			wled->auto_calib_done = true;
>>> > > +		mutex_unlock(&wled->lock);
>>> > > +	}
>>> > > +
>>> > > +	return rc;
>>> > > +}
>>> > > +
>>> > >  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>>> > >  {
>>> > >  	struct qcom_wled *wled = _wled;
>>> > > @@ -319,6 +584,33 @@ static irqreturn_t
>>> > > qcom_wled_ovp_irq_handler(int irq, void *_wled)
>>> > >  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
>>> > >  			int_sts, fault_sts);
>>> > >
>>> > > +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
>>> > > +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
>>> > > +			if (qcom_wled_auto_cal_required(wled)) {
>>> >
>>> > So this will be invoked only once, iff we didn't boot with a faulty
>>> > configuration in which case the qcom_wled_auto_calibrate_at_init() has
>>> > already done this step and set auto_calib_done.
>>> >
>>> >
>>> > Which also would mean that all logic in this handler, beyond the
>>> > printouts, are only ever going to be executed zero or one times.
>>> >
>>> > Why don't you just do auto-detection during probe (iff the flag is set
>>> > in DT) and you can remove all this extra logic?
>>> >
>>> I think we have seen a issue, where the OVP interrupt is not getting 
>>> set
>>> some times during the execution of this function at boot. In that 
>>> case the
>>> auto calibration is
>>> done bit later. That's why this code is added.
>>> > > +				mutex_lock(&wled->lock);
>>> > > +				if (wled->cfg.ovp_irq > 0 &&
>>> > > +						!wled->ovp_irq_disabled) {
>>> > > +					disable_irq_nosync(wled->cfg.ovp_irq);
>>> > > +					wled->ovp_irq_disabled = true;
>>> > > +				}
>>> > > +
>>> > > +				rc = qcom_wled_auto_calibrate(wled);
>>> > > +				if (rc < 0)
>>> > > +					pr_err("Failed auto-calibration rc=%d\n",
>>> > > +						rc);
>>> >
>>> > qcom_wled_auto_calibrate() did already print.
>>> >
>>> Ok. I will remove it in the next series.
>>> > > +				else
>>> > > +					wled->auto_calib_done = true;
>>> > > +
>>> > > +				if (wled->cfg.ovp_irq > 0 &&
>>> > > +						wled->ovp_irq_disabled) {
>>> > > +					enable_irq(wled->cfg.ovp_irq);
>>> > > +					wled->ovp_irq_disabled = false;
>>> > > +				}
>>> > > +				mutex_unlock(&wled->lock);
>>> > > +			}
>>> > > +		}
>>> > > +	}
>>> > > +
>>> >
>>> > Regards,
>>> > Bjorn
>>> > --
>>> > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
>>> > in
>>> > the body of a message to majordomo@vger.kernel.org
>>> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
>> --
>> To unsubscribe from this list: send the line "unsubscribe 
>> linux-arm-msm" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Kiran Gunda April 23, 2018, 11:26 a.m. UTC | #7
On 2018-04-20 21:33, Bjorn Andersson wrote:
> On Thu 19 Apr 22:43 PDT 2018, kgunda@codeaurora.org wrote:
> 
>> On 2018-04-19 21:28, Bjorn Andersson wrote:
>> > On Thu 19 Apr 03:45 PDT 2018, kgunda@codeaurora.org wrote:
>> > > On 2017-12-05 11:10, Bjorn Andersson wrote:
> [..]
>> > > > When is this feature needed?
>> > > >
>> > > This feature is needed if the string configuration is given wrong in
>> > > the DT node by the user.
>> >
>> > DT describes the hardware and for all other nodes it must do so
>> > accurately.
>> >
>> But the user may not be aware of the strings present on the display 
>> panel or
>> may be using the same software on different devices which have 
>> different
>> strings present.
> 
> Swapping display board would still require an update to the DTS, to
> support the new panel. But I think that if you're implementing auto
> string detection, it should be used as the mechanism to detect the
> strings, not something that is run at some later point in time when we
> detect a failure.
> 
Ok. got your point. Yes, the detection is done during the boot-up 
itself,
not later point.
>> > For cases where the hardware supports auto detection of functionality we
>> > remove information from DT and rely on that logic to figure out the
>> > hardware. We do not use it to reconfigure the hardware once we detect an
>> > error. So when auto-detection is enabled it should always be used to
>> > probe the hardware.
>> >
>> The auto string detection is not supported in any qcom hardware and i 
>> don't
>> think there is a plan to introduce in new hardware also.
>> 
> 
> Sorry, I don't follow. Who is using auto string detection then?
> 
I mean to say. The HW doesn't handle the auto-detection completely in 
the HW
without having the S/W intervention, which we are using now.

> Regards,
> Bjorn
> 
>> > Regards,
>> > Bjorn
>> >
>> > > > > Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
>> > > > > ---
>> > > > >  .../bindings/leds/backlight/qcom-spmi-wled.txt     |   5 +
>> > > > >  drivers/video/backlight/qcom-spmi-wled.c           | 304
>> > > > > ++++++++++++++++++++-
>> > > > >  2 files changed, 306 insertions(+), 3 deletions(-)
>> > > > >
>> > > > > diff --git
>> > > > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > > > index d39ee93..f06c0cd 100644
>> > > > > ---
>> > > > > a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > > > +++
>> > > > > b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
>> > > > > @@ -94,6 +94,11 @@ The PMIC is connected to the host processor via
>> > > > > SPMI bus.
>> > > > >  	Definition: Interrupt names associated with the interrupts.
>> > > > >  		    Currently supported interrupts are "sc-irq" and "ovp-irq".
>> > > > >
>> > > > > +- qcom,auto-calibration
>> > > >
>> > > > qcom,auto-string-detect?
>> > > >
>> > > ok. Will address in the next patch.
>> > > > > +	Usage:      optional
>> > > > > +	Value type: <bool>
>> > > > > +	Definition: Enables auto-calibration of the WLED sink configuration.
>> > > > > +
>> > > > >  Example:
>> > > > >
>> > > > >  qcom-wled@d800 {
>> > > > > diff --git a/drivers/video/backlight/qcom-spmi-wled.c
>> > > > > b/drivers/video/backlight/qcom-spmi-wled.c
>> > > > > index 8b2a77a..aee5c56 100644
>> > > > > --- a/drivers/video/backlight/qcom-spmi-wled.c
>> > > > > +++ b/drivers/video/backlight/qcom-spmi-wled.c
>> > > > > @@ -38,11 +38,14 @@
>> > > > >  #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
>> > > > >
>> > > > >  #define QCOM_WLED_CTRL_INT_RT_STS		0x10
>> > > > > +#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
>> > > >
>> > > > The use of BIT() makes this a mask and not a bit number, so if you just
>> > > > drop that you can afford to spell out the "FAULT" like the data sheet
>> > > > does. Perhaps even making it QCOM_WLED_CTRL_OVP_FAULT_STATUS ?
>> > > >
>> > > ok. Will change it in the next series.
>> > > > >
>> > > > >  #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
>> > > > >  #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
>> > > > >  #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
>> > > > >
>> > > > > +#define QCOM_WLED_CTRL_FDBK_OP			0x48
>> > > >
>> > > > This is called WLED_CTRL_FEEDBACK_CONTROL, why the need to make it
>> > > > unreadable?
>> > > >
>> > > Ok. Will address it in next series.
>> > > > > +
>> > > > >  #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
>> > > > >  #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
>> > > > >
>> > > > > @@ -99,6 +102,7 @@ struct qcom_wled_config {
>> > > > >  	int ovp_irq;
>> > > > >  	bool en_cabc;
>> > > > >  	bool ext_pfet_sc_pro_en;
>> > > > > +	bool auto_calib_enabled;
>> > > > >  };
>> > > > >
>> > > > >  struct qcom_wled {
>> > > > > @@ -108,18 +112,25 @@ struct qcom_wled {
>> > > > >  	struct mutex lock;
>> > > > >  	struct qcom_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 sc_count;
>> > > > >  	bool prev_state;
>> > > > >  	bool ovp_irq_disabled;
>> > > > > +	bool auto_calib_done;
>> > > > > +	bool force_mod_disable;
>> > > > >  };
>> > > > >
>> > > > >  static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
>> > > > >  {
>> > > > >  	int rc;
>> > > > >
>> > > > > +	if (wled->force_mod_disable)
>> > > > > +		return 0;
>> > > > > +
>> > > > >  	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
>> > > > >  			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
>> > > > >  			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
>> > > > > @@ -187,12 +198,10 @@ static int qcom_wled_set_brightness(struct
>> > > > > qcom_wled *wled, u16 brightness)
>> > > > >  	v[1] = (brightness >> 8) & 0xf;
>> > > > >
>> > > > >  	for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > > > -		if (string_cfg & BIT(i)) {
>> > > >
>> > > > Why was this check here in the first place, if it's now fine to
>> > > > configure the brightness of all strings?
>> > > >
>> > > > Also, a single-string config of 0b0001 will only set brightness on the
>> > > > first string, while 0b1000 will set brightness on all strings.
>> > > >
>> > > I will correct/remove it next series.
>> > > > >  			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
>> > > > >  					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
>> > > > >  			if (rc < 0)
>> > > > >  				return rc;
>> > > > > -		}
>> > > > >  	}
>> > > > >
>> > > > >  	return 0;
>> > > > > @@ -294,6 +303,262 @@ static irqreturn_t
>> > > > > qcom_wled_sc_irq_handler(int irq, void *_wled)
>> > > > >  	return IRQ_HANDLED;
>> > > > >  }
>> > > > >
>> > > > > +#define AUTO_CALIB_BRIGHTNESS		200
>> > > > > +static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
>> > > > > +{
>> > > > > +	int rc = 0, i;
>> > > > > +	u32 sink_config = 0, int_sts;
>> > > > > +	u8 reg = 0, sink_test = 0, sink_valid = 0;
>> > > > > +	u8 string_cfg = wled->cfg.string_cfg;
>> > > > > +
>> > > > > +	/* read configured sink configuration */
>> > > > > +	rc = regmap_read(wled->regmap, wled->sink_addr +
>> > > > > +			QCOM_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 + QCOM_WLED_CTRL_MOD_ENABLE,
>> > > > > +			QCOM_WLED_CTRL_MOD_EN_MASK, 0);
>> > > > > +	if (rc < 0) {
>> > > > > +		pr_err("Failed to disable WLED module rc=%d\n",	rc);
>> > > > > +		goto failed_calib;
>> > > > > +	}
>> > > >
>> > > > Any error handling beyond this point seems to leave the backlight off
>> > > > (indefinitely?), this does seem like potentially bad user experience...
>> > > Ok. will address in next series.
>> > > >
>> > > > In particular I wonder about the case when this would happen at some
>> > > > random time, minutes, hours, days, months after the device was booted.
>> > > >
>> > > This will happen for every reboot.
>> > > > > +
>> > > > > +	/* set low brightness across all sinks */
>> > > > > +	rc = qcom_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;
>> > > > > +	}
>> > > > > +
>> > > > > +	if (wled->cfg.en_cabc) {
>> > > > > +		for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > > > +			reg = 0;
>> > > > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>> > > > > +					QCOM_WLED_SINK_CABC_REG(i),
>> > > > > +					QCOM_WLED_SINK_CABC_MASK, reg);
>> > > >
>> > > > Just replace "reg" with 0.
>> > > Ok. will address in next series.
>> > > >
>> > > > > +			if (rc < 0)
>> > > > > +				goto failed_calib;
>> > > > > +		}
>> > > > > +	}
>> > > > > +
>> > > > > +	/* disable all sinks */
>> > > > > +	rc = regmap_write(wled->regmap,
>> > > > > +			wled->sink_addr + QCOM_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 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
>> > > >
>> > > > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i);
>> > > >
>> > > Will address in next series.
>> > > > > +
>> > > > > +		/* Enable feedback control */
>> > > > > +		rc = regmap_write(wled->regmap, wled->ctrl_addr +
>> > > > > +				QCOM_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 +
>> > > > > +				QCOM_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 +
>> > > > > +				QCOM_WLED_CTRL_MOD_ENABLE,
>> > > > > +				QCOM_WLED_CTRL_MOD_EN_MASK,
>> > > > > +				QCOM_WLED_CTRL_MOD_EN_MASK);
>> > > >
>> > > > I like the use of regmap_update_bits(..., MASK, MASK) it's clean, but
>> > > > makes me wonder why it's done differently in qcom_wled_module_enable().
>> > > >
>> > > will address it in the next series.
>> > > > > +		if (rc < 0) {
>> > > > > +			pr_err("Failed to enable WLED module rc=%d\n", rc);
>> > > > > +			goto failed_calib;
>> > > > > +		}
>> > > > > +
>> > > > > +		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> > > > > +				QCOM_WLED_SOFT_START_DLY_US + 1000);
>> > > > > +
>> > > > > +		rc = regmap_read(wled->regmap, wled->ctrl_addr +
>> > > > > +				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
>> > > > > +		if (rc < 0) {
>> > > > > +			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
>> > > > > +			goto failed_calib;
>> > > > > +		}
>> > > > > +
>> > > > > +		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
>> > > > > +			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 + QCOM_WLED_CTRL_MOD_ENABLE,
>> > > > > +				QCOM_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_warn("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 + QCOM_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;
>> > > > > +	}
>> > > > > +
>> > > > > +	/* MODULATOR_EN setting for valid sinks */
>> > > >
>> > > > "Enable valid sinks"
>> > > >
>> > > Will address it in the next series.
>> > > > > +	for (i = 0; (string_cfg >> i) != 0; i++) {
>> > > > > +		if (wled->cfg.en_cabc) {
>> > > > > +			reg = QCOM_WLED_SINK_CABC_EN;
>> > > >
>> > > > "reg" is a bad name of a variable holding the "value" to be written to a
>> > > > register.
>> > > >
>> > > Will address it in the next series.
>> > > > > +			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
>> > > > > +					QCOM_WLED_SINK_CABC_REG(i),
>> > > > > +					QCOM_WLED_SINK_CABC_MASK, reg);
>> > > >
>> > > > Again, just inline the value in the function call.
>> > > >
>> > > Will address it in the next series.
>> > > > > +			if (rc < 0)
>> > > > > +				goto failed_calib;
>> > > > > +		}
>> > > > > +
>> > > > > +		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
>> > > >
>> > > > BIT(QCOM_WLED_SINK_CURR_SINK_SHFT + i)
>> > > >
>> > > Will address it in the next series.
>> > > > > +			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
>> > > > > +		else
>> > > > > +			reg = 0x0; /* disable modulator_en for unused sink */
>> > > > > +
>> > > > > +		rc = regmap_write(wled->regmap, wled->sink_addr +
>> > > > > +				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
>> > > > > +		if (rc < 0) {
>> > > > > +			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
>> > > > > +			goto failed_calib;
>> > > > > +		}
>> > > > > +	}
>> > > > > +
>> > > > > +	/* restore the feedback setting */
>> > > > > +	rc = regmap_write(wled->regmap,
>> > > > > +			wled->ctrl_addr + QCOM_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 = qcom_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 + QCOM_WLED_CTRL_MOD_ENABLE,
>> > > > > +			QCOM_WLED_CTRL_MOD_EN_MASK,
>> > > > > +			QCOM_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 */
>> > > >
>> > > > What comes after this that you want to delay?
>> > > >
>> > > > This delay is used to make the OVP IRQ not fire immediately, but as
>> > > > we've now successfully executed the string auto detection run we're
>> > > > never going to do anything in the OVP handler.
>> > > >
>> > > Will correct it in the next series.
>> > > > > +	usleep_range(QCOM_WLED_SOFT_START_DLY_US,
>> > > > > +			QCOM_WLED_SOFT_START_DLY_US + 1000);
>> > > > > +
>> > > > > +failed_calib:
>> > > > > +	return rc;
>> > > > > +}
>> > > > > +
>> > > > > +#define WLED_AUTO_CAL_OVP_COUNT		5
>> > > > > +#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
>> > > > > +static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
>> > > > > +{
>> > > > > +	s64 elapsed_time_us;
>> > > > > +
>> > > > > +	/*
>> > > > > +	 * Check if the OVP fault was an occasional one
>> > > > > +	 * or if its firing continuously, the latter qualifies
>> > > > > +	 * for an auto-calibration check.
>> > > > > +	 */
>> > > > > +	if (!wled->auto_calibration_ovp_count) {
>> > > > > +		wled->start_ovp_fault_time = ktime_get();
>> > > > > +		wled->auto_calibration_ovp_count++;
>> > > > > +	} else {
>> > > > > +		elapsed_time_us = ktime_us_delta(ktime_get(),
>> > > > > +				wled->start_ovp_fault_time);
>> > > > > +		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
>> > > > > +			wled->auto_calibration_ovp_count = 0;
>> > > > > +		else
>> > > > > +			wled->auto_calibration_ovp_count++;
>> > > > > +
>> > > > > +		if (wled->auto_calibration_ovp_count >=
>> > > > > +				WLED_AUTO_CAL_OVP_COUNT) {
>> > > > > +			wled->auto_calibration_ovp_count = 0;
>> > > > > +			return true;
>> > > > > +		}
>> > > > > +	}
>> > > > > +
>> > > > > +	return false;
>> > > > > +}
>> > > > > +
>> > > > > +static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
>> > > >
>> > > > I presume this function is expected to detect if there is a invalid
>> > > > configuration at boot and try to figure out which strings are actually
>> > > > wired.
>> > > >
>> > > Correct.
>> > > > > +{
>> > > > > +	int rc;
>> > > > > +	u32 fault_status = 0, rt_status = 0;
>> > > > > +
>> > > > > +	if (!wled->cfg.auto_calib_enabled)
>> > > > > +		return 0;
>> > > > > +
>> > > > > +	rc = regmap_read(wled->regmap,
>> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
>> > > > > +			&rt_status);
>> > > > > +	if (rc < 0)
>> > > > > +		pr_err("Failed to read RT status rc=%d\n", rc);
>> > > > > +
>> > > > > +	rc = regmap_read(wled->regmap,
>> > > > > +			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
>> > > > > +			&fault_status);
>> > > > > +	if (rc < 0)
>> > > > > +		pr_err("Failed to read fault status rc=%d\n", rc);
>> > > > > +
>> > > > > +	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
>> > > > > +			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
>> > > >
>> > > > You should be able to drop the extra () around these.
>> > > >
>> > > Ok. Will remove it in the next series.
>> > > > > +		mutex_lock(&wled->lock);
>> > > > > +		rc = qcom_wled_auto_calibrate(wled);
>> > > > > +		if (rc < 0)
>> > > > > +			pr_err("Failed auto-calibration rc=%d\n", rc);
>> > > >
>> > > > qcom_wled_auto_calibrate() did already print, no need to repeat this.
>> > > >
>> > > Ok. Will remove this in the next series.
>> > > > > +		else
>> > > > > +			wled->auto_calib_done = true;
>> > > > > +		mutex_unlock(&wled->lock);
>> > > > > +	}
>> > > > > +
>> > > > > +	return rc;
>> > > > > +}
>> > > > > +
>> > > > >  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
>> > > > >  {
>> > > > >  	struct qcom_wled *wled = _wled;
>> > > > > @@ -319,6 +584,33 @@ static irqreturn_t
>> > > > > qcom_wled_ovp_irq_handler(int irq, void *_wled)
>> > > > >  		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
>> > > > >  			int_sts, fault_sts);
>> > > > >
>> > > > > +	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
>> > > > > +		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
>> > > > > +			if (qcom_wled_auto_cal_required(wled)) {
>> > > >
>> > > > So this will be invoked only once, iff we didn't boot with a faulty
>> > > > configuration in which case the qcom_wled_auto_calibrate_at_init() has
>> > > > already done this step and set auto_calib_done.
>> > > >
>> > > >
>> > > > Which also would mean that all logic in this handler, beyond the
>> > > > printouts, are only ever going to be executed zero or one times.
>> > > >
>> > > > Why don't you just do auto-detection during probe (iff the flag is set
>> > > > in DT) and you can remove all this extra logic?
>> > > >
>> > > I think we have seen a issue, where the OVP interrupt is not getting
>> > > set
>> > > some times during the execution of this function at boot. In that
>> > > case the
>> > > auto calibration is
>> > > done bit later. That's why this code is added.
>> > > > > +				mutex_lock(&wled->lock);
>> > > > > +				if (wled->cfg.ovp_irq > 0 &&
>> > > > > +						!wled->ovp_irq_disabled) {
>> > > > > +					disable_irq_nosync(wled->cfg.ovp_irq);
>> > > > > +					wled->ovp_irq_disabled = true;
>> > > > > +				}
>> > > > > +
>> > > > > +				rc = qcom_wled_auto_calibrate(wled);
>> > > > > +				if (rc < 0)
>> > > > > +					pr_err("Failed auto-calibration rc=%d\n",
>> > > > > +						rc);
>> > > >
>> > > > qcom_wled_auto_calibrate() did already print.
>> > > >
>> > > Ok. I will remove it in the next series.
>> > > > > +				else
>> > > > > +					wled->auto_calib_done = true;
>> > > > > +
>> > > > > +				if (wled->cfg.ovp_irq > 0 &&
>> > > > > +						wled->ovp_irq_disabled) {
>> > > > > +					enable_irq(wled->cfg.ovp_irq);
>> > > > > +					wled->ovp_irq_disabled = false;
>> > > > > +				}
>> > > > > +				mutex_unlock(&wled->lock);
>> > > > > +			}
>> > > > > +		}
>> > > > > +	}
>> > > > > +
>> > > >
>> > > > Regards,
>> > > > Bjorn
>> > > > --
>> > > > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
>> > > > in
>> > > > the body of a message to majordomo@vger.kernel.org
>> > > > More majordomo info at  http://vger.kernel.org/majordomo-info.html
>> > --
>> > To unsubscribe from this list: send the line "unsubscribe linux-arm-msm"
>> > in
>> > the body of a message to majordomo@vger.kernel.org
>> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> --
> To unsubscribe from this list: send the line "unsubscribe 
> linux-arm-msm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-fbdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
index d39ee93..f06c0cd 100644
--- a/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
+++ b/Documentation/devicetree/bindings/leds/backlight/qcom-spmi-wled.txt
@@ -94,6 +94,11 @@  The PMIC is connected to the host processor via SPMI bus.
 	Definition: Interrupt names associated with the interrupts.
 		    Currently supported interrupts are "sc-irq" and "ovp-irq".
 
+- qcom,auto-calibration
+	Usage:      optional
+	Value type: <bool>
+	Definition: Enables auto-calibration of the WLED sink configuration.
+
 Example:
 
 qcom-wled@d800 {
diff --git a/drivers/video/backlight/qcom-spmi-wled.c b/drivers/video/backlight/qcom-spmi-wled.c
index 8b2a77a..aee5c56 100644
--- a/drivers/video/backlight/qcom-spmi-wled.c
+++ b/drivers/video/backlight/qcom-spmi-wled.c
@@ -38,11 +38,14 @@ 
 #define  QCOM_WLED_CTRL_SC_FAULT_BIT		BIT(2)
 
 #define QCOM_WLED_CTRL_INT_RT_STS		0x10
+#define  QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT	BIT(1)
 
 #define QCOM_WLED_CTRL_MOD_ENABLE		0x46
 #define  QCOM_WLED_CTRL_MOD_EN_MASK		BIT(7)
 #define  QCOM_WLED_CTRL_MODULE_EN_SHIFT		7
 
+#define QCOM_WLED_CTRL_FDBK_OP			0x48
+
 #define QCOM_WLED_CTRL_SWITCH_FREQ		0x4c
 #define  QCOM_WLED_CTRL_SWITCH_FREQ_MASK	GENMASK(3, 0)
 
@@ -99,6 +102,7 @@  struct qcom_wled_config {
 	int ovp_irq;
 	bool en_cabc;
 	bool ext_pfet_sc_pro_en;
+	bool auto_calib_enabled;
 };
 
 struct qcom_wled {
@@ -108,18 +112,25 @@  struct qcom_wled {
 	struct mutex lock;
 	struct qcom_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 sc_count;
 	bool prev_state;
 	bool ovp_irq_disabled;
+	bool auto_calib_done;
+	bool force_mod_disable;
 };
 
 static int qcom_wled_module_enable(struct qcom_wled *wled, int val)
 {
 	int rc;
 
+	if (wled->force_mod_disable)
+		return 0;
+
 	rc = regmap_update_bits(wled->regmap, wled->ctrl_addr +
 			QCOM_WLED_CTRL_MOD_ENABLE, QCOM_WLED_CTRL_MOD_EN_MASK,
 			val << QCOM_WLED_CTRL_MODULE_EN_SHIFT);
@@ -187,12 +198,10 @@  static int qcom_wled_set_brightness(struct qcom_wled *wled, u16 brightness)
 	v[1] = (brightness >> 8) & 0xf;
 
 	for (i = 0; (string_cfg >> i) != 0; i++) {
-		if (string_cfg & BIT(i)) {
 			rc = regmap_bulk_write(wled->regmap, wled->sink_addr +
 					QCOM_WLED_SINK_BRIGHT_LSB_REG(i), v, 2);
 			if (rc < 0)
 				return rc;
-		}
 	}
 
 	return 0;
@@ -294,6 +303,262 @@  static irqreturn_t qcom_wled_sc_irq_handler(int irq, void *_wled)
 	return IRQ_HANDLED;
 }
 
+#define AUTO_CALIB_BRIGHTNESS		200
+static int qcom_wled_auto_calibrate(struct qcom_wled *wled)
+{
+	int rc = 0, i;
+	u32 sink_config = 0, int_sts;
+	u8 reg = 0, sink_test = 0, sink_valid = 0;
+	u8 string_cfg = wled->cfg.string_cfg;
+
+	/* read configured sink configuration */
+	rc = regmap_read(wled->regmap, wled->sink_addr +
+			QCOM_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 + QCOM_WLED_CTRL_MOD_ENABLE,
+			QCOM_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 = qcom_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;
+	}
+
+	if (wled->cfg.en_cabc) {
+		for (i = 0; (string_cfg >> i) != 0; i++) {
+			reg = 0;
+			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
+					QCOM_WLED_SINK_CABC_REG(i),
+					QCOM_WLED_SINK_CABC_MASK, reg);
+			if (rc < 0)
+				goto failed_calib;
+		}
+	}
+
+	/* disable all sinks */
+	rc = regmap_write(wled->regmap,
+			wled->sink_addr + QCOM_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 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i);
+
+		/* Enable feedback control */
+		rc = regmap_write(wled->regmap, wled->ctrl_addr +
+				QCOM_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 +
+				QCOM_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 +
+				QCOM_WLED_CTRL_MOD_ENABLE,
+				QCOM_WLED_CTRL_MOD_EN_MASK,
+				QCOM_WLED_CTRL_MOD_EN_MASK);
+		if (rc < 0) {
+			pr_err("Failed to enable WLED module rc=%d\n", rc);
+			goto failed_calib;
+		}
+
+		usleep_range(QCOM_WLED_SOFT_START_DLY_US,
+				QCOM_WLED_SOFT_START_DLY_US + 1000);
+
+		rc = regmap_read(wled->regmap, wled->ctrl_addr +
+				QCOM_WLED_CTRL_INT_RT_STS, &int_sts);
+		if (rc < 0) {
+			pr_err("Error in reading WLED_INT_RT_STS rc=%d\n", rc);
+			goto failed_calib;
+		}
+
+		if (int_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT)
+			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 + QCOM_WLED_CTRL_MOD_ENABLE,
+				QCOM_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_warn("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 + QCOM_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;
+	}
+
+	/* MODULATOR_EN setting for valid sinks */
+	for (i = 0; (string_cfg >> i) != 0; i++) {
+		if (wled->cfg.en_cabc) {
+			reg = QCOM_WLED_SINK_CABC_EN;
+			rc = regmap_update_bits(wled->regmap, wled->sink_addr +
+					QCOM_WLED_SINK_CABC_REG(i),
+					QCOM_WLED_SINK_CABC_MASK, reg);
+			if (rc < 0)
+				goto failed_calib;
+		}
+
+		if (sink_config & (1 << (QCOM_WLED_SINK_CURR_SINK_SHFT + i)))
+			reg = QCOM_WLED_SINK_REG_STR_MOD_EN;
+		else
+			reg = 0x0; /* disable modulator_en for unused sink */
+
+		rc = regmap_write(wled->regmap, wled->sink_addr +
+				QCOM_WLED_SINK_MOD_EN_REG(i), reg);
+		if (rc < 0) {
+			pr_err("Failed to configure MODULATOR_EN rc=%d\n", rc);
+			goto failed_calib;
+		}
+	}
+
+	/* restore the feedback setting */
+	rc = regmap_write(wled->regmap,
+			wled->ctrl_addr + QCOM_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 = qcom_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 + QCOM_WLED_CTRL_MOD_ENABLE,
+			QCOM_WLED_CTRL_MOD_EN_MASK,
+			QCOM_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(QCOM_WLED_SOFT_START_DLY_US,
+			QCOM_WLED_SOFT_START_DLY_US + 1000);
+
+failed_calib:
+	return rc;
+}
+
+#define WLED_AUTO_CAL_OVP_COUNT		5
+#define WLED_AUTO_CAL_CNT_DLY_US	1000000	/* 1 second */
+static bool qcom_wled_auto_cal_required(struct qcom_wled *wled)
+{
+	s64 elapsed_time_us;
+
+	/*
+	 * Check if the OVP fault was an occasional one
+	 * or if its firing continuously, the latter qualifies
+	 * for an auto-calibration check.
+	 */
+	if (!wled->auto_calibration_ovp_count) {
+		wled->start_ovp_fault_time = ktime_get();
+		wled->auto_calibration_ovp_count++;
+	} else {
+		elapsed_time_us = ktime_us_delta(ktime_get(),
+				wled->start_ovp_fault_time);
+		if (elapsed_time_us > WLED_AUTO_CAL_CNT_DLY_US)
+			wled->auto_calibration_ovp_count = 0;
+		else
+			wled->auto_calibration_ovp_count++;
+
+		if (wled->auto_calibration_ovp_count >=
+				WLED_AUTO_CAL_OVP_COUNT) {
+			wled->auto_calibration_ovp_count = 0;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+static int qcom_wled_auto_calibrate_at_init(struct qcom_wled *wled)
+{
+	int rc;
+	u32 fault_status = 0, rt_status = 0;
+
+	if (!wled->cfg.auto_calib_enabled)
+		return 0;
+
+	rc = regmap_read(wled->regmap,
+			wled->ctrl_addr + QCOM_WLED_CTRL_INT_RT_STS,
+			&rt_status);
+	if (rc < 0)
+		pr_err("Failed to read RT status rc=%d\n", rc);
+
+	rc = regmap_read(wled->regmap,
+			wled->ctrl_addr + QCOM_WLED_CTRL_FAULT_STATUS,
+			&fault_status);
+	if (rc < 0)
+		pr_err("Failed to read fault status rc=%d\n", rc);
+
+	if ((rt_status & QCOM_WLED_CTRL_OVP_FLT_RT_STS_BIT) ||
+			(fault_status & QCOM_WLED_CTRL_OVP_FAULT_BIT)) {
+		mutex_lock(&wled->lock);
+		rc = qcom_wled_auto_calibrate(wled);
+		if (rc < 0)
+			pr_err("Failed auto-calibration rc=%d\n", rc);
+		else
+			wled->auto_calib_done = true;
+		mutex_unlock(&wled->lock);
+	}
+
+	return rc;
+}
+
 static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
 {
 	struct qcom_wled *wled = _wled;
@@ -319,6 +584,33 @@  static irqreturn_t qcom_wled_ovp_irq_handler(int irq, void *_wled)
 		pr_err("WLED OVP fault detected, int_sts=%x fault_sts= %x\n",
 			int_sts, fault_sts);
 
+	if (fault_sts & QCOM_WLED_CTRL_OVP_FAULT_BIT) {
+		if (wled->cfg.auto_calib_enabled && !wled->auto_calib_done) {
+			if (qcom_wled_auto_cal_required(wled)) {
+				mutex_lock(&wled->lock);
+				if (wled->cfg.ovp_irq > 0 &&
+						!wled->ovp_irq_disabled) {
+					disable_irq_nosync(wled->cfg.ovp_irq);
+					wled->ovp_irq_disabled = true;
+				}
+
+				rc = qcom_wled_auto_calibrate(wled);
+				if (rc < 0)
+					pr_err("Failed auto-calibration rc=%d\n",
+						rc);
+				else
+					wled->auto_calib_done = true;
+
+				if (wled->cfg.ovp_irq > 0 &&
+						wled->ovp_irq_disabled) {
+					enable_irq(wled->cfg.ovp_irq);
+					wled->ovp_irq_disabled = false;
+				}
+				mutex_unlock(&wled->lock);
+			}
+		}
+	}
+
 	return IRQ_HANDLED;
 }
 
@@ -394,6 +686,10 @@  static int qcom_wled_setup(struct qcom_wled *wled)
 		return rc;
 	}
 
+	rc = qcom_wled_auto_calibrate_at_init(wled);
+	if (rc < 0)
+		pr_err("Failed to auto-calibrate at init rc=%d\n", rc);
+
 	if (sc_irq >= 0) {
 		rc = devm_request_threaded_irq(&wled->pdev->dev, sc_irq,
 				NULL, qcom_wled_sc_irq_handler, IRQF_ONESHOT,
@@ -432,7 +728,7 @@  static int qcom_wled_setup(struct qcom_wled *wled)
 				NULL, qcom_wled_ovp_irq_handler, IRQF_ONESHOT,
 				"qcom_wled_ovp_irq", wled);
 		if (rc < 0) {
-			dev_err(&wled->pdev->dev, "Unable to request ovp(%d) IRQ(err:%d)\n",
+			pr_err("Unable to request ovp(%d) IRQ(err:%d)\n",
 				ovp_irq, rc);
 			return rc;
 		}
@@ -457,6 +753,7 @@  static int qcom_wled_setup(struct qcom_wled *wled)
 	.string_cfg = 0xf,
 	.en_cabc = 0,
 	.ext_pfet_sc_pro_en = 1,
+	.auto_calib_enabled = 1,
 };
 
 struct qcom_wled_var_cfg {
@@ -563,6 +860,7 @@  static int qcom_wled_configure(struct qcom_wled *wled, struct device *dev)
 	} 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);