]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
leds: flash: leds-qcom-flash: Limit LED current based on thermal condition
authorFenglin Wu <quic_fenglinw@quicinc.com>
Fri, 5 Jul 2024 07:55:01 +0000 (15:55 +0800)
committerLee Jones <lee@kernel.org>
Thu, 1 Aug 2024 12:40:08 +0000 (13:40 +0100)
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>
Link: https://lore.kernel.org/r/20240705-qcom_flash_thermal_derating-v3-1-8e2e2783e3a6@quicinc.com
Signed-off-by: Lee Jones <lee@kernel.org>
drivers/leds/flash/leds-qcom-flash.c

index bf70bf6fb0d5939d88bbce4c0b2fa531c78e5542..41ce034f700ee5f88d15f859c2d17b5f408dff2c 100644 (file)
@@ -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
 
 
 #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;
@@ -707,6 +860,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;