mirror of
https://github.com/torvalds/linux.git
synced 2026-03-13 22:36:17 +01:00
leds: flash: leds-qcom-flash: Limit LED current based on thermal condition
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>
This commit is contained in:
parent
10cc487692
commit
a0864cf320
1 changed files with 162 additions and 1 deletions
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue