Message ID | 20240513-qcom_flash_thermal_derating-v2-1-e41a07d0eb83@quicinc.com |
---|---|
State | Superseded |
Headers | show |
Series | [v2] leds: flash: leds-qcom-flash: limit LED current based on thermal condition | expand |
On Mon, 13 May 2024, 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 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 | 156 ++++++++++++++++++++++++++++++++++- > 1 file changed, 155 insertions(+), 1 deletion(-) > > diff --git a/drivers/leds/flash/leds-qcom-flash.c b/drivers/leds/flash/leds-qcom-flash.c > index 7c99a3039171..d7c03c25941a 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,121 @@ 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; > + > + 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 || (strobe && led->current_in_use_ma != 0)) { Isn't the check for 'strobe' here redundant? > + 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) { > + mutex_unlock(&flash_data->lock); > + return 0; Pre-initialise 'rc = 0' during the declaration, then 'goto unlock' here. > + } > + } > + > + /* > + * 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; Bunching up code decreases readability. '\n' > + 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; '\n' > + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH2], min_thrsh); > + if (rc < 0) > + goto restore; Maybe detail why you're jumping ahead here. > + 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 */ Sentences start with upper case chars. > + 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 */ As above and throughout. > + if (therm_ma <= flash_data->total_ma) > + avail_ma = 0; > + else > + avail_ma = therm_ma - flash_data->total_ma; '\n' > + *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); '\n' > +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 +452,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 +572,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 +850,13 @@ 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; '\n' > + rc = regmap_read(regmap, reg_base + FLASH_REVISION_REG, &val); > + if (rc < 0) { > + dev_err(dev, "Read flash LED module revision failed, rc=%d\n", rc); "Failed to read flash LED module revision" > + 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 <quic_fenglinw@quicinc.com>
diff --git a/drivers/leds/flash/leds-qcom-flash.c b/drivers/leds/flash/leds-qcom-flash.c index 7c99a3039171..d7c03c25941a 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,121 @@ 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; + + 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 || (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) { + mutex_unlock(&flash_data->lock); + return 0; + } + } + + /* + * 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; + 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 +452,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 +572,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 +850,13 @@ 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, "Read flash LED module revision failed, 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;