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 |
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, ¤t_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,
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, >
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, > >
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,
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 --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, ¤t_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;