Contributors: 5
Author Tokens Token Proportion Commits Commit Proportion
Fenglin Wu 3796 98.93% 3 37.50%
Dylan Van Assche 35 0.91% 2 25.00%
Uwe Kleine-König 2 0.05% 1 12.50%
Jiapeng Chong 2 0.05% 1 12.50%
Tom Rix 2 0.05% 1 12.50%
Total 3837 8


// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2022 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_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

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_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	*/
};

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	*/
};

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;
	u8			leds_count;
	u8			max_channels;
	u8			chan_en_bits;
};

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;
	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 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 = 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 = 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;

	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;

	flash_data->v4l2_flash[flash_data->leds_count] =
		v4l2_flash_init(dev, fwnode, &led->flash, &qcom_v4l2_flash_ops, &v4l2_cfg);
	return PTR_ERR_OR_ZERO(flash_data->v4l2_flash);
}
# 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", &current_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", &current_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", &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;
	} 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");