diff mbox series

[RESEND,v3] leds: flash: leds-qcom-flash: limit LED current based on thermal condition

Message ID 20240705-qcom_flash_thermal_derating-v3-1-8e2e2783e3a6@quicinc.com
State New
Headers show
Series [RESEND,v3] leds: flash: leds-qcom-flash: limit LED current based on thermal condition | expand

Commit Message

Fenglin Wu via B4 Relay July 5, 2024, 7:55 a.m. UTC
From: Fenglin Wu <quic_fenglinw@quicinc.com>

The flash module has status bits to indicate different thermal
conditions which are called as OTSTx. For each OTSTx status,
there is a recommended total flash current for all channels to
prevent the flash module entering into higher thermal level.
For example, the total flash current should be limited to 1000mA/500mA
respectively when the HW reaches the OTST1/OTST2 thermal level.

Signed-off-by: Fenglin Wu <quic_fenglinw@quicinc.com>
---
Changes in v3:
- Fix coding style issues to address review comments in v2.
- Link to v2: https://lore.kernel.org/r/20240513-qcom_flash_thermal_derating-v2-1-e41a07d0eb83@quicinc.com

Changes in v2:
- Update thermal threshold level 2 register definition for mvflash_4ch_regs.
    Mvflash_4ch module thermal threshold level 2 configuration register
    offset is 0x78, not succeeding from thermal threshold level 1 register 0x7a.
    Hence it is not appropriate to use REG_FIELD_ID to define thermal threshold
    register fileds like mvflash_3ch. Update to use REG_FIELD instead.
- Link to v1: https://lore.kernel.org/r/20240509-qcom_flash_thermal_derating-v1-1-1d5e68e5d71c@quicinc.com
---
 drivers/leds/flash/leds-qcom-flash.c | 163 ++++++++++++++++++++++++++++++++++-
 1 file changed, 162 insertions(+), 1 deletion(-)


---
base-commit: ca66b10a11da3c445c9c0ca1184f549bbe9061f2
change-id: 20240507-qcom_flash_thermal_derating-260b1f3c757c

Best regards,

Comments

Luca Weiss July 5, 2024, 2:23 p.m. UTC | #1
On Fri Jul 5, 2024 at 9:55 AM CEST, Fenglin Wu via B4 Relay wrote:
> From: Fenglin Wu <quic_fenglinw@quicinc.com>
>
> The flash module has status bits to indicate different thermal
> conditions which are called as OTSTx. For each OTSTx status,
> there is a recommended total flash current for all channels to
> prevent the flash module entering into higher thermal level.
> For example, the total flash current should be limited to 1000mA/500mA
> respectively when the HW reaches the OTST1/OTST2 thermal level.

Hi Fenglin,

Only semi-related to this patch, but I wanted to ask.

Since most phones with a flash also have a thermistor for the flash led,
is there any plan to add support to be able to declare the flash led to
be a "cooling-device" for the relevant thermal zone? That way from a
Linux thermal API standpoint when the zone gets too hot that it can ask
the driver to throttle the brightness or turn the LED off completely.

Right now the only action the kernel can take is with type 'critical' to
just kill the entire system to mitigate the thermal situation.

Regards
Luca

