a0864cf320
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>
952 lines
26 KiB
C
952 lines
26 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2022, 2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/bitfield.h>
|
|
#include <linux/bits.h>
|
|
#include <linux/leds.h>
|
|
#include <linux/led-class-flash.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/property.h>
|
|
#include <linux/regmap.h>
|
|
#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 FLASH_SUBTYPE_REG 0x05
|
|
#define FLASH_SUBTYPE_3CH_PM8150_VAL 0x04
|
|
#define FLASH_SUBTYPE_3CH_PMI8998_VAL 0x03
|
|
#define FLASH_SUBTYPE_4CH_VAL 0x07
|
|
|
|
#define FLASH_STS_3CH_OTST1 BIT(0)
|
|
#define FLASH_STS_3CH_OTST2 BIT(1)
|
|
#define FLASH_STS_3CH_OTST3 BIT(2)
|
|
#define FLASH_STS_3CH_BOB_THM_OVERLOAD BIT(3)
|
|
#define FLASH_STS_3CH_VPH_DROOP BIT(4)
|
|
#define FLASH_STS_3CH_BOB_ILIM_S1 BIT(5)
|
|
#define FLASH_STS_3CH_BOB_ILIM_S2 BIT(6)
|
|
#define FLASH_STS_3CH_BCL_IBAT BIT(7)
|
|
|
|
#define FLASH_STS_4CH_VPH_LOW BIT(0)
|
|
#define FLASH_STS_4CH_BCL_IBAT BIT(1)
|
|
#define FLASH_STS_4CH_BOB_ILIM_S1 BIT(2)
|
|
#define FLASH_STS_4CH_BOB_ILIM_S2 BIT(3)
|
|
#define FLASH_STS_4CH_OTST2 BIT(4)
|
|
#define FLASH_STS_4CH_OTST1 BIT(5)
|
|
#define FLASH_STS_4CHG_BOB_THM_OVERLOAD BIT(6)
|
|
|
|
#define FLASH_TIMER_EN_BIT BIT(7)
|
|
#define FLASH_TIMER_VAL_MASK GENMASK(6, 0)
|
|
#define FLASH_TIMER_STEP_MS 10
|
|
|
|
#define FLASH_STROBE_HW_SW_SEL_BIT BIT(2)
|
|
#define SW_STROBE_VAL 0
|
|
#define HW_STROBE_VAL 1
|
|
#define FLASH_HW_STROBE_TRIGGER_SEL_BIT BIT(1)
|
|
#define STROBE_LEVEL_TRIGGER_VAL 0
|
|
#define STROBE_EDGE_TRIGGER_VAL 1
|
|
#define FLASH_STROBE_POLARITY_BIT BIT(0)
|
|
#define STROBE_ACTIVE_HIGH_VAL 1
|
|
|
|
#define FLASH_IRES_MASK_4CH BIT(0)
|
|
#define FLASH_IRES_MASK_3CH GENMASK(1, 0)
|
|
#define FLASH_IRES_12P5MA_VAL 0
|
|
#define FLASH_IRES_5MA_VAL_4CH 1
|
|
#define FLASH_IRES_5MA_VAL_3CH 3
|
|
|
|
/* constants */
|
|
#define FLASH_CURRENT_MAX_UA 1500000
|
|
#define TORCH_CURRENT_MAX_UA 500000
|
|
#define FLASH_TOTAL_CURRENT_MAX_UA 2000000
|
|
#define FLASH_CURRENT_DEFAULT_UA 1000000
|
|
#define TORCH_CURRENT_DEFAULT_UA 200000
|
|
|
|
#define TORCH_IRES_UA 5000
|
|
#define FLASH_IRES_UA 12500
|
|
|
|
#define FLASH_TIMEOUT_MAX_US 1280000
|
|
#define FLASH_TIMEOUT_STEP_US 10000
|
|
|
|
#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,
|
|
};
|
|
|
|
enum led_mode {
|
|
FLASH_MODE,
|
|
TORCH_MODE,
|
|
};
|
|
|
|
enum led_strobe {
|
|
SW_STROBE,
|
|
HW_STROBE,
|
|
};
|
|
|
|
enum {
|
|
REG_STATUS1,
|
|
REG_STATUS2,
|
|
REG_STATUS3,
|
|
REG_CHAN_TIMER,
|
|
REG_ITARGET,
|
|
REG_MODULE_EN,
|
|
REG_IRESOLUTION,
|
|
REG_CHAN_STROBE,
|
|
REG_CHAN_EN,
|
|
REG_THERM_THRSH1,
|
|
REG_THERM_THRSH2,
|
|
REG_THERM_THRSH3,
|
|
REG_MAX_COUNT,
|
|
};
|
|
|
|
static struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = {
|
|
REG_FIELD(0x08, 0, 7), /* status1 */
|
|
REG_FIELD(0x09, 0, 7), /* status2 */
|
|
REG_FIELD(0x0a, 0, 7), /* status3 */
|
|
REG_FIELD_ID(0x40, 0, 7, 3, 1), /* chan_timer */
|
|
REG_FIELD_ID(0x43, 0, 6, 3, 1), /* itarget */
|
|
REG_FIELD(0x46, 7, 7), /* module_en */
|
|
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] = {
|
|
REG_FIELD(0x06, 0, 7), /* status1 */
|
|
REG_FIELD(0x07, 0, 6), /* status2 */
|
|
REG_FIELD(0x09, 0, 7), /* status3 */
|
|
REG_FIELD_ID(0x3e, 0, 7, 4, 1), /* chan_timer */
|
|
REG_FIELD_ID(0x42, 0, 6, 4, 1), /* itarget */
|
|
REG_FIELD(0x46, 7, 7), /* module_en */
|
|
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 {
|
|
struct v4l2_flash **v4l2_flash;
|
|
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 {
|
|
struct qcom_flash_data *flash_data;
|
|
struct led_classdev_flash flash;
|
|
u32 max_flash_current_ma;
|
|
u32 max_torch_current_ma;
|
|
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;
|
|
};
|
|
|
|
static int set_flash_module_en(struct qcom_flash_led *led, bool en)
|
|
{
|
|
struct qcom_flash_data *flash_data = led->flash_data;
|
|
u8 led_mask = 0, enable;
|
|
int i, rc;
|
|
|
|
for (i = 0; i < led->chan_count; i++)
|
|
led_mask |= BIT(led->chan_id[i]);
|
|
|
|
mutex_lock(&flash_data->lock);
|
|
if (en)
|
|
flash_data->chan_en_bits |= led_mask;
|
|
else
|
|
flash_data->chan_en_bits &= ~led_mask;
|
|
|
|
enable = !!flash_data->chan_en_bits;
|
|
rc = regmap_field_write(flash_data->r_fields[REG_MODULE_EN], enable);
|
|
if (rc)
|
|
dev_err(led->flash.led_cdev.dev, "write module_en failed, rc=%d\n", rc);
|
|
mutex_unlock(&flash_data->lock);
|
|
|
|
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;
|
|
u32 itarg_ua, ires_ua;
|
|
u8 shift, ires_mask = 0, ires_val = 0, chan_id;
|
|
int i, rc;
|
|
|
|
/*
|
|
* Split the current across the channels and set the
|
|
* IRESOLUTION and ITARGET registers accordingly.
|
|
*/
|
|
itarg_ua = (current_ma * UA_PER_MA) / led->chan_count + 1;
|
|
ires_ua = (mode == FLASH_MODE) ? FLASH_IRES_UA : TORCH_IRES_UA;
|
|
|
|
for (i = 0; i < led->chan_count; i++) {
|
|
u8 itarget = 0;
|
|
|
|
if (itarg_ua > ires_ua)
|
|
itarget = itarg_ua / ires_ua - 1;
|
|
|
|
chan_id = led->chan_id[i];
|
|
|
|
rc = regmap_fields_write(flash_data->r_fields[REG_ITARGET], chan_id, itarget);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
|
|
shift = chan_id * 2;
|
|
ires_mask |= FLASH_IRES_MASK_3CH << shift;
|
|
ires_val |= ((mode == FLASH_MODE) ?
|
|
(FLASH_IRES_12P5MA_VAL << shift) :
|
|
(FLASH_IRES_5MA_VAL_3CH << shift));
|
|
} else if (flash_data->hw_type == QCOM_MVFLASH_4CH) {
|
|
shift = chan_id;
|
|
ires_mask |= FLASH_IRES_MASK_4CH << shift;
|
|
ires_val |= ((mode == FLASH_MODE) ?
|
|
(FLASH_IRES_12P5MA_VAL << shift) :
|
|
(FLASH_IRES_5MA_VAL_4CH << shift));
|
|
} else {
|
|
dev_err(led->flash.led_cdev.dev,
|
|
"HW type %d is not supported\n", flash_data->hw_type);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
return regmap_field_update_bits(flash_data->r_fields[REG_IRESOLUTION], ires_mask, ires_val);
|
|
}
|
|
|
|
static int set_flash_timeout(struct qcom_flash_led *led, u32 timeout_ms)
|
|
{
|
|
struct qcom_flash_data *flash_data = led->flash_data;
|
|
u8 timer, chan_id;
|
|
int rc, i;
|
|
|
|
/* set SAFETY_TIMER for all the channels connected to the same LED */
|
|
timeout_ms = min_t(u32, timeout_ms, led->max_timeout_ms);
|
|
|
|
for (i = 0; i < led->chan_count; i++) {
|
|
chan_id = led->chan_id[i];
|
|
|
|
timer = timeout_ms / FLASH_TIMER_STEP_MS;
|
|
timer = clamp_t(u8, timer, 0, FLASH_TIMER_VAL_MASK);
|
|
|
|
if (timeout_ms)
|
|
timer |= FLASH_TIMER_EN_BIT;
|
|
|
|
rc = regmap_fields_write(flash_data->r_fields[REG_CHAN_TIMER], chan_id, timer);
|
|
if (rc)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_flash_strobe(struct qcom_flash_led *led, enum led_strobe strobe, bool state)
|
|
{
|
|
struct qcom_flash_data *flash_data = led->flash_data;
|
|
u8 strobe_sel, chan_en, chan_id, chan_mask = 0;
|
|
int rc, i;
|
|
|
|
/* Set SW strobe config for all channels connected to the LED */
|
|
for (i = 0; i < led->chan_count; i++) {
|
|
chan_id = led->chan_id[i];
|
|
|
|
if (strobe == SW_STROBE)
|
|
strobe_sel = FIELD_PREP(FLASH_STROBE_HW_SW_SEL_BIT, SW_STROBE_VAL);
|
|
else
|
|
strobe_sel = FIELD_PREP(FLASH_STROBE_HW_SW_SEL_BIT, HW_STROBE_VAL);
|
|
|
|
strobe_sel |=
|
|
FIELD_PREP(FLASH_HW_STROBE_TRIGGER_SEL_BIT, STROBE_LEVEL_TRIGGER_VAL) |
|
|
FIELD_PREP(FLASH_STROBE_POLARITY_BIT, STROBE_ACTIVE_HIGH_VAL);
|
|
|
|
rc = regmap_fields_write(
|
|
flash_data->r_fields[REG_CHAN_STROBE], chan_id, strobe_sel);
|
|
if (rc)
|
|
return rc;
|
|
|
|
chan_mask |= BIT(chan_id);
|
|
}
|
|
|
|
/* Enable/disable flash channels */
|
|
chan_en = state ? chan_mask : 0;
|
|
rc = regmap_field_update_bits(flash_data->r_fields[REG_CHAN_EN], chan_mask, chan_en);
|
|
if (rc)
|
|
return rc;
|
|
|
|
led->enabled = state;
|
|
return 0;
|
|
}
|
|
|
|
static inline struct qcom_flash_led *flcdev_to_qcom_fled(struct led_classdev_flash *flcdev)
|
|
{
|
|
return container_of(flcdev, struct qcom_flash_led, flash);
|
|
}
|
|
|
|
static int qcom_flash_brightness_set(struct led_classdev_flash *fled_cdev, u32 brightness)
|
|
{
|
|
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
|
|
|
led->flash_current_ma = min_t(u32, led->max_flash_current_ma, brightness / UA_PER_MA);
|
|
return 0;
|
|
}
|
|
|
|
static int qcom_flash_timeout_set(struct led_classdev_flash *fled_cdev, u32 timeout)
|
|
{
|
|
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
|
|
|
led->flash_timeout_ms = timeout / USEC_PER_MSEC;
|
|
return 0;
|
|
}
|
|
|
|
static int qcom_flash_strobe_set(struct led_classdev_flash *fled_cdev, bool state)
|
|
{
|
|
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
|
int rc;
|
|
|
|
rc = set_flash_strobe(led, SW_STROBE, false);
|
|
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;
|
|
|
|
rc = set_flash_timeout(led, led->flash_timeout_ms);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = set_flash_module_en(led, state);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return set_flash_strobe(led, SW_STROBE, state);
|
|
}
|
|
|
|
static int qcom_flash_strobe_get(struct led_classdev_flash *fled_cdev, bool *state)
|
|
{
|
|
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
|
|
|
*state = led->enabled;
|
|
return 0;
|
|
}
|
|
|
|
static int qcom_flash_fault_get(struct led_classdev_flash *fled_cdev, u32 *fault)
|
|
{
|
|
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
|
struct qcom_flash_data *flash_data = led->flash_data;
|
|
u8 shift, chan_id, chan_mask = 0;
|
|
u8 ot_mask = 0, oc_mask = 0, uv_mask = 0;
|
|
u32 val, fault_sts = 0;
|
|
int i, rc;
|
|
|
|
rc = regmap_field_read(flash_data->r_fields[REG_STATUS1], &val);
|
|
if (rc)
|
|
return rc;
|
|
|
|
for (i = 0; i < led->chan_count; i++) {
|
|
chan_id = led->chan_id[i];
|
|
shift = chan_id * 2;
|
|
|
|
if (val & BIT(shift))
|
|
fault_sts |= LED_FAULT_SHORT_CIRCUIT;
|
|
|
|
chan_mask |= BIT(chan_id);
|
|
}
|
|
|
|
rc = regmap_field_read(flash_data->r_fields[REG_STATUS2], &val);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
|
|
ot_mask = FLASH_STS_3CH_OTST1 |
|
|
FLASH_STS_3CH_OTST2 |
|
|
FLASH_STS_3CH_OTST3 |
|
|
FLASH_STS_3CH_BOB_THM_OVERLOAD;
|
|
oc_mask = FLASH_STS_3CH_BOB_ILIM_S1 |
|
|
FLASH_STS_3CH_BOB_ILIM_S2 |
|
|
FLASH_STS_3CH_BCL_IBAT;
|
|
uv_mask = FLASH_STS_3CH_VPH_DROOP;
|
|
} else if (flash_data->hw_type == QCOM_MVFLASH_4CH) {
|
|
ot_mask = FLASH_STS_4CH_OTST2 |
|
|
FLASH_STS_4CH_OTST1 |
|
|
FLASH_STS_4CHG_BOB_THM_OVERLOAD;
|
|
oc_mask = FLASH_STS_4CH_BCL_IBAT |
|
|
FLASH_STS_4CH_BOB_ILIM_S1 |
|
|
FLASH_STS_4CH_BOB_ILIM_S2;
|
|
uv_mask = FLASH_STS_4CH_VPH_LOW;
|
|
}
|
|
|
|
if (val & ot_mask)
|
|
fault_sts |= LED_FAULT_OVER_TEMPERATURE;
|
|
|
|
if (val & oc_mask)
|
|
fault_sts |= LED_FAULT_OVER_CURRENT;
|
|
|
|
if (val & uv_mask)
|
|
fault_sts |= LED_FAULT_INPUT_VOLTAGE;
|
|
|
|
rc = regmap_field_read(flash_data->r_fields[REG_STATUS3], &val);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
|
|
if (val & chan_mask)
|
|
fault_sts |= LED_FAULT_TIMEOUT;
|
|
} else if (flash_data->hw_type == QCOM_MVFLASH_4CH) {
|
|
for (i = 0; i < led->chan_count; i++) {
|
|
chan_id = led->chan_id[i];
|
|
shift = chan_id * 2;
|
|
|
|
if (val & BIT(shift))
|
|
fault_sts |= LED_FAULT_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
*fault = fault_sts;
|
|
return 0;
|
|
}
|
|
|
|
static int qcom_flash_led_brightness_set(struct led_classdev *led_cdev,
|
|
enum led_brightness brightness)
|
|
{
|
|
struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
|
|
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
|
u32 current_ma = brightness * led->max_torch_current_ma / LED_FULL;
|
|
bool enable = !!brightness;
|
|
int rc;
|
|
|
|
rc = set_flash_strobe(led, SW_STROBE, false);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = set_flash_module_en(led, false);
|
|
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;
|
|
|
|
/* Disable flash timeout for torch LED */
|
|
rc = set_flash_timeout(led, 0);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = set_flash_module_en(led, enable);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return set_flash_strobe(led, SW_STROBE, enable);
|
|
}
|
|
|
|
static const struct led_flash_ops qcom_flash_ops = {
|
|
.flash_brightness_set = qcom_flash_brightness_set,
|
|
.strobe_set = qcom_flash_strobe_set,
|
|
.strobe_get = qcom_flash_strobe_get,
|
|
.timeout_set = qcom_flash_timeout_set,
|
|
.fault_get = qcom_flash_fault_get,
|
|
};
|
|
|
|
#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
|
|
static int qcom_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable)
|
|
{
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
|
int rc;
|
|
|
|
rc = set_flash_module_en(led, enable);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (enable)
|
|
return set_flash_strobe(led, HW_STROBE, true);
|
|
else
|
|
return set_flash_strobe(led, SW_STROBE, false);
|
|
}
|
|
|
|
static enum led_brightness
|
|
qcom_flash_intensity_to_led_brightness(struct v4l2_flash *v4l2_flash, s32 intensity)
|
|
{
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
|
u32 current_ma = intensity / UA_PER_MA;
|
|
|
|
current_ma = min_t(u32, current_ma, led->max_torch_current_ma);
|
|
if (!current_ma)
|
|
return LED_OFF;
|
|
|
|
return (current_ma * LED_FULL) / led->max_torch_current_ma;
|
|
}
|
|
|
|
static s32 qcom_flash_brightness_to_led_intensity(struct v4l2_flash *v4l2_flash,
|
|
enum led_brightness brightness)
|
|
{
|
|
struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
|
|
struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
|
|
|
|
return (brightness * led->max_torch_current_ma * UA_PER_MA) / LED_FULL;
|
|
}
|
|
|
|
static const struct v4l2_flash_ops qcom_v4l2_flash_ops = {
|
|
.external_strobe_set = qcom_flash_external_strobe_set,
|
|
.intensity_to_led_brightness = qcom_flash_intensity_to_led_brightness,
|
|
.led_brightness_to_intensity = qcom_flash_brightness_to_led_intensity,
|
|
};
|
|
|
|
static int
|
|
qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwnode_handle *fwnode)
|
|
{
|
|
struct qcom_flash_data *flash_data = led->flash_data;
|
|
struct v4l2_flash_config v4l2_cfg = { 0 };
|
|
struct led_flash_setting *intensity = &v4l2_cfg.intensity;
|
|
struct v4l2_flash *v4l2_flash;
|
|
|
|
if (!(led->flash.led_cdev.flags & LED_DEV_CAP_FLASH))
|
|
return 0;
|
|
|
|
intensity->min = intensity->step = TORCH_IRES_UA * led->chan_count;
|
|
intensity->max = led->max_torch_current_ma * UA_PER_MA;
|
|
intensity->val = min_t(u32, intensity->max, TORCH_CURRENT_DEFAULT_UA);
|
|
|
|
strscpy(v4l2_cfg.dev_name, led->flash.led_cdev.dev->kobj.name,
|
|
sizeof(v4l2_cfg.dev_name));
|
|
|
|
v4l2_cfg.has_external_strobe = true;
|
|
v4l2_cfg.flash_faults = LED_FAULT_INPUT_VOLTAGE |
|
|
LED_FAULT_OVER_CURRENT |
|
|
LED_FAULT_SHORT_CIRCUIT |
|
|
LED_FAULT_OVER_TEMPERATURE |
|
|
LED_FAULT_TIMEOUT;
|
|
|
|
v4l2_flash = v4l2_flash_init(dev, fwnode, &led->flash, &qcom_v4l2_flash_ops, &v4l2_cfg);
|
|
if (IS_ERR(v4l2_flash))
|
|
return PTR_ERR(v4l2_flash);
|
|
|
|
flash_data->v4l2_flash[flash_data->leds_count] = v4l2_flash;
|
|
return 0;
|
|
}
|
|
# else
|
|
static int
|
|
qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwnode_handle *fwnode)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int qcom_flash_register_led_device(struct device *dev,
|
|
struct fwnode_handle *node, struct qcom_flash_led *led)
|
|
{
|
|
struct qcom_flash_data *flash_data = led->flash_data;
|
|
struct led_init_data init_data;
|
|
struct led_classdev_flash *flash = &led->flash;
|
|
struct led_flash_setting *brightness, *timeout;
|
|
u32 current_ua, timeout_us;
|
|
u32 channels[4];
|
|
int i, rc, count;
|
|
|
|
count = fwnode_property_count_u32(node, "led-sources");
|
|
if (count <= 0) {
|
|
dev_err(dev, "No led-sources specified\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (count > flash_data->max_channels) {
|
|
dev_err(dev, "led-sources count %u exceeds maximum channel count %u\n",
|
|
count, flash_data->max_channels);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = fwnode_property_read_u32_array(node, "led-sources", channels, count);
|
|
if (rc < 0) {
|
|
dev_err(dev, "Failed to read led-sources property, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
led->chan_count = count;
|
|
led->chan_id = devm_kcalloc(dev, count, sizeof(u8), GFP_KERNEL);
|
|
if (!led->chan_id)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
if ((channels[i] == 0) || (channels[i] > flash_data->max_channels)) {
|
|
dev_err(dev, "led-source out of HW support range [1-%u]\n",
|
|
flash_data->max_channels);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Make chan_id indexing from 0 */
|
|
led->chan_id[i] = channels[i] - 1;
|
|
}
|
|
|
|
rc = fwnode_property_read_u32(node, "led-max-microamp", ¤t_ua);
|
|
if (rc < 0) {
|
|
dev_err(dev, "Failed to read led-max-microamp property, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (current_ua == 0) {
|
|
dev_err(dev, "led-max-microamp shouldn't be 0\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
current_ua = min_t(u32, current_ua, TORCH_CURRENT_MAX_UA * led->chan_count);
|
|
led->max_torch_current_ma = current_ua / UA_PER_MA;
|
|
|
|
if (fwnode_property_present(node, "flash-max-microamp")) {
|
|
flash->led_cdev.flags |= LED_DEV_CAP_FLASH;
|
|
|
|
rc = fwnode_property_read_u32(node, "flash-max-microamp", ¤t_ua);
|
|
if (rc < 0) {
|
|
dev_err(dev, "Failed to read flash-max-microamp property, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
current_ua = min_t(u32, current_ua, FLASH_CURRENT_MAX_UA * led->chan_count);
|
|
current_ua = min_t(u32, current_ua, FLASH_TOTAL_CURRENT_MAX_UA);
|
|
|
|
/* Initialize flash class LED device brightness settings */
|
|
brightness = &flash->brightness;
|
|
brightness->min = brightness->step = FLASH_IRES_UA * led->chan_count;
|
|
brightness->max = current_ua;
|
|
brightness->val = min_t(u32, current_ua, FLASH_CURRENT_DEFAULT_UA);
|
|
|
|
led->max_flash_current_ma = current_ua / UA_PER_MA;
|
|
led->flash_current_ma = brightness->val / UA_PER_MA;
|
|
|
|
rc = fwnode_property_read_u32(node, "flash-max-timeout-us", &timeout_us);
|
|
if (rc < 0) {
|
|
dev_err(dev, "Failed to read flash-max-timeout-us property, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
timeout_us = min_t(u32, timeout_us, FLASH_TIMEOUT_MAX_US);
|
|
|
|
/* Initialize flash class LED device timeout settings */
|
|
timeout = &flash->timeout;
|
|
timeout->min = timeout->step = FLASH_TIMEOUT_STEP_US;
|
|
timeout->val = timeout->max = timeout_us;
|
|
|
|
led->max_timeout_ms = led->flash_timeout_ms = timeout_us / USEC_PER_MSEC;
|
|
|
|
flash->ops = &qcom_flash_ops;
|
|
}
|
|
|
|
flash->led_cdev.brightness_set_blocking = qcom_flash_led_brightness_set;
|
|
|
|
init_data.fwnode = node;
|
|
init_data.devicename = NULL;
|
|
init_data.default_label = NULL;
|
|
init_data.devname_mandatory = false;
|
|
|
|
rc = devm_led_classdev_flash_register_ext(dev, flash, &init_data);
|
|
if (rc < 0) {
|
|
dev_err(dev, "Register flash LED classdev failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return qcom_flash_v4l2_init(dev, led, node);
|
|
}
|
|
|
|
static int qcom_flash_led_probe(struct platform_device *pdev)
|
|
{
|
|
struct qcom_flash_data *flash_data;
|
|
struct qcom_flash_led *led;
|
|
struct fwnode_handle *child;
|
|
struct device *dev = &pdev->dev;
|
|
struct regmap *regmap;
|
|
struct reg_field *regs;
|
|
int count, i, rc;
|
|
u32 val, reg_base;
|
|
|
|
flash_data = devm_kzalloc(dev, sizeof(*flash_data), GFP_KERNEL);
|
|
if (!flash_data)
|
|
return -ENOMEM;
|
|
|
|
regmap = dev_get_regmap(dev->parent, NULL);
|
|
if (!regmap) {
|
|
dev_err(dev, "Failed to get parent regmap\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = fwnode_property_read_u32(dev->fwnode, "reg", ®_base);
|
|
if (rc < 0) {
|
|
dev_err(dev, "Failed to get register base address, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = regmap_read(regmap, reg_base + FLASH_TYPE_REG, &val);
|
|
if (rc < 0) {
|
|
dev_err(dev, "Read flash LED module type failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (val != FLASH_TYPE_VAL) {
|
|
dev_err(dev, "type %#x is not a flash LED module\n", val);
|
|
return -ENODEV;
|
|
}
|
|
|
|
rc = regmap_read(regmap, reg_base + FLASH_SUBTYPE_REG, &val);
|
|
if (rc < 0) {
|
|
dev_err(dev, "Read flash LED module subtype failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (val == FLASH_SUBTYPE_3CH_PM8150_VAL || val == FLASH_SUBTYPE_3CH_PMI8998_VAL) {
|
|
flash_data->hw_type = QCOM_MVFLASH_3CH;
|
|
flash_data->max_channels = 3;
|
|
regs = mvflash_3ch_regs;
|
|
} else if (val == FLASH_SUBTYPE_4CH_VAL) {
|
|
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;
|
|
}
|
|
|
|
for (i = 0; i < REG_MAX_COUNT; i++)
|
|
regs[i].reg += reg_base;
|
|
|
|
rc = devm_regmap_field_bulk_alloc(dev, regmap, flash_data->r_fields, regs, REG_MAX_COUNT);
|
|
if (rc < 0) {
|
|
dev_err(dev, "Failed to allocate regmap field, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, flash_data);
|
|
mutex_init(&flash_data->lock);
|
|
|
|
count = device_get_child_node_count(dev);
|
|
if (count == 0 || count > flash_data->max_channels) {
|
|
dev_err(dev, "No child or child count exceeds %d\n", flash_data->max_channels);
|
|
return -EINVAL;
|
|
}
|
|
|
|
flash_data->v4l2_flash = devm_kcalloc(dev, count,
|
|
sizeof(*flash_data->v4l2_flash), GFP_KERNEL);
|
|
if (!flash_data->v4l2_flash)
|
|
return -ENOMEM;
|
|
|
|
device_for_each_child_node(dev, child) {
|
|
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
|
|
if (!led) {
|
|
rc = -ENOMEM;
|
|
goto release;
|
|
}
|
|
|
|
led->flash_data = flash_data;
|
|
rc = qcom_flash_register_led_device(dev, child, led);
|
|
if (rc < 0)
|
|
goto release;
|
|
|
|
flash_data->leds_count++;
|
|
}
|
|
|
|
return 0;
|
|
|
|
release:
|
|
fwnode_handle_put(child);
|
|
while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count)
|
|
v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]);
|
|
return rc;
|
|
}
|
|
|
|
static void qcom_flash_led_remove(struct platform_device *pdev)
|
|
{
|
|
struct qcom_flash_data *flash_data = platform_get_drvdata(pdev);
|
|
|
|
while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count)
|
|
v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]);
|
|
|
|
mutex_destroy(&flash_data->lock);
|
|
}
|
|
|
|
static const struct of_device_id qcom_flash_led_match_table[] = {
|
|
{ .compatible = "qcom,spmi-flash-led" },
|
|
{ }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, qcom_flash_led_match_table);
|
|
static struct platform_driver qcom_flash_led_driver = {
|
|
.driver = {
|
|
.name = "leds-qcom-flash",
|
|
.of_match_table = qcom_flash_led_match_table,
|
|
},
|
|
.probe = qcom_flash_led_probe,
|
|
.remove_new = qcom_flash_led_remove,
|
|
};
|
|
|
|
module_platform_driver(qcom_flash_led_driver);
|
|
|
|
MODULE_DESCRIPTION("QCOM Flash LED driver");
|
|
MODULE_LICENSE("GPL");
|