From: Fenglin Wu <[email protected]>
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 <[email protected]>
---
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)) {
+ 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;
---
base-commit: ca66b10a11da3c445c9c0ca1184f549bbe9061f2
change-id: 20240507-qcom_flash_thermal_derating-260b1f3c757c
Best regards,
--
Fenglin Wu <[email protected]>
On Mon, 13 May 2024, Fenglin Wu via B4 Relay wrote:
> From: Fenglin Wu <[email protected]>
>
> 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 <[email protected]>
> ---
> 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 <[email protected]>
--
Lee Jones [李琼斯]