>
> Signed-off-by: Fenglin Wu <quic_fenglinw@quicinc.com>
> ---
> Changes in v3:
> - Fix coding style issues to address review comments in v2.
> - Link to v2: https://lore.kernel.org/r/20240513-qcom_flash_thermal_derating-v2-1-e41a07d0eb83@quicinc.com
>
> Changes in v2:
> - Update thermal threshold level 2 register definition for mvflash_4ch_regs.
>     Mvflash_4ch module thermal threshold level 2 configuration register
>     offset is 0x78, not succeeding from thermal threshold level 1 register 0x7a.
>     Hence it is not appropriate to use REG_FIELD_ID to define thermal threshold
>     register fileds like mvflash_3ch. Update to use REG_FIELD instead.
> - Link to v1: https://lore.kernel.org/r/20240509-qcom_flash_thermal_derating-v1-1-1d5e68e5d71c@quicinc.com
> ---
>  drivers/leds/flash/leds-qcom-flash.c | 163 ++++++++++++++++++++++++++++++++++-
>  1 file changed, 162 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/leds/flash/leds-qcom-flash.c b/drivers/leds/flash/leds-qcom-flash.c
> index 7c99a3039171..aa22686fafe0 100644
> --- a/drivers/leds/flash/leds-qcom-flash.c
> +++ b/drivers/leds/flash/leds-qcom-flash.c
> @@ -1,6 +1,6 @@
>  // SPDX-License-Identifier: GPL-2.0-only
>  /*
> - * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
> + * Copyright (c) 2022, 2024 Qualcomm Innovation Center, Inc. All rights reserved.
>   */
>  
>  #include <linux/bitfield.h>
> @@ -14,6 +14,9 @@
>  #include <media/v4l2-flash-led-class.h>
>  
>  /* registers definitions */
> +#define FLASH_REVISION_REG		0x00
> +#define FLASH_4CH_REVISION_V0P1		0x01
> +
>  #define FLASH_TYPE_REG			0x04
>  #define FLASH_TYPE_VAL			0x18
>  
> @@ -73,6 +76,16 @@
>  
>  #define UA_PER_MA			1000
>  
> +/* thermal threshold constants */
> +#define OTST_3CH_MIN_VAL		3
> +#define OTST1_4CH_MIN_VAL		0
> +#define OTST1_4CH_V0P1_MIN_VAL		3
> +#define OTST2_4CH_MIN_VAL		0
> +
> +#define OTST1_MAX_CURRENT_MA		1000
> +#define OTST2_MAX_CURRENT_MA		500
> +#define OTST3_MAX_CURRENT_MA		200
> +
>  enum hw_type {
>  	QCOM_MVFLASH_3CH,
>  	QCOM_MVFLASH_4CH,
> @@ -98,6 +111,9 @@ enum {
>  	REG_IRESOLUTION,
>  	REG_CHAN_STROBE,
>  	REG_CHAN_EN,
> +	REG_THERM_THRSH1,
> +	REG_THERM_THRSH2,
> +	REG_THERM_THRSH3,
>  	REG_MAX_COUNT,
>  };
>  
> @@ -111,6 +127,9 @@ static struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = {
>  	REG_FIELD(0x47, 0, 5),                  /* iresolution	*/
>  	REG_FIELD_ID(0x49, 0, 2, 3, 1),         /* chan_strobe	*/
>  	REG_FIELD(0x4c, 0, 2),                  /* chan_en	*/
> +	REG_FIELD(0x56, 0, 2),			/* therm_thrsh1 */
> +	REG_FIELD(0x57, 0, 2),			/* therm_thrsh2 */
> +	REG_FIELD(0x58, 0, 2),			/* therm_thrsh3 */
>  };
>  
>  static struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = {
> @@ -123,6 +142,8 @@ static struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = {
>  	REG_FIELD(0x49, 0, 3),			/* iresolution	*/
>  	REG_FIELD_ID(0x4a, 0, 6, 4, 1),		/* chan_strobe	*/
>  	REG_FIELD(0x4e, 0, 3),			/* chan_en	*/
> +	REG_FIELD(0x7a, 0, 2),			/* therm_thrsh1 */
> +	REG_FIELD(0x78, 0, 2),			/* therm_thrsh2 */
>  };
>  
>  struct qcom_flash_data {
> @@ -130,9 +151,11 @@ struct qcom_flash_data {
>  	struct regmap_field     *r_fields[REG_MAX_COUNT];
>  	struct mutex		lock;
>  	enum hw_type		hw_type;
> +	u32			total_ma;
>  	u8			leds_count;
>  	u8			max_channels;
>  	u8			chan_en_bits;
> +	u8			revision;
>  };
>  
>  struct qcom_flash_led {
> @@ -143,6 +166,7 @@ struct qcom_flash_led {
>  	u32				max_timeout_ms;
>  	u32				flash_current_ma;
>  	u32				flash_timeout_ms;
> +	u32				current_in_use_ma;
>  	u8				*chan_id;
>  	u8				chan_count;
>  	bool				enabled;
> @@ -172,6 +196,127 @@ static int set_flash_module_en(struct qcom_flash_led *led, bool en)
>  	return rc;
>  }
>  
> +static int update_allowed_flash_current(struct qcom_flash_led *led, u32 *current_ma, bool strobe)
> +{
> +	struct qcom_flash_data *flash_data = led->flash_data;
> +	u32 therm_ma, avail_ma, thrsh[3], min_thrsh, sts;
> +	int rc = 0;
> +
> +	mutex_lock(&flash_data->lock);
> +	/*
> +	 * Put previously allocated current into allowed budget in either of these two cases:
> +	 * 1) LED is disabled;
> +	 * 2) LED is enabled repeatedly
> +	 */
> +	if (!strobe || led->current_in_use_ma != 0) {
> +		if (flash_data->total_ma >= led->current_in_use_ma)
> +			flash_data->total_ma -= led->current_in_use_ma;
> +		else
> +			flash_data->total_ma = 0;
> +
> +		led->current_in_use_ma = 0;
> +		if (!strobe)
> +			goto unlock;
> +	}
> +
> +	/*
> +	 * Cache the default thermal threshold settings, and set them to the lowest levels before
> +	 * reading over-temp real time status. If over-temp has been triggered at the lowest
> +	 * threshold, it's very likely that it would be triggered at a higher (default) threshold
> +	 * when more flash current is requested. Prevent device from triggering over-temp condition
> +	 * by limiting the flash current for the new request.
> +	 */
> +	rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH1], &thrsh[0]);
> +	if (rc < 0)
> +		goto unlock;
> +
> +	rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH2], &thrsh[1]);
> +	if (rc < 0)
> +		goto unlock;
> +
> +	if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
> +		rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH3], &thrsh[2]);
> +		if (rc < 0)
> +			goto unlock;
> +	}
> +
> +	min_thrsh = OTST_3CH_MIN_VAL;
> +	if (flash_data->hw_type == QCOM_MVFLASH_4CH)
> +		min_thrsh = (flash_data->revision == FLASH_4CH_REVISION_V0P1) ?
> +			OTST1_4CH_V0P1_MIN_VAL : OTST1_4CH_MIN_VAL;
> +
> +	rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH1], min_thrsh);
> +	if (rc < 0)
> +		goto unlock;
> +
> +	if (flash_data->hw_type == QCOM_MVFLASH_4CH)
> +		min_thrsh = OTST2_4CH_MIN_VAL;
> +
> +	/*
> +	 * The default thermal threshold settings have been updated hence
> +	 * restore them if any fault happens starting from here.
> +	 */
> +	rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH2], min_thrsh);
> +	if (rc < 0)
> +		goto restore;
> +
> +	if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
> +		rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH3], min_thrsh);
> +		if (rc < 0)
> +			goto restore;
> +	}
> +
> +	/* Read thermal level status to get corresponding derating flash current */
> +	rc = regmap_field_read(flash_data->r_fields[REG_STATUS2], &sts);
> +	if (rc)
> +		goto restore;
> +
> +	therm_ma = FLASH_TOTAL_CURRENT_MAX_UA / 1000;
> +	if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
> +		if (sts & FLASH_STS_3CH_OTST3)
> +			therm_ma = OTST3_MAX_CURRENT_MA;
> +		else if (sts & FLASH_STS_3CH_OTST2)
> +			therm_ma = OTST2_MAX_CURRENT_MA;
> +		else if (sts & FLASH_STS_3CH_OTST1)
> +			therm_ma = OTST1_MAX_CURRENT_MA;
> +	} else {
> +		if (sts & FLASH_STS_4CH_OTST2)
> +			therm_ma = OTST2_MAX_CURRENT_MA;
> +		else if (sts & FLASH_STS_4CH_OTST1)
> +			therm_ma = OTST1_MAX_CURRENT_MA;
> +	}
> +
> +	/* Calculate the allowed flash current for the request */
> +	if (therm_ma <= flash_data->total_ma)
> +		avail_ma = 0;
> +	else
> +		avail_ma = therm_ma - flash_data->total_ma;
> +
> +	*current_ma = min_t(u32, *current_ma, avail_ma);
> +	led->current_in_use_ma = *current_ma;
> +	flash_data->total_ma += led->current_in_use_ma;
> +
> +	dev_dbg(led->flash.led_cdev.dev, "allowed flash current: %dmA, total current: %dmA\n",
> +					led->current_in_use_ma, flash_data->total_ma);
> +
> +restore:
> +	/* Restore to default thermal threshold settings */
> +	rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH1], thrsh[0]);
> +	if (rc < 0)
> +		goto unlock;
> +
> +	rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH2], thrsh[1]);
> +	if (rc < 0)
> +		goto unlock;
> +
> +	if (flash_data->hw_type == QCOM_MVFLASH_3CH)
> +		rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH3], thrsh[2]);
> +
> +unlock:
> +	mutex_unlock(&flash_data->lock);
> +	return rc;
> +}
> +
>  static int set_flash_current(struct qcom_flash_led *led, u32 current_ma, enum led_mode mode)
>  {
>  	struct qcom_flash_data *flash_data = led->flash_data;
> @@ -313,6 +458,10 @@ static int qcom_flash_strobe_set(struct led_classdev_flash *fled_cdev, bool stat
>  	if (rc)
>  		return rc;
>  
> +	rc = update_allowed_flash_current(led, &led->flash_current_ma, state);
> +	if (rc < 0)
> +		return rc;
> +
>  	rc = set_flash_current(led, led->flash_current_ma, FLASH_MODE);
>  	if (rc)
>  		return rc;
> @@ -429,6 +578,10 @@ static int qcom_flash_led_brightness_set(struct led_classdev *led_cdev,
>  	if (rc)
>  		return rc;
>  
> +	rc = update_allowed_flash_current(led, &current_ma, enable);
> +	if (rc < 0)
> +		return rc;
> +
>  	rc = set_flash_current(led, current_ma, TORCH_MODE);
>  	if (rc)
>  		return rc;
> @@ -703,6 +856,14 @@ static int qcom_flash_led_probe(struct platform_device *pdev)
>  		flash_data->hw_type = QCOM_MVFLASH_4CH;
>  		flash_data->max_channels = 4;
>  		regs = mvflash_4ch_regs;
> +
> +		rc = regmap_read(regmap, reg_base + FLASH_REVISION_REG, &val);
> +		if (rc < 0) {
> +			dev_err(dev, "Failed to read flash LED module revision, rc=%d\n", rc);
> +			return rc;
> +		}
> +
> +		flash_data->revision = val;
>  	} else {
>  		dev_err(dev, "flash LED subtype %#x is not yet supported\n", val);
>  		return -ENODEV;
>
> ---
> base-commit: ca66b10a11da3c445c9c0ca1184f549bbe9061f2
> change-id: 20240507-qcom_flash_thermal_derating-260b1f3c757c
>
> Best regards,
Fenglin Wu July 8, 2024, 2:59 a.m. UTC | #2
On 7/5/2024 10:23 PM, Luca Weiss wrote:
> On Fri Jul 5, 2024 at 9:55 AM CEST, Fenglin Wu via B4 Relay wrote:
>> From: Fenglin Wu <quic_fenglinw@quicinc.com>
>>
>> The flash module has status bits to indicate different thermal
>> conditions which are called as OTSTx. For each OTSTx status,
>> there is a recommended total flash current for all channels to
>> prevent the flash module entering into higher thermal level.
>> For example, the total flash current should be limited to 1000mA/500mA
>> respectively when the HW reaches the OTST1/OTST2 thermal level.
> 
> Hi Fenglin,
> 
> Only semi-related to this patch, but I wanted to ask.
> 
> Since most phones with a flash also have a thermistor for the flash led,
> is there any plan to add support to be able to declare the flash led to
> be a "cooling-device" for the relevant thermal zone? That way from a
> Linux thermal API standpoint when the zone gets too hot that it can ask
> the driver to throttle the brightness or turn the LED off completely.
> 
> Right now the only action the kernel can take is with type 'critical' to
> just kill the entire system to mitigate the thermal situation.
> 
> Regards
> Luca
> 

Hi Luca,

This change provides the ability to throttle flash current based on the 
thermal status sensed by the temperature sensor inside the flash module 
HW , it doesn't need to register anything in Linux thermal framework.

For the case that you mentioned, when an external thermistor is 
installed nearby the flash LED component and normally the ADC_TM driver 
registers a thermal_zone device with it, I agree that having the flash 
LED driver providing a thermal_cooling device so that any cooling 
mapping policy could be defined between the thermal sensor and the 
cooling device would be a good option for better system level thermal 
control. I would assume that this could be added in flash LED framework 
driver instead of the client drivers considering this should be a common 
request because of the big thermal dissipation of flash LED?

Fenglin
>>
>> ---
>> base-commit: ca66b10a11da3c445c9c0ca1184f549bbe9061f2
>> change-id: 20240507-qcom_flash_thermal_derating-260b1f3c757c
>>
>> Best regards,
>
Luca Weiss July 8, 2024, 7:39 a.m. UTC | #3
On Mon Jul 8, 2024 at 4:59 AM CEST, Fenglin Wu wrote:
>
>
> On 7/5/2024 10:23 PM, Luca Weiss wrote:
> > On Fri Jul 5, 2024 at 9:55 AM CEST, Fenglin Wu via B4 Relay wrote:
> >> From: Fenglin Wu <quic_fenglinw@quicinc.com>
> >>
> >> The flash module has status bits to indicate different thermal
> >> conditions which are called as OTSTx. For each OTSTx status,
> >> there is a recommended total flash current for all channels to
> >> prevent the flash module entering into higher thermal level.
> >> For example, the total flash current should be limited to 1000mA/500mA
> >> respectively when the HW reaches the OTST1/OTST2 thermal level.
> > 
> > Hi Fenglin,
> > 
> > Only semi-related to this patch, but I wanted to ask.
> > 
> > Since most phones with a flash also have a thermistor for the flash led,
> > is there any plan to add support to be able to declare the flash led to
> > be a "cooling-device" for the relevant thermal zone? That way from a
> > Linux thermal API standpoint when the zone gets too hot that it can ask
> > the driver to throttle the brightness or turn the LED off completely.
> > 
> > Right now the only action the kernel can take is with type 'critical' to
> > just kill the entire system to mitigate the thermal situation.
> > 
> > Regards
> > Luca
> > 
>
> Hi Luca,
>
> This change provides the ability to throttle flash current based on the 
> thermal status sensed by the temperature sensor inside the flash module 
> HW , it doesn't need to register anything in Linux thermal framework.
>
> For the case that you mentioned, when an external thermistor is 
> installed nearby the flash LED component and normally the ADC_TM driver 
> registers a thermal_zone device with it, I agree that having the flash 
> LED driver providing a thermal_cooling device so that any cooling 
> mapping policy could be defined between the thermal sensor and the 
> cooling device would be a good option for better system level thermal 
> control. I would assume that this could be added in flash LED framework 
> driver instead of the client drivers considering this should be a common 
> request because of the big thermal dissipation of flash LED?

Right, the LED core getting the ability to register a cooling device
would probably be a reasonable solution, that way any flash LED driver
would be cooling-ready. Apart from decreasing brightness - or worst case
turning the LED off completely I can't think of many other actions that
could be taken anyways?

Pavel, Lee, your opinion?

Regards
Luca

>
> Fenglin
> >>
> >> ---
> >> base-commit: ca66b10a11da3c445c9c0ca1184f549bbe9061f2
> >> change-id: 20240507-qcom_flash_thermal_derating-260b1f3c757c
> >>
> >> Best regards,
> >
Fenglin Wu July 23, 2024, 3:15 a.m. UTC | #4
On 7/5/2024 3:55 PM, Fenglin Wu via B4 Relay wrote:
> From: Fenglin Wu <quic_fenglinw@quicinc.com>
> 
> The flash module has status bits to indicate different thermal
> conditions which are called as OTSTx. For each OTSTx status,
> there is a recommended total flash current for all channels to
> prevent the flash module entering into higher thermal level.
> For example, the total flash current should be limited to 1000mA/500mA
> respectively when the HW reaches the OTST1/OTST2 thermal level.
> 
> Signed-off-by: Fenglin Wu <quic_fenglinw@quicinc.com>
> ---
> Changes in v3:
> - Fix coding style issues to address review comments in v2.
> - Link to v2: https://lore.kernel.org/r/20240513-qcom_flash_thermal_derating-v2-1-e41a07d0eb83@quicinc.com
> 
> Changes in v2:
> - Update thermal threshold level 2 register definition for mvflash_4ch_regs.
>      Mvflash_4ch module thermal threshold level 2 configuration register
>      offset is 0x78, not succeeding from thermal threshold level 1 register 0x7a.
>      Hence it is not appropriate to use REG_FIELD_ID to define thermal threshold
>      register fileds like mvflash_3ch. Update to use REG_FIELD instead.
> - Link to v1: https://lore.kernel.org/r/20240509-qcom_flash_thermal_derating-v1-1-1d5e68e5d71c@quicinc.com
> ---

Hi Jones,

Can you help to review the change again when you are available?

Fenglin
> 
> Best regards,
Lee Jones July 25, 2024, 4:28 p.m. UTC | #5
On Fri, 05 Jul 2024 15:55:01 +0800, Fenglin Wu wrote:
> The flash module has status bits to indicate different thermal
> conditions which are called as OTSTx. For each OTSTx status,
> there is a recommended total flash current for all channels to
> prevent the flash module entering into higher thermal level.
> For example, the total flash current should be limited to 1000mA/500mA
> respectively when the HW reaches the OTST1/OTST2 thermal level.
> 
> [...]

Applied, thanks!

[1/1] leds: flash: leds-qcom-flash: limit LED current based on thermal condition
      commit: 3db5127c28f8faa1c245588ab982f72d033e1de4

--
Lee Jones [李琼斯]
diff mbox series

Patch

diff --git a/drivers/leds/flash/leds-qcom-flash.c b/drivers/leds/flash/leds-qcom-flash.c
index 7c99a3039171..aa22686fafe0 100644
--- a/drivers/leds/flash/leds-qcom-flash.c
+++ b/drivers/leds/flash/leds-qcom-flash.c
@@ -1,6 +1,6 @@ 
 // SPDX-License-Identifier: GPL-2.0-only
 /*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2022, 2024 Qualcomm Innovation Center, Inc. All rights reserved.
  */
 
 #include <linux/bitfield.h>
@@ -14,6 +14,9 @@ 
 #include <media/v4l2-flash-led-class.h>
 
 /* registers definitions */
+#define FLASH_REVISION_REG		0x00
+#define FLASH_4CH_REVISION_V0P1		0x01
+
 #define FLASH_TYPE_REG			0x04
 #define FLASH_TYPE_VAL			0x18
 
@@ -73,6 +76,16 @@ 
 
 #define UA_PER_MA			1000
 
+/* thermal threshold constants */
+#define OTST_3CH_MIN_VAL		3
+#define OTST1_4CH_MIN_VAL		0
+#define OTST1_4CH_V0P1_MIN_VAL		3
+#define OTST2_4CH_MIN_VAL		0
+
+#define OTST1_MAX_CURRENT_MA		1000
+#define OTST2_MAX_CURRENT_MA		500
+#define OTST3_MAX_CURRENT_MA		200
+
 enum hw_type {
 	QCOM_MVFLASH_3CH,
 	QCOM_MVFLASH_4CH,
@@ -98,6 +111,9 @@  enum {
 	REG_IRESOLUTION,
 	REG_CHAN_STROBE,
 	REG_CHAN_EN,
+	REG_THERM_THRSH1,
+	REG_THERM_THRSH2,
+	REG_THERM_THRSH3,
 	REG_MAX_COUNT,
 };
 
@@ -111,6 +127,9 @@  static struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = {
 	REG_FIELD(0x47, 0, 5),                  /* iresolution	*/
 	REG_FIELD_ID(0x49, 0, 2, 3, 1),         /* chan_strobe	*/
 	REG_FIELD(0x4c, 0, 2),                  /* chan_en	*/
+	REG_FIELD(0x56, 0, 2),			/* therm_thrsh1 */
+	REG_FIELD(0x57, 0, 2),			/* therm_thrsh2 */
+	REG_FIELD(0x58, 0, 2),			/* therm_thrsh3 */
 };
 
 static struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = {
@@ -123,6 +142,8 @@  static struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = {
 	REG_FIELD(0x49, 0, 3),			/* iresolution	*/
 	REG_FIELD_ID(0x4a, 0, 6, 4, 1),		/* chan_strobe	*/
 	REG_FIELD(0x4e, 0, 3),			/* chan_en	*/
+	REG_FIELD(0x7a, 0, 2),			/* therm_thrsh1 */
+	REG_FIELD(0x78, 0, 2),			/* therm_thrsh2 */
 };
 
 struct qcom_flash_data {
@@ -130,9 +151,11 @@  struct qcom_flash_data {
 	struct regmap_field     *r_fields[REG_MAX_COUNT];
 	struct mutex		lock;
 	enum hw_type		hw_type;
+	u32			total_ma;
 	u8			leds_count;
 	u8			max_channels;
 	u8			chan_en_bits;
+	u8			revision;
 };
 
 struct qcom_flash_led {
@@ -143,6 +166,7 @@  struct qcom_flash_led {
 	u32				max_timeout_ms;
 	u32				flash_current_ma;
 	u32				flash_timeout_ms;
+	u32				current_in_use_ma;
 	u8				*chan_id;
 	u8				chan_count;
 	bool				enabled;
@@ -172,6 +196,127 @@  static int set_flash_module_en(struct qcom_flash_led *led, bool en)
 	return rc;
 }
 
+static int update_allowed_flash_current(struct qcom_flash_led *led, u32 *current_ma, bool strobe)
+{
+	struct qcom_flash_data *flash_data = led->flash_data;
+	u32 therm_ma, avail_ma, thrsh[3], min_thrsh, sts;
+	int rc = 0;
+
+	mutex_lock(&flash_data->lock);
+	/*
+	 * Put previously allocated current into allowed budget in either of these two cases:
+	 * 1) LED is disabled;
+	 * 2) LED is enabled repeatedly
+	 */
+	if (!strobe || led->current_in_use_ma != 0) {
+		if (flash_data->total_ma >= led->current_in_use_ma)
+			flash_data->total_ma -= led->current_in_use_ma;
+		else
+			flash_data->total_ma = 0;
+
+		led->current_in_use_ma = 0;
+		if (!strobe)
+			goto unlock;
+	}
+
+	/*
+	 * Cache the default thermal threshold settings, and set them to the lowest levels before
+	 * reading over-temp real time status. If over-temp has been triggered at the lowest
+	 * threshold, it's very likely that it would be triggered at a higher (default) threshold
+	 * when more flash current is requested. Prevent device from triggering over-temp condition
+	 * by limiting the flash current for the new request.
+	 */
+	rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH1], &thrsh[0]);
+	if (rc < 0)
+		goto unlock;
+
+	rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH2], &thrsh[1]);
+	if (rc < 0)
+		goto unlock;
+
+	if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
+		rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH3], &thrsh[2]);
+		if (rc < 0)
+			goto unlock;
+	}
+
+	min_thrsh = OTST_3CH_MIN_VAL;
+	if (flash_data->hw_type == QCOM_MVFLASH_4CH)
+		min_thrsh = (flash_data->revision == FLASH_4CH_REVISION_V0P1) ?
+			OTST1_4CH_V0P1_MIN_VAL : OTST1_4CH_MIN_VAL;
+
+	rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH1], min_thrsh);
+	if (rc < 0)
+		goto unlock;
+
+	if (flash_data->hw_type == QCOM_MVFLASH_4CH)
+		min_thrsh = OTST2_4CH_MIN_VAL;
+
+	/*
+	 * The default thermal threshold settings have been updated hence
+	 * restore them if any fault happens starting from here.
+	 */
+	rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH2], min_thrsh);
+	if (rc < 0)
+		goto restore;
+
+	if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
+		rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH3], min_thrsh);
+		if (rc < 0)
+			goto restore;
+	}
+
+	/* Read thermal level status to get corresponding derating flash current */
+	rc = regmap_field_read(flash_data->r_fields[REG_STATUS2], &sts);
+	if (rc)
+		goto restore;
+
+	therm_ma = FLASH_TOTAL_CURRENT_MAX_UA / 1000;
+	if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
+		if (sts & FLASH_STS_3CH_OTST3)
+			therm_ma = OTST3_MAX_CURRENT_MA;
+		else if (sts & FLASH_STS_3CH_OTST2)
+			therm_ma = OTST2_MAX_CURRENT_MA;
+		else if (sts & FLASH_STS_3CH_OTST1)
+			therm_ma = OTST1_MAX_CURRENT_MA;
+	} else {
+		if (sts & FLASH_STS_4CH_OTST2)
+			therm_ma = OTST2_MAX_CURRENT_MA;
+		else if (sts & FLASH_STS_4CH_OTST1)
+			therm_ma = OTST1_MAX_CURRENT_MA;
+	}
+
+	/* Calculate the allowed flash current for the request */
+	if (therm_ma <= flash_data->total_ma)
+		avail_ma = 0;
+	else
+		avail_ma = therm_ma - flash_data->total_ma;
+
+	*current_ma = min_t(u32, *current_ma, avail_ma);
+	led->current_in_use_ma = *current_ma;
+	flash_data->total_ma += led->current_in_use_ma;
+
+	dev_dbg(led->flash.led_cdev.dev, "allowed flash current: %dmA, total current: %dmA\n",
+					led->current_in_use_ma, flash_data->total_ma);
+
+restore:
+	/* Restore to default thermal threshold settings */
+	rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH1], thrsh[0]);
+	if (rc < 0)
+		goto unlock;
+
+	rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH2], thrsh[1]);
+	if (rc < 0)
+		goto unlock;
+
+	if (flash_data->hw_type == QCOM_MVFLASH_3CH)
+		rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH3], thrsh[2]);
+
+unlock:
+	mutex_unlock(&flash_data->lock);
+	return rc;
+}
+
 static int set_flash_current(struct qcom_flash_led *led, u32 current_ma, enum led_mode mode)
 {
 	struct qcom_flash_data *flash_data = led->flash_data;
@@ -313,6 +458,10 @@  static int qcom_flash_strobe_set(struct led_classdev_flash *fled_cdev, bool stat
 	if (rc)
 		return rc;
 
+	rc = update_allowed_flash_current(led, &led->flash_current_ma, state);
+	if (rc < 0)
+		return rc;
+
 	rc = set_flash_current(led, led->flash_current_ma, FLASH_MODE);
 	if (rc)
 		return rc;
@@ -429,6 +578,10 @@  static int qcom_flash_led_brightness_set(struct led_classdev *led_cdev,
 	if (rc)
 		return rc;
 
+	rc = update_allowed_flash_current(led, &current_ma, enable);
+	if (rc < 0)
+		return rc;
+
 	rc = set_flash_current(led, current_ma, TORCH_MODE);
 	if (rc)
 		return rc;
@@ -703,6 +856,14 @@  static int qcom_flash_led_probe(struct platform_device *pdev)
 		flash_data->hw_type = QCOM_MVFLASH_4CH;
 		flash_data->max_channels = 4;
 		regs = mvflash_4ch_regs;
+
+		rc = regmap_read(regmap, reg_base + FLASH_REVISION_REG, &val);
+		if (rc < 0) {
+			dev_err(dev, "Failed to read flash LED module revision, rc=%d\n", rc);
+			return rc;
+		}
+
+		flash_data->revision = val;
 	} else {
 		dev_err(dev, "flash LED subtype %#x is not yet supported\n", val);
 		return -ENODEV;