summaryrefslogtreecommitdiffstats
path: root/drivers/leds/flash
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--drivers/leds/flash/Kconfig93
-rw-r--r--drivers/leds/flash/Makefile11
-rw-r--r--drivers/leds/flash/leds-aat1290.c556
-rw-r--r--drivers/leds/flash/leds-as3645a.c772
-rw-r--r--drivers/leds/flash/leds-ktd2692.c419
-rw-r--r--drivers/leds/flash/leds-lm3601x.c482
-rw-r--r--drivers/leds/flash/leds-max77693.c1059
-rw-r--r--drivers/leds/flash/leds-mt6360.c910
-rw-r--r--drivers/leds/flash/leds-rt4505.c429
-rw-r--r--drivers/leds/flash/leds-rt8515.c399
-rw-r--r--drivers/leds/flash/leds-sgm3140.c312
11 files changed, 5442 insertions, 0 deletions
diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
new file mode 100644
index 000000000..d3eb689b1
--- /dev/null
+++ b/drivers/leds/flash/Kconfig
@@ -0,0 +1,93 @@
+# SPDX-License-Identifier: GPL-2.0
+
+if LEDS_CLASS_FLASH
+
+config LEDS_AAT1290
+ tristate "LED support for the AAT1290"
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ depends on GPIOLIB || COMPILE_TEST
+ depends on OF
+ depends on PINCTRL
+ help
+ This option enables support for the LEDs on the AAT1290.
+
+config LEDS_AS3645A
+ tristate "AS3645A and LM3555 LED flash controllers support"
+ depends on I2C
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ help
+ Enable LED flash class support for AS3645A LED flash
+ controller. V4L2 flash API is provided as well if
+ CONFIG_V4L2_FLASH_API is enabled.
+
+config LEDS_KTD2692
+ tristate "LED support for Kinetic KTD2692 flash LED controller"
+ depends on OF
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ This option enables support for Kinetic KTD2692 LED flash connected
+ through ExpressWire interface.
+
+ Say Y to enable this driver.
+
+config LEDS_LM3601X
+ tristate "LED support for LM3601x Chips"
+ depends on LEDS_CLASS && I2C
+ select REGMAP_I2C
+ help
+ This option enables support for the TI LM3601x family
+ of flash, torch and indicator classes.
+
+config LEDS_MAX77693
+ tristate "LED support for MAX77693 Flash"
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ depends on MFD_MAX77693
+ depends on OF
+ help
+ This option enables support for the flash part of the MAX77693
+ multifunction device. It has build in control for two leds in flash
+ and torch mode.
+
+config LEDS_MT6360
+ tristate "LED Support for Mediatek MT6360 PMIC"
+ depends on LEDS_CLASS && OF
+ depends on LEDS_CLASS_FLASH || !LEDS_CLASS_FLASH
+ depends on LEDS_CLASS_MULTICOLOR || !LEDS_CLASS_MULTICOLOR
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ depends on MFD_MT6360
+ help
+ This option enables support for dual Flash LED drivers found on
+ Mediatek MT6360 PMIC.
+ Independent current sources supply for each flash LED support torch
+ and strobe mode.
+
+config LEDS_RT4505
+ tristate "LED support for RT4505 flashlight controller"
+ depends on I2C && OF
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ select REGMAP_I2C
+ help
+ This option enables support for the RT4505 flash LED controller.
+ RT4505 includes torch and flash functions with programmable current.
+ And it's commonly used to compensate the illuminance for the camera
+ inside the mobile product like as phones or tablets.
+
+config LEDS_RT8515
+ tristate "LED support for Richtek RT8515 flash/torch LED"
+ depends on GPIOLIB
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ help
+ This option enables support for the Richtek RT8515 flash
+ and torch LEDs found on some mobile phones.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-rt8515.
+
+config LEDS_SGM3140
+ tristate "LED support for the SGM3140"
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ help
+ This option enables support for the SGM3140 500mA Buck/Boost Charge
+ Pump LED Driver.
+
+endif # LEDS_CLASS_FLASH
diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile
new file mode 100644
index 000000000..0acbddc0b
--- /dev/null
+++ b/drivers/leds/flash/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_LEDS_MT6360) += leds-mt6360.o
+obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
+obj-$(CONFIG_LEDS_AS3645A) += leds-as3645a.o
+obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o
+obj-$(CONFIG_LEDS_LM3601X) += leds-lm3601x.o
+obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
+obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o
+obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o
+obj-$(CONFIG_LEDS_SGM3140) += leds-sgm3140.o
diff --git a/drivers/leds/flash/leds-aat1290.c b/drivers/leds/flash/leds-aat1290.c
new file mode 100644
index 000000000..589484b22
--- /dev/null
+++ b/drivers/leds/flash/leds-aat1290.c
@@ -0,0 +1,556 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED Flash class driver for the AAT1290
+ * 1.5A Step-Up Current Regulator for Flash LEDs
+ *
+ * Copyright (C) 2015, Samsung Electronics Co., Ltd.
+ * Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/led-class-flash.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define AAT1290_MOVIE_MODE_CURRENT_ADDR 17
+#define AAT1290_MAX_MM_CURR_PERCENT_0 16
+#define AAT1290_MAX_MM_CURR_PERCENT_100 1
+
+#define AAT1290_FLASH_SAFETY_TIMER_ADDR 18
+
+#define AAT1290_MOVIE_MODE_CONFIG_ADDR 19
+#define AAT1290_MOVIE_MODE_OFF 1
+#define AAT1290_MOVIE_MODE_ON 3
+
+#define AAT1290_MM_CURRENT_RATIO_ADDR 20
+#define AAT1290_MM_TO_FL_1_92 1
+
+#define AAT1290_MM_TO_FL_RATIO 1000 / 1920
+#define AAT1290_MAX_MM_CURRENT(fl_max) (fl_max * AAT1290_MM_TO_FL_RATIO)
+
+#define AAT1290_LATCH_TIME_MIN_US 500
+#define AAT1290_LATCH_TIME_MAX_US 1000
+#define AAT1290_EN_SET_TICK_TIME_US 1
+#define AAT1290_FLEN_OFF_DELAY_TIME_US 10
+#define AAT1290_FLASH_TM_NUM_LEVELS 16
+#define AAT1290_MM_CURRENT_SCALE_SIZE 15
+
+#define AAT1290_NAME "aat1290"
+
+
+struct aat1290_led_config_data {
+ /* maximum LED current in movie mode */
+ u32 max_mm_current;
+ /* maximum LED current in flash mode */
+ u32 max_flash_current;
+ /* maximum flash timeout */
+ u32 max_flash_tm;
+ /* external strobe capability */
+ bool has_external_strobe;
+ /* max LED brightness level */
+ enum led_brightness max_brightness;
+};
+
+struct aat1290_led {
+ /* platform device data */
+ struct platform_device *pdev;
+ /* secures access to the device */
+ struct mutex lock;
+
+ /* corresponding LED Flash class device */
+ struct led_classdev_flash fled_cdev;
+ /* V4L2 Flash device */
+ struct v4l2_flash *v4l2_flash;
+
+ /* FLEN pin */
+ struct gpio_desc *gpio_fl_en;
+ /* EN|SET pin */
+ struct gpio_desc *gpio_en_set;
+ /* movie mode current scale */
+ int *mm_current_scale;
+ /* device mode */
+ bool movie_mode;
+ /* brightness cache */
+ unsigned int torch_brightness;
+};
+
+static struct aat1290_led *fled_cdev_to_led(
+ struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct aat1290_led, fled_cdev);
+}
+
+static struct led_classdev_flash *led_cdev_to_fled_cdev(
+ struct led_classdev *led_cdev)
+{
+ return container_of(led_cdev, struct led_classdev_flash, led_cdev);
+}
+
+static void aat1290_as2cwire_write(struct aat1290_led *led, int addr, int value)
+{
+ int i;
+
+ gpiod_direction_output(led->gpio_fl_en, 0);
+ gpiod_direction_output(led->gpio_en_set, 0);
+
+ udelay(AAT1290_FLEN_OFF_DELAY_TIME_US);
+
+ /* write address */
+ for (i = 0; i < addr; ++i) {
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpiod_direction_output(led->gpio_en_set, 0);
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpiod_direction_output(led->gpio_en_set, 1);
+ }
+
+ usleep_range(AAT1290_LATCH_TIME_MIN_US, AAT1290_LATCH_TIME_MAX_US);
+
+ /* write data */
+ for (i = 0; i < value; ++i) {
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpiod_direction_output(led->gpio_en_set, 0);
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpiod_direction_output(led->gpio_en_set, 1);
+ }
+
+ usleep_range(AAT1290_LATCH_TIME_MIN_US, AAT1290_LATCH_TIME_MAX_US);
+}
+
+static void aat1290_set_flash_safety_timer(struct aat1290_led *led,
+ unsigned int micro_sec)
+{
+ struct led_classdev_flash *fled_cdev = &led->fled_cdev;
+ struct led_flash_setting *flash_tm = &fled_cdev->timeout;
+ int flash_tm_reg = AAT1290_FLASH_TM_NUM_LEVELS -
+ (micro_sec / flash_tm->step) + 1;
+
+ aat1290_as2cwire_write(led, AAT1290_FLASH_SAFETY_TIMER_ADDR,
+ flash_tm_reg);
+}
+
+/* LED subsystem callbacks */
+
+static int aat1290_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = led_cdev_to_fled_cdev(led_cdev);
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+
+ mutex_lock(&led->lock);
+
+ if (brightness == 0) {
+ gpiod_direction_output(led->gpio_fl_en, 0);
+ gpiod_direction_output(led->gpio_en_set, 0);
+ led->movie_mode = false;
+ } else {
+ if (!led->movie_mode) {
+ aat1290_as2cwire_write(led,
+ AAT1290_MM_CURRENT_RATIO_ADDR,
+ AAT1290_MM_TO_FL_1_92);
+ led->movie_mode = true;
+ }
+
+ aat1290_as2cwire_write(led, AAT1290_MOVIE_MODE_CURRENT_ADDR,
+ AAT1290_MAX_MM_CURR_PERCENT_0 - brightness);
+ aat1290_as2cwire_write(led, AAT1290_MOVIE_MODE_CONFIG_ADDR,
+ AAT1290_MOVIE_MODE_ON);
+ }
+
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int aat1290_led_flash_strobe_set(struct led_classdev_flash *fled_cdev,
+ bool state)
+
+{
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+ struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+ struct led_flash_setting *timeout = &fled_cdev->timeout;
+
+ mutex_lock(&led->lock);
+
+ if (state) {
+ aat1290_set_flash_safety_timer(led, timeout->val);
+ gpiod_direction_output(led->gpio_fl_en, 1);
+ } else {
+ gpiod_direction_output(led->gpio_fl_en, 0);
+ gpiod_direction_output(led->gpio_en_set, 0);
+ }
+
+ /*
+ * To reenter movie mode after a flash event the part must be cycled
+ * off and back on to reset the movie mode and reprogrammed via the
+ * AS2Cwire. Therefore the brightness and movie_mode properties needs
+ * to be updated here to reflect the actual state.
+ */
+ led_cdev->brightness = 0;
+ led->movie_mode = false;
+
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int aat1290_led_flash_timeout_set(struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ /*
+ * Don't do anything - flash timeout is cached in the led-class-flash
+ * core and will be applied in the strobe_set op, as writing the
+ * safety timer register spuriously turns the torch mode on.
+ */
+
+ return 0;
+}
+
+static int aat1290_led_parse_dt(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg,
+ struct device_node **sub_node)
+{
+ struct device *dev = &led->pdev->dev;
+ struct device_node *child_node;
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+ struct pinctrl *pinctrl;
+#endif
+ int ret = 0;
+
+ led->gpio_fl_en = devm_gpiod_get(dev, "flen", GPIOD_ASIS);
+ if (IS_ERR(led->gpio_fl_en)) {
+ ret = PTR_ERR(led->gpio_fl_en);
+ dev_err(dev, "Unable to claim gpio \"flen\".\n");
+ return ret;
+ }
+
+ led->gpio_en_set = devm_gpiod_get(dev, "enset", GPIOD_ASIS);
+ if (IS_ERR(led->gpio_en_set)) {
+ ret = PTR_ERR(led->gpio_en_set);
+ dev_err(dev, "Unable to claim gpio \"enset\".\n");
+ return ret;
+ }
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+ pinctrl = devm_pinctrl_get_select_default(&led->pdev->dev);
+ if (IS_ERR(pinctrl)) {
+ cfg->has_external_strobe = false;
+ dev_info(dev,
+ "No support for external strobe detected.\n");
+ } else {
+ cfg->has_external_strobe = true;
+ }
+#endif
+
+ child_node = of_get_next_available_child(dev_of_node(dev), NULL);
+ if (!child_node) {
+ dev_err(dev, "No DT child node found for connected LED.\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(child_node, "led-max-microamp",
+ &cfg->max_mm_current);
+ /*
+ * led-max-microamp will default to 1/20 of flash-max-microamp
+ * in case it is missing.
+ */
+ if (ret < 0)
+ dev_warn(dev,
+ "led-max-microamp DT property missing\n");
+
+ ret = of_property_read_u32(child_node, "flash-max-microamp",
+ &cfg->max_flash_current);
+ if (ret < 0) {
+ dev_err(dev,
+ "flash-max-microamp DT property missing\n");
+ goto err_parse_dt;
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-timeout-us",
+ &cfg->max_flash_tm);
+ if (ret < 0) {
+ dev_err(dev,
+ "flash-max-timeout-us DT property missing\n");
+ goto err_parse_dt;
+ }
+
+ *sub_node = child_node;
+
+err_parse_dt:
+ of_node_put(child_node);
+
+ return ret;
+}
+
+static void aat1290_led_validate_mm_current(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg)
+{
+ int i, b = 0, e = AAT1290_MM_CURRENT_SCALE_SIZE;
+
+ while (e - b > 1) {
+ i = b + (e - b) / 2;
+ if (cfg->max_mm_current < led->mm_current_scale[i])
+ e = i;
+ else
+ b = i;
+ }
+
+ cfg->max_mm_current = led->mm_current_scale[b];
+ cfg->max_brightness = b + 1;
+}
+
+static int init_mm_current_scale(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg)
+{
+ static const int max_mm_current_percent[] = {
+ 20, 22, 25, 28, 32, 36, 40, 45, 50, 56,
+ 63, 71, 79, 89, 100
+ };
+ int i, max_mm_current =
+ AAT1290_MAX_MM_CURRENT(cfg->max_flash_current);
+
+ led->mm_current_scale = devm_kzalloc(&led->pdev->dev,
+ sizeof(max_mm_current_percent),
+ GFP_KERNEL);
+ if (!led->mm_current_scale)
+ return -ENOMEM;
+
+ for (i = 0; i < AAT1290_MM_CURRENT_SCALE_SIZE; ++i)
+ led->mm_current_scale[i] = max_mm_current *
+ max_mm_current_percent[i] / 100;
+
+ return 0;
+}
+
+static int aat1290_led_get_configuration(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg,
+ struct device_node **sub_node)
+{
+ int ret;
+
+ ret = aat1290_led_parse_dt(led, cfg, sub_node);
+ if (ret < 0)
+ return ret;
+ /*
+ * Init non-linear movie mode current scale basing
+ * on the max flash current from led configuration.
+ */
+ ret = init_mm_current_scale(led, cfg);
+ if (ret < 0)
+ return ret;
+
+ aat1290_led_validate_mm_current(led, cfg);
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+#else
+ devm_kfree(&led->pdev->dev, led->mm_current_scale);
+#endif
+
+ return 0;
+}
+
+static void aat1290_init_flash_timeout(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg)
+{
+ struct led_classdev_flash *fled_cdev = &led->fled_cdev;
+ struct led_flash_setting *setting;
+
+ /* Init flash timeout setting */
+ setting = &fled_cdev->timeout;
+ setting->min = cfg->max_flash_tm / AAT1290_FLASH_TM_NUM_LEVELS;
+ setting->max = cfg->max_flash_tm;
+ setting->step = setting->min;
+ setting->val = setting->max;
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static enum led_brightness aat1290_intensity_to_brightness(
+ struct v4l2_flash *v4l2_flash,
+ s32 intensity)
+{
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+ int i;
+
+ for (i = AAT1290_MM_CURRENT_SCALE_SIZE - 1; i >= 0; --i)
+ if (intensity >= led->mm_current_scale[i])
+ return i + 1;
+
+ return 1;
+}
+
+static s32 aat1290_brightness_to_intensity(struct v4l2_flash *v4l2_flash,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+
+ return led->mm_current_scale[brightness - 1];
+}
+
+static int aat1290_led_external_strobe_set(struct v4l2_flash *v4l2_flash,
+ bool enable)
+{
+ struct aat1290_led *led = fled_cdev_to_led(v4l2_flash->fled_cdev);
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+ struct pinctrl *pinctrl;
+
+ gpiod_direction_output(led->gpio_fl_en, 0);
+ gpiod_direction_output(led->gpio_en_set, 0);
+
+ led->movie_mode = false;
+ led_cdev->brightness = 0;
+
+ pinctrl = devm_pinctrl_get_select(&led->pdev->dev,
+ enable ? "isp" : "host");
+ if (IS_ERR(pinctrl)) {
+ dev_warn(&led->pdev->dev, "Unable to switch strobe source.\n");
+ return PTR_ERR(pinctrl);
+ }
+
+ return 0;
+}
+
+static void aat1290_init_v4l2_flash_config(struct aat1290_led *led,
+ struct aat1290_led_config_data *led_cfg,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+ struct led_classdev *led_cdev = &led->fled_cdev.led_cdev;
+ struct led_flash_setting *s;
+
+ strlcpy(v4l2_sd_cfg->dev_name, led_cdev->dev->kobj.name,
+ sizeof(v4l2_sd_cfg->dev_name));
+
+ s = &v4l2_sd_cfg->intensity;
+ s->min = led->mm_current_scale[0];
+ s->max = led_cfg->max_mm_current;
+ s->step = 1;
+ s->val = s->max;
+
+ v4l2_sd_cfg->has_external_strobe = led_cfg->has_external_strobe;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = aat1290_led_external_strobe_set,
+ .intensity_to_led_brightness = aat1290_intensity_to_brightness,
+ .led_brightness_to_intensity = aat1290_brightness_to_intensity,
+};
+#else
+static inline void aat1290_init_v4l2_flash_config(struct aat1290_led *led,
+ struct aat1290_led_config_data *led_cfg,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+static const struct v4l2_flash_ops v4l2_flash_ops;
+#endif
+
+static const struct led_flash_ops flash_ops = {
+ .strobe_set = aat1290_led_flash_strobe_set,
+ .timeout_set = aat1290_led_flash_timeout_set,
+};
+
+static int aat1290_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *sub_node = NULL;
+ struct aat1290_led *led;
+ struct led_classdev *led_cdev;
+ struct led_classdev_flash *fled_cdev;
+ struct led_init_data init_data = {};
+ struct aat1290_led_config_data led_cfg = {};
+ struct v4l2_flash_config v4l2_sd_cfg = {};
+ int ret;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pdev = pdev;
+ platform_set_drvdata(pdev, led);
+
+ fled_cdev = &led->fled_cdev;
+ fled_cdev->ops = &flash_ops;
+ led_cdev = &fled_cdev->led_cdev;
+
+ ret = aat1290_led_get_configuration(led, &led_cfg, &sub_node);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&led->lock);
+
+ /* Initialize LED Flash class device */
+ led_cdev->brightness_set_blocking = aat1290_led_brightness_set;
+ led_cdev->max_brightness = led_cfg.max_brightness;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+
+ aat1290_init_flash_timeout(led, &led_cfg);
+
+ init_data.fwnode = of_fwnode_handle(sub_node);
+ init_data.devicename = AAT1290_NAME;
+
+ /* Register LED Flash class device */
+ ret = led_classdev_flash_register_ext(&pdev->dev, fled_cdev,
+ &init_data);
+ if (ret < 0)
+ goto err_flash_register;
+
+ aat1290_init_v4l2_flash_config(led, &led_cfg, &v4l2_sd_cfg);
+
+ /* Create V4L2 Flash subdev. */
+ led->v4l2_flash = v4l2_flash_init(dev, of_fwnode_handle(sub_node),
+ fled_cdev, &v4l2_flash_ops,
+ &v4l2_sd_cfg);
+ if (IS_ERR(led->v4l2_flash)) {
+ ret = PTR_ERR(led->v4l2_flash);
+ goto error_v4l2_flash_init;
+ }
+
+ return 0;
+
+error_v4l2_flash_init:
+ led_classdev_flash_unregister(fled_cdev);
+err_flash_register:
+ mutex_destroy(&led->lock);
+
+ return ret;
+}
+
+static int aat1290_led_remove(struct platform_device *pdev)
+{
+ struct aat1290_led *led = platform_get_drvdata(pdev);
+
+ v4l2_flash_release(led->v4l2_flash);
+ led_classdev_flash_unregister(&led->fled_cdev);
+
+ mutex_destroy(&led->lock);
+
+ return 0;
+}
+
+static const struct of_device_id aat1290_led_dt_match[] = {
+ { .compatible = "skyworks,aat1290" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, aat1290_led_dt_match);
+
+static struct platform_driver aat1290_led_driver = {
+ .probe = aat1290_led_probe,
+ .remove = aat1290_led_remove,
+ .driver = {
+ .name = "aat1290",
+ .of_match_table = aat1290_led_dt_match,
+ },
+};
+
+module_platform_driver(aat1290_led_driver);
+
+MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
+MODULE_DESCRIPTION("Skyworks Current Regulator for Flash LEDs");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-as3645a.c b/drivers/leds/flash/leds-as3645a.c
new file mode 100644
index 000000000..bb2249771
--- /dev/null
+++ b/drivers/leds/flash/leds-as3645a.c
@@ -0,0 +1,772 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * drivers/leds/leds-as3645a.c - AS3645A and LM3555 flash controllers driver
+ *
+ * Copyright (C) 2008-2011 Nokia Corporation
+ * Copyright (c) 2011, 2017 Intel Corporation.
+ *
+ * Based on drivers/media/i2c/as3645a.c.
+ *
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/led-class-flash.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#include <media/v4l2-flash-led-class.h>
+
+#define AS_TIMER_US_TO_CODE(t) (((t) / 1000 - 100) / 50)
+#define AS_TIMER_CODE_TO_US(c) ((50 * (c) + 100) * 1000)
+
+/* Register definitions */
+
+/* Read-only Design info register: Reset state: xxxx 0001 */
+#define AS_DESIGN_INFO_REG 0x00
+#define AS_DESIGN_INFO_FACTORY(x) (((x) >> 4))
+#define AS_DESIGN_INFO_MODEL(x) ((x) & 0x0f)
+
+/* Read-only Version control register: Reset state: 0000 0000
+ * for first engineering samples
+ */
+#define AS_VERSION_CONTROL_REG 0x01
+#define AS_VERSION_CONTROL_RFU(x) (((x) >> 4))
+#define AS_VERSION_CONTROL_VERSION(x) ((x) & 0x0f)
+
+/* Read / Write (Indicator and timer register): Reset state: 0000 1111 */
+#define AS_INDICATOR_AND_TIMER_REG 0x02
+#define AS_INDICATOR_AND_TIMER_TIMEOUT_SHIFT 0
+#define AS_INDICATOR_AND_TIMER_VREF_SHIFT 4
+#define AS_INDICATOR_AND_TIMER_INDICATOR_SHIFT 6
+
+/* Read / Write (Current set register): Reset state: 0110 1001 */
+#define AS_CURRENT_SET_REG 0x03
+#define AS_CURRENT_ASSIST_LIGHT_SHIFT 0
+#define AS_CURRENT_LED_DET_ON (1 << 3)
+#define AS_CURRENT_FLASH_CURRENT_SHIFT 4
+
+/* Read / Write (Control register): Reset state: 1011 0100 */
+#define AS_CONTROL_REG 0x04
+#define AS_CONTROL_MODE_SETTING_SHIFT 0
+#define AS_CONTROL_STROBE_ON (1 << 2)
+#define AS_CONTROL_OUT_ON (1 << 3)
+#define AS_CONTROL_EXT_TORCH_ON (1 << 4)
+#define AS_CONTROL_STROBE_TYPE_EDGE (0 << 5)
+#define AS_CONTROL_STROBE_TYPE_LEVEL (1 << 5)
+#define AS_CONTROL_COIL_PEAK_SHIFT 6
+
+/* Read only (D3 is read / write) (Fault and info): Reset state: 0000 x000 */
+#define AS_FAULT_INFO_REG 0x05
+#define AS_FAULT_INFO_INDUCTOR_PEAK_LIMIT (1 << 1)
+#define AS_FAULT_INFO_INDICATOR_LED (1 << 2)
+#define AS_FAULT_INFO_LED_AMOUNT (1 << 3)
+#define AS_FAULT_INFO_TIMEOUT (1 << 4)
+#define AS_FAULT_INFO_OVER_TEMPERATURE (1 << 5)
+#define AS_FAULT_INFO_SHORT_CIRCUIT (1 << 6)
+#define AS_FAULT_INFO_OVER_VOLTAGE (1 << 7)
+
+/* Boost register */
+#define AS_BOOST_REG 0x0d
+#define AS_BOOST_CURRENT_DISABLE (0 << 0)
+#define AS_BOOST_CURRENT_ENABLE (1 << 0)
+
+/* Password register is used to unlock boost register writing */
+#define AS_PASSWORD_REG 0x0f
+#define AS_PASSWORD_UNLOCK_VALUE 0x55
+
+#define AS_NAME "as3645a"
+#define AS_I2C_ADDR (0x60 >> 1) /* W:0x60, R:0x61 */
+
+#define AS_FLASH_TIMEOUT_MIN 100000 /* us */
+#define AS_FLASH_TIMEOUT_MAX 850000
+#define AS_FLASH_TIMEOUT_STEP 50000
+
+#define AS_FLASH_INTENSITY_MIN 200000 /* uA */
+#define AS_FLASH_INTENSITY_MAX_1LED 500000
+#define AS_FLASH_INTENSITY_MAX_2LEDS 400000
+#define AS_FLASH_INTENSITY_STEP 20000
+
+#define AS_TORCH_INTENSITY_MIN 20000 /* uA */
+#define AS_TORCH_INTENSITY_MAX 160000
+#define AS_TORCH_INTENSITY_STEP 20000
+
+#define AS_INDICATOR_INTENSITY_MIN 0 /* uA */
+#define AS_INDICATOR_INTENSITY_MAX 10000
+#define AS_INDICATOR_INTENSITY_STEP 2500
+
+#define AS_PEAK_mA_MAX 2000
+#define AS_PEAK_mA_TO_REG(a) \
+ ((min_t(u32, AS_PEAK_mA_MAX, a) - 1250) / 250)
+
+/* LED numbers for Devicetree */
+#define AS_LED_FLASH 0
+#define AS_LED_INDICATOR 1
+
+enum as_mode {
+ AS_MODE_EXT_TORCH = 0 << AS_CONTROL_MODE_SETTING_SHIFT,
+ AS_MODE_INDICATOR = 1 << AS_CONTROL_MODE_SETTING_SHIFT,
+ AS_MODE_ASSIST = 2 << AS_CONTROL_MODE_SETTING_SHIFT,
+ AS_MODE_FLASH = 3 << AS_CONTROL_MODE_SETTING_SHIFT,
+};
+
+struct as3645a_config {
+ u32 flash_timeout_us;
+ u32 flash_max_ua;
+ u32 assist_max_ua;
+ u32 indicator_max_ua;
+ u32 voltage_reference;
+ u32 peak;
+};
+
+struct as3645a {
+ struct i2c_client *client;
+
+ struct mutex mutex;
+
+ struct led_classdev_flash fled;
+ struct led_classdev iled_cdev;
+
+ struct v4l2_flash *vf;
+ struct v4l2_flash *vfind;
+
+ struct fwnode_handle *flash_node;
+ struct fwnode_handle *indicator_node;
+
+ struct as3645a_config cfg;
+
+ enum as_mode mode;
+ unsigned int timeout;
+ unsigned int flash_current;
+ unsigned int assist_current;
+ unsigned int indicator_current;
+ enum v4l2_flash_strobe_source strobe_source;
+};
+
+#define fled_to_as3645a(__fled) container_of(__fled, struct as3645a, fled)
+#define iled_cdev_to_as3645a(__iled_cdev) \
+ container_of(__iled_cdev, struct as3645a, iled_cdev)
+
+/* Return negative errno else zero on success */
+static int as3645a_write(struct as3645a *flash, u8 addr, u8 val)
+{
+ struct i2c_client *client = flash->client;
+ int rval;
+
+ rval = i2c_smbus_write_byte_data(client, addr, val);
+
+ dev_dbg(&client->dev, "Write Addr:%02X Val:%02X %s\n", addr, val,
+ rval < 0 ? "fail" : "ok");
+
+ return rval;
+}
+
+/* Return negative errno else a data byte received from the device. */
+static int as3645a_read(struct as3645a *flash, u8 addr)
+{
+ struct i2c_client *client = flash->client;
+ int rval;
+
+ rval = i2c_smbus_read_byte_data(client, addr);
+
+ dev_dbg(&client->dev, "Read Addr:%02X Val:%02X %s\n", addr, rval,
+ rval < 0 ? "fail" : "ok");
+
+ return rval;
+}
+
+/* -----------------------------------------------------------------------------
+ * Hardware configuration and trigger
+ */
+
+/**
+ * as3645a_set_current - Set flash configuration registers
+ * @flash: The flash
+ *
+ * Configure the hardware with flash, assist and indicator currents, as well as
+ * flash timeout.
+ *
+ * Return 0 on success, or a negative error code if an I2C communication error
+ * occurred.
+ */
+static int as3645a_set_current(struct as3645a *flash)
+{
+ u8 val;
+
+ val = (flash->flash_current << AS_CURRENT_FLASH_CURRENT_SHIFT)
+ | (flash->assist_current << AS_CURRENT_ASSIST_LIGHT_SHIFT)
+ | AS_CURRENT_LED_DET_ON;
+
+ return as3645a_write(flash, AS_CURRENT_SET_REG, val);
+}
+
+static int as3645a_set_timeout(struct as3645a *flash)
+{
+ u8 val;
+
+ val = flash->timeout << AS_INDICATOR_AND_TIMER_TIMEOUT_SHIFT;
+
+ val |= (flash->cfg.voltage_reference
+ << AS_INDICATOR_AND_TIMER_VREF_SHIFT)
+ | ((flash->indicator_current ? flash->indicator_current - 1 : 0)
+ << AS_INDICATOR_AND_TIMER_INDICATOR_SHIFT);
+
+ return as3645a_write(flash, AS_INDICATOR_AND_TIMER_REG, val);
+}
+
+/**
+ * as3645a_set_control - Set flash control register
+ * @flash: The flash
+ * @mode: Desired output mode
+ * @on: Desired output state
+ *
+ * Configure the hardware with output mode and state.
+ *
+ * Return 0 on success, or a negative error code if an I2C communication error
+ * occurred.
+ */
+static int
+as3645a_set_control(struct as3645a *flash, enum as_mode mode, bool on)
+{
+ u8 reg;
+
+ /* Configure output parameters and operation mode. */
+ reg = (flash->cfg.peak << AS_CONTROL_COIL_PEAK_SHIFT)
+ | (on ? AS_CONTROL_OUT_ON : 0)
+ | mode;
+
+ if (mode == AS_MODE_FLASH &&
+ flash->strobe_source == V4L2_FLASH_STROBE_SOURCE_EXTERNAL)
+ reg |= AS_CONTROL_STROBE_TYPE_LEVEL
+ | AS_CONTROL_STROBE_ON;
+
+ return as3645a_write(flash, AS_CONTROL_REG, reg);
+}
+
+static int as3645a_get_fault(struct led_classdev_flash *fled, u32 *fault)
+{
+ struct as3645a *flash = fled_to_as3645a(fled);
+ int rval;
+
+ /* NOTE: reading register clears fault status */
+ rval = as3645a_read(flash, AS_FAULT_INFO_REG);
+ if (rval < 0)
+ return rval;
+
+ if (rval & AS_FAULT_INFO_INDUCTOR_PEAK_LIMIT)
+ *fault |= LED_FAULT_OVER_CURRENT;
+
+ if (rval & AS_FAULT_INFO_INDICATOR_LED)
+ *fault |= LED_FAULT_INDICATOR;
+
+ dev_dbg(&flash->client->dev, "%u connected LEDs\n",
+ rval & AS_FAULT_INFO_LED_AMOUNT ? 2 : 1);
+
+ if (rval & AS_FAULT_INFO_TIMEOUT)
+ *fault |= LED_FAULT_TIMEOUT;
+
+ if (rval & AS_FAULT_INFO_OVER_TEMPERATURE)
+ *fault |= LED_FAULT_OVER_TEMPERATURE;
+
+ if (rval & AS_FAULT_INFO_SHORT_CIRCUIT)
+ *fault |= LED_FAULT_OVER_CURRENT;
+
+ if (rval & AS_FAULT_INFO_OVER_VOLTAGE)
+ *fault |= LED_FAULT_INPUT_VOLTAGE;
+
+ return rval;
+}
+
+static unsigned int __as3645a_current_to_reg(unsigned int min, unsigned int max,
+ unsigned int step,
+ unsigned int val)
+{
+ if (val < min)
+ val = min;
+
+ if (val > max)
+ val = max;
+
+ return (val - min) / step;
+}
+
+static unsigned int as3645a_current_to_reg(struct as3645a *flash, bool is_flash,
+ unsigned int ua)
+{
+ if (is_flash)
+ return __as3645a_current_to_reg(AS_TORCH_INTENSITY_MIN,
+ flash->cfg.assist_max_ua,
+ AS_TORCH_INTENSITY_STEP, ua);
+ else
+ return __as3645a_current_to_reg(AS_FLASH_INTENSITY_MIN,
+ flash->cfg.flash_max_ua,
+ AS_FLASH_INTENSITY_STEP, ua);
+}
+
+static int as3645a_set_indicator_brightness(struct led_classdev *iled_cdev,
+ enum led_brightness brightness)
+{
+ struct as3645a *flash = iled_cdev_to_as3645a(iled_cdev);
+ int rval;
+
+ flash->indicator_current = brightness;
+
+ rval = as3645a_set_timeout(flash);
+ if (rval)
+ return rval;
+
+ return as3645a_set_control(flash, AS_MODE_INDICATOR, brightness);
+}
+
+static int as3645a_set_assist_brightness(struct led_classdev *fled_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled = lcdev_to_flcdev(fled_cdev);
+ struct as3645a *flash = fled_to_as3645a(fled);
+ int rval;
+
+ if (brightness) {
+ /* Register value 0 is 20 mA. */
+ flash->assist_current = brightness - 1;
+
+ rval = as3645a_set_current(flash);
+ if (rval)
+ return rval;
+ }
+
+ return as3645a_set_control(flash, AS_MODE_ASSIST, brightness);
+}
+
+static int as3645a_set_flash_brightness(struct led_classdev_flash *fled,
+ u32 brightness_ua)
+{
+ struct as3645a *flash = fled_to_as3645a(fled);
+
+ flash->flash_current = as3645a_current_to_reg(flash, true,
+ brightness_ua);
+
+ return as3645a_set_current(flash);
+}
+
+static int as3645a_set_flash_timeout(struct led_classdev_flash *fled,
+ u32 timeout_us)
+{
+ struct as3645a *flash = fled_to_as3645a(fled);
+
+ flash->timeout = AS_TIMER_US_TO_CODE(timeout_us);
+
+ return as3645a_set_timeout(flash);
+}
+
+static int as3645a_set_strobe(struct led_classdev_flash *fled, bool state)
+{
+ struct as3645a *flash = fled_to_as3645a(fled);
+
+ return as3645a_set_control(flash, AS_MODE_FLASH, state);
+}
+
+static const struct led_flash_ops as3645a_led_flash_ops = {
+ .flash_brightness_set = as3645a_set_flash_brightness,
+ .timeout_set = as3645a_set_flash_timeout,
+ .strobe_set = as3645a_set_strobe,
+ .fault_get = as3645a_get_fault,
+};
+
+static int as3645a_setup(struct as3645a *flash)
+{
+ struct device *dev = &flash->client->dev;
+ u32 fault = 0;
+ int rval;
+
+ /* clear errors */
+ rval = as3645a_read(flash, AS_FAULT_INFO_REG);
+ if (rval < 0)
+ return rval;
+
+ dev_dbg(dev, "Fault info: %02x\n", rval);
+
+ rval = as3645a_set_current(flash);
+ if (rval < 0)
+ return rval;
+
+ rval = as3645a_set_timeout(flash);
+ if (rval < 0)
+ return rval;
+
+ rval = as3645a_set_control(flash, AS_MODE_INDICATOR, false);
+ if (rval < 0)
+ return rval;
+
+ /* read status */
+ rval = as3645a_get_fault(&flash->fled, &fault);
+ if (rval < 0)
+ return rval;
+
+ dev_dbg(dev, "AS_INDICATOR_AND_TIMER_REG: %02x\n",
+ as3645a_read(flash, AS_INDICATOR_AND_TIMER_REG));
+ dev_dbg(dev, "AS_CURRENT_SET_REG: %02x\n",
+ as3645a_read(flash, AS_CURRENT_SET_REG));
+ dev_dbg(dev, "AS_CONTROL_REG: %02x\n",
+ as3645a_read(flash, AS_CONTROL_REG));
+
+ return rval & ~AS_FAULT_INFO_LED_AMOUNT ? -EIO : 0;
+}
+
+static int as3645a_detect(struct as3645a *flash)
+{
+ struct device *dev = &flash->client->dev;
+ int rval, man, model, rfu, version;
+ const char *vendor;
+
+ rval = as3645a_read(flash, AS_DESIGN_INFO_REG);
+ if (rval < 0) {
+ dev_err(dev, "can't read design info reg\n");
+ return rval;
+ }
+
+ man = AS_DESIGN_INFO_FACTORY(rval);
+ model = AS_DESIGN_INFO_MODEL(rval);
+
+ rval = as3645a_read(flash, AS_VERSION_CONTROL_REG);
+ if (rval < 0) {
+ dev_err(dev, "can't read version control reg\n");
+ return rval;
+ }
+
+ rfu = AS_VERSION_CONTROL_RFU(rval);
+ version = AS_VERSION_CONTROL_VERSION(rval);
+
+ /* Verify the chip model and version. */
+ if (model != 0x01 || rfu != 0x00) {
+ dev_err(dev, "AS3645A not detected (model %d rfu %d)\n",
+ model, rfu);
+ return -ENODEV;
+ }
+
+ switch (man) {
+ case 1:
+ vendor = "AMS, Austria Micro Systems";
+ break;
+ case 2:
+ vendor = "ADI, Analog Devices Inc.";
+ break;
+ case 3:
+ vendor = "NSC, National Semiconductor";
+ break;
+ case 4:
+ vendor = "NXP";
+ break;
+ case 5:
+ vendor = "TI, Texas Instrument";
+ break;
+ default:
+ vendor = "Unknown";
+ }
+
+ dev_info(dev, "Chip vendor: %s (%d) Version: %d\n", vendor,
+ man, version);
+
+ rval = as3645a_write(flash, AS_PASSWORD_REG, AS_PASSWORD_UNLOCK_VALUE);
+ if (rval < 0)
+ return rval;
+
+ return as3645a_write(flash, AS_BOOST_REG, AS_BOOST_CURRENT_DISABLE);
+}
+
+static int as3645a_parse_node(struct as3645a *flash,
+ struct fwnode_handle *fwnode)
+{
+ struct as3645a_config *cfg = &flash->cfg;
+ struct fwnode_handle *child;
+ int rval;
+
+ fwnode_for_each_child_node(fwnode, child) {
+ u32 id = 0;
+
+ fwnode_property_read_u32(child, "reg", &id);
+
+ switch (id) {
+ case AS_LED_FLASH:
+ flash->flash_node = child;
+ fwnode_handle_get(child);
+ break;
+ case AS_LED_INDICATOR:
+ flash->indicator_node = child;
+ fwnode_handle_get(child);
+ break;
+ default:
+ dev_warn(&flash->client->dev,
+ "unknown LED %u encountered, ignoring\n", id);
+ break;
+ }
+ }
+
+ if (!flash->flash_node) {
+ dev_err(&flash->client->dev, "can't find flash node\n");
+ return -ENODEV;
+ }
+
+ rval = fwnode_property_read_u32(flash->flash_node, "flash-timeout-us",
+ &cfg->flash_timeout_us);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read flash-timeout-us property for flash\n");
+ goto out_err;
+ }
+
+ rval = fwnode_property_read_u32(flash->flash_node, "flash-max-microamp",
+ &cfg->flash_max_ua);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read flash-max-microamp property for flash\n");
+ goto out_err;
+ }
+
+ rval = fwnode_property_read_u32(flash->flash_node, "led-max-microamp",
+ &cfg->assist_max_ua);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read led-max-microamp property for flash\n");
+ goto out_err;
+ }
+
+ fwnode_property_read_u32(flash->flash_node, "voltage-reference",
+ &cfg->voltage_reference);
+
+ fwnode_property_read_u32(flash->flash_node, "ams,input-max-microamp",
+ &cfg->peak);
+ cfg->peak = AS_PEAK_mA_TO_REG(cfg->peak);
+
+ if (!flash->indicator_node) {
+ dev_warn(&flash->client->dev,
+ "can't find indicator node\n");
+ rval = -ENODEV;
+ goto out_err;
+ }
+
+
+ rval = fwnode_property_read_u32(flash->indicator_node,
+ "led-max-microamp",
+ &cfg->indicator_max_ua);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read led-max-microamp property for indicator\n");
+ goto out_err;
+ }
+
+ return 0;
+
+out_err:
+ fwnode_handle_put(flash->flash_node);
+ fwnode_handle_put(flash->indicator_node);
+
+ return rval;
+}
+
+static int as3645a_led_class_setup(struct as3645a *flash)
+{
+ struct led_classdev *fled_cdev = &flash->fled.led_cdev;
+ struct led_classdev *iled_cdev = &flash->iled_cdev;
+ struct led_init_data init_data = {};
+ struct led_flash_setting *cfg;
+ int rval;
+
+ iled_cdev->brightness_set_blocking = as3645a_set_indicator_brightness;
+ iled_cdev->max_brightness =
+ flash->cfg.indicator_max_ua / AS_INDICATOR_INTENSITY_STEP;
+ iled_cdev->flags = LED_CORE_SUSPENDRESUME;
+
+ init_data.fwnode = flash->indicator_node;
+ init_data.devicename = AS_NAME;
+ init_data.default_label = "indicator";
+
+ rval = led_classdev_register_ext(&flash->client->dev, iled_cdev,
+ &init_data);
+ if (rval < 0)
+ return rval;
+
+ cfg = &flash->fled.brightness;
+ cfg->min = AS_FLASH_INTENSITY_MIN;
+ cfg->max = flash->cfg.flash_max_ua;
+ cfg->step = AS_FLASH_INTENSITY_STEP;
+ cfg->val = flash->cfg.flash_max_ua;
+
+ cfg = &flash->fled.timeout;
+ cfg->min = AS_FLASH_TIMEOUT_MIN;
+ cfg->max = flash->cfg.flash_timeout_us;
+ cfg->step = AS_FLASH_TIMEOUT_STEP;
+ cfg->val = flash->cfg.flash_timeout_us;
+
+ flash->fled.ops = &as3645a_led_flash_ops;
+
+ fled_cdev->brightness_set_blocking = as3645a_set_assist_brightness;
+ /* Value 0 is off in LED class. */
+ fled_cdev->max_brightness =
+ as3645a_current_to_reg(flash, false,
+ flash->cfg.assist_max_ua) + 1;
+ fled_cdev->flags = LED_DEV_CAP_FLASH | LED_CORE_SUSPENDRESUME;
+
+ init_data.fwnode = flash->flash_node;
+ init_data.devicename = AS_NAME;
+ init_data.default_label = "flash";
+
+ rval = led_classdev_flash_register_ext(&flash->client->dev,
+ &flash->fled, &init_data);
+ if (rval)
+ goto out_err;
+
+ return rval;
+
+out_err:
+ led_classdev_unregister(iled_cdev);
+ dev_err(&flash->client->dev,
+ "led_classdev_flash_register() failed, error %d\n",
+ rval);
+ return rval;
+}
+
+static int as3645a_v4l2_setup(struct as3645a *flash)
+{
+ struct led_classdev_flash *fled = &flash->fled;
+ struct led_classdev *led = &fled->led_cdev;
+ struct v4l2_flash_config cfg = {
+ .intensity = {
+ .min = AS_TORCH_INTENSITY_MIN,
+ .max = flash->cfg.assist_max_ua,
+ .step = AS_TORCH_INTENSITY_STEP,
+ .val = flash->cfg.assist_max_ua,
+ },
+ };
+ struct v4l2_flash_config cfgind = {
+ .intensity = {
+ .min = AS_INDICATOR_INTENSITY_MIN,
+ .max = flash->cfg.indicator_max_ua,
+ .step = AS_INDICATOR_INTENSITY_STEP,
+ .val = flash->cfg.indicator_max_ua,
+ },
+ };
+
+ strlcpy(cfg.dev_name, led->dev->kobj.name, sizeof(cfg.dev_name));
+ strlcpy(cfgind.dev_name, flash->iled_cdev.dev->kobj.name,
+ sizeof(cfgind.dev_name));
+
+ flash->vf = v4l2_flash_init(
+ &flash->client->dev, flash->flash_node, &flash->fled, NULL,
+ &cfg);
+ if (IS_ERR(flash->vf))
+ return PTR_ERR(flash->vf);
+
+ flash->vfind = v4l2_flash_indicator_init(
+ &flash->client->dev, flash->indicator_node, &flash->iled_cdev,
+ &cfgind);
+ if (IS_ERR(flash->vfind)) {
+ v4l2_flash_release(flash->vf);
+ return PTR_ERR(flash->vfind);
+ }
+
+ return 0;
+}
+
+static int as3645a_probe(struct i2c_client *client)
+{
+ struct as3645a *flash;
+ int rval;
+
+ if (!dev_fwnode(&client->dev))
+ return -ENODEV;
+
+ flash = devm_kzalloc(&client->dev, sizeof(*flash), GFP_KERNEL);
+ if (flash == NULL)
+ return -ENOMEM;
+
+ flash->client = client;
+
+ rval = as3645a_parse_node(flash, dev_fwnode(&client->dev));
+ if (rval < 0)
+ return rval;
+
+ rval = as3645a_detect(flash);
+ if (rval < 0)
+ goto out_put_nodes;
+
+ mutex_init(&flash->mutex);
+ i2c_set_clientdata(client, flash);
+
+ rval = as3645a_setup(flash);
+ if (rval)
+ goto out_mutex_destroy;
+
+ rval = as3645a_led_class_setup(flash);
+ if (rval)
+ goto out_mutex_destroy;
+
+ rval = as3645a_v4l2_setup(flash);
+ if (rval)
+ goto out_led_classdev_flash_unregister;
+
+ return 0;
+
+out_led_classdev_flash_unregister:
+ led_classdev_flash_unregister(&flash->fled);
+
+out_mutex_destroy:
+ mutex_destroy(&flash->mutex);
+
+out_put_nodes:
+ fwnode_handle_put(flash->flash_node);
+ fwnode_handle_put(flash->indicator_node);
+
+ return rval;
+}
+
+static void as3645a_remove(struct i2c_client *client)
+{
+ struct as3645a *flash = i2c_get_clientdata(client);
+
+ as3645a_set_control(flash, AS_MODE_EXT_TORCH, false);
+
+ v4l2_flash_release(flash->vf);
+ v4l2_flash_release(flash->vfind);
+
+ led_classdev_flash_unregister(&flash->fled);
+ led_classdev_unregister(&flash->iled_cdev);
+
+ mutex_destroy(&flash->mutex);
+
+ fwnode_handle_put(flash->flash_node);
+ fwnode_handle_put(flash->indicator_node);
+}
+
+static const struct i2c_device_id as3645a_id_table[] = {
+ { AS_NAME, 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, as3645a_id_table);
+
+static const struct of_device_id as3645a_of_table[] = {
+ { .compatible = "ams,as3645a" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, as3645a_of_table);
+
+static struct i2c_driver as3645a_i2c_driver = {
+ .driver = {
+ .of_match_table = as3645a_of_table,
+ .name = AS_NAME,
+ },
+ .probe_new = as3645a_probe,
+ .remove = as3645a_remove,
+ .id_table = as3645a_id_table,
+};
+
+module_i2c_driver(as3645a_i2c_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>");
+MODULE_DESCRIPTION("LED flash driver for AS3645A, LM3555 and their clones");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-ktd2692.c b/drivers/leds/flash/leds-ktd2692.c
new file mode 100644
index 000000000..670f3bf2e
--- /dev/null
+++ b/drivers/leds/flash/leds-ktd2692.c
@@ -0,0 +1,419 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED driver : leds-ktd2692.c
+ *
+ * Copyright (C) 2015 Samsung Electronics
+ * Ingi Kim <ingi2.kim@samsung.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+/* Value related the movie mode */
+#define KTD2692_MOVIE_MODE_CURRENT_LEVELS 16
+#define KTD2692_MM_TO_FL_RATIO(x) ((x) / 3)
+#define KTD2692_MM_MIN_CURR_THRESHOLD_SCALE 8
+
+/* Value related the flash mode */
+#define KTD2692_FLASH_MODE_TIMEOUT_LEVELS 8
+#define KTD2692_FLASH_MODE_TIMEOUT_DISABLE 0
+#define KTD2692_FLASH_MODE_CURR_PERCENT(x) (((x) * 16) / 100)
+
+/* Macro for getting offset of flash timeout */
+#define GET_TIMEOUT_OFFSET(timeout, step) ((timeout) / (step))
+
+/* Base register address */
+#define KTD2692_REG_LVP_BASE 0x00
+#define KTD2692_REG_FLASH_TIMEOUT_BASE 0x20
+#define KTD2692_REG_MM_MIN_CURR_THRESHOLD_BASE 0x40
+#define KTD2692_REG_MOVIE_CURRENT_BASE 0x60
+#define KTD2692_REG_FLASH_CURRENT_BASE 0x80
+#define KTD2692_REG_MODE_BASE 0xA0
+
+/* Set bit coding time for expresswire interface */
+#define KTD2692_TIME_RESET_US 700
+#define KTD2692_TIME_DATA_START_TIME_US 10
+#define KTD2692_TIME_HIGH_END_OF_DATA_US 350
+#define KTD2692_TIME_LOW_END_OF_DATA_US 10
+#define KTD2692_TIME_SHORT_BITSET_US 4
+#define KTD2692_TIME_LONG_BITSET_US 12
+
+/* KTD2692 default length of name */
+#define KTD2692_NAME_LENGTH 20
+
+enum ktd2692_bitset {
+ KTD2692_LOW = 0,
+ KTD2692_HIGH,
+};
+
+/* Movie / Flash Mode Control */
+enum ktd2692_led_mode {
+ KTD2692_MODE_DISABLE = 0, /* default */
+ KTD2692_MODE_MOVIE,
+ KTD2692_MODE_FLASH,
+};
+
+struct ktd2692_led_config_data {
+ /* maximum LED current in movie mode */
+ u32 movie_max_microamp;
+ /* maximum LED current in flash mode */
+ u32 flash_max_microamp;
+ /* maximum flash timeout */
+ u32 flash_max_timeout;
+ /* max LED brightness level */
+ enum led_brightness max_brightness;
+};
+
+struct ktd2692_context {
+ /* Related LED Flash class device */
+ struct led_classdev_flash fled_cdev;
+
+ /* secures access to the device */
+ struct mutex lock;
+ struct regulator *regulator;
+
+ struct gpio_desc *aux_gpio;
+ struct gpio_desc *ctrl_gpio;
+
+ enum ktd2692_led_mode mode;
+ enum led_brightness torch_brightness;
+};
+
+static struct ktd2692_context *fled_cdev_to_led(
+ struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct ktd2692_context, fled_cdev);
+}
+
+static void ktd2692_expresswire_start(struct ktd2692_context *led)
+{
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH);
+ udelay(KTD2692_TIME_DATA_START_TIME_US);
+}
+
+static void ktd2692_expresswire_reset(struct ktd2692_context *led)
+{
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW);
+ udelay(KTD2692_TIME_RESET_US);
+}
+
+static void ktd2692_expresswire_end(struct ktd2692_context *led)
+{
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW);
+ udelay(KTD2692_TIME_LOW_END_OF_DATA_US);
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH);
+ udelay(KTD2692_TIME_HIGH_END_OF_DATA_US);
+}
+
+static void ktd2692_expresswire_set_bit(struct ktd2692_context *led, bool bit)
+{
+ /*
+ * The Low Bit(0) and High Bit(1) is based on a time detection
+ * algorithm between time low and time high
+ * Time_(L_LB) : Low time of the Low Bit(0)
+ * Time_(H_LB) : High time of the LOW Bit(0)
+ * Time_(L_HB) : Low time of the High Bit(1)
+ * Time_(H_HB) : High time of the High Bit(1)
+ *
+ * It can be simplified to:
+ * Low Bit(0) : 2 * Time_(H_LB) < Time_(L_LB)
+ * High Bit(1) : 2 * Time_(L_HB) < Time_(H_HB)
+ * HIGH ___ ____ _.. _________ ___
+ * |_________| |_.. |____| |__|
+ * LOW <L_LB> <H_LB> <L_HB> <H_HB>
+ * [ Low Bit (0) ] [ High Bit(1) ]
+ */
+ if (bit) {
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW);
+ udelay(KTD2692_TIME_SHORT_BITSET_US);
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH);
+ udelay(KTD2692_TIME_LONG_BITSET_US);
+ } else {
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW);
+ udelay(KTD2692_TIME_LONG_BITSET_US);
+ gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH);
+ udelay(KTD2692_TIME_SHORT_BITSET_US);
+ }
+}
+
+static void ktd2692_expresswire_write(struct ktd2692_context *led, u8 value)
+{
+ int i;
+
+ ktd2692_expresswire_start(led);
+ for (i = 7; i >= 0; i--)
+ ktd2692_expresswire_set_bit(led, value & BIT(i));
+ ktd2692_expresswire_end(led);
+}
+
+static int ktd2692_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct ktd2692_context *led = fled_cdev_to_led(fled_cdev);
+
+ mutex_lock(&led->lock);
+
+ if (brightness == LED_OFF) {
+ led->mode = KTD2692_MODE_DISABLE;
+ gpiod_direction_output(led->aux_gpio, KTD2692_LOW);
+ } else {
+ ktd2692_expresswire_write(led, brightness |
+ KTD2692_REG_MOVIE_CURRENT_BASE);
+ led->mode = KTD2692_MODE_MOVIE;
+ }
+
+ ktd2692_expresswire_write(led, led->mode | KTD2692_REG_MODE_BASE);
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int ktd2692_led_flash_strobe_set(struct led_classdev_flash *fled_cdev,
+ bool state)
+{
+ struct ktd2692_context *led = fled_cdev_to_led(fled_cdev);
+ struct led_flash_setting *timeout = &fled_cdev->timeout;
+ u32 flash_tm_reg;
+
+ mutex_lock(&led->lock);
+
+ if (state) {
+ flash_tm_reg = GET_TIMEOUT_OFFSET(timeout->val, timeout->step);
+ ktd2692_expresswire_write(led, flash_tm_reg
+ | KTD2692_REG_FLASH_TIMEOUT_BASE);
+
+ led->mode = KTD2692_MODE_FLASH;
+ gpiod_direction_output(led->aux_gpio, KTD2692_HIGH);
+ } else {
+ led->mode = KTD2692_MODE_DISABLE;
+ gpiod_direction_output(led->aux_gpio, KTD2692_LOW);
+ }
+
+ ktd2692_expresswire_write(led, led->mode | KTD2692_REG_MODE_BASE);
+
+ fled_cdev->led_cdev.brightness = LED_OFF;
+ led->mode = KTD2692_MODE_DISABLE;
+
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int ktd2692_led_flash_timeout_set(struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ return 0;
+}
+
+static void ktd2692_init_movie_current_max(struct ktd2692_led_config_data *cfg)
+{
+ u32 offset, step;
+ u32 movie_current_microamp;
+
+ offset = KTD2692_MOVIE_MODE_CURRENT_LEVELS;
+ step = KTD2692_MM_TO_FL_RATIO(cfg->flash_max_microamp)
+ / KTD2692_MOVIE_MODE_CURRENT_LEVELS;
+
+ do {
+ movie_current_microamp = step * offset;
+ offset--;
+ } while ((movie_current_microamp > cfg->movie_max_microamp) &&
+ (offset > 0));
+
+ cfg->max_brightness = offset;
+}
+
+static void ktd2692_init_flash_timeout(struct led_classdev_flash *fled_cdev,
+ struct ktd2692_led_config_data *cfg)
+{
+ struct led_flash_setting *setting;
+
+ setting = &fled_cdev->timeout;
+ setting->min = KTD2692_FLASH_MODE_TIMEOUT_DISABLE;
+ setting->max = cfg->flash_max_timeout;
+ setting->step = cfg->flash_max_timeout
+ / (KTD2692_FLASH_MODE_TIMEOUT_LEVELS - 1);
+ setting->val = cfg->flash_max_timeout;
+}
+
+static void ktd2692_setup(struct ktd2692_context *led)
+{
+ led->mode = KTD2692_MODE_DISABLE;
+ ktd2692_expresswire_reset(led);
+ gpiod_direction_output(led->aux_gpio, KTD2692_LOW);
+
+ ktd2692_expresswire_write(led, (KTD2692_MM_MIN_CURR_THRESHOLD_SCALE - 1)
+ | KTD2692_REG_MM_MIN_CURR_THRESHOLD_BASE);
+ ktd2692_expresswire_write(led, KTD2692_FLASH_MODE_CURR_PERCENT(45)
+ | KTD2692_REG_FLASH_CURRENT_BASE);
+}
+
+static void regulator_disable_action(void *_data)
+{
+ struct device *dev = _data;
+ struct ktd2692_context *led = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regulator_disable(led->regulator);
+ if (ret)
+ dev_err(dev, "Failed to disable supply: %d\n", ret);
+}
+
+static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev,
+ struct ktd2692_led_config_data *cfg)
+{
+ struct device_node *np = dev_of_node(dev);
+ struct device_node *child_node;
+ int ret;
+
+ if (!np)
+ return -ENXIO;
+
+ led->ctrl_gpio = devm_gpiod_get(dev, "ctrl", GPIOD_ASIS);
+ ret = PTR_ERR_OR_ZERO(led->ctrl_gpio);
+ if (ret)
+ return dev_err_probe(dev, ret, "cannot get ctrl-gpios\n");
+
+ led->aux_gpio = devm_gpiod_get_optional(dev, "aux", GPIOD_ASIS);
+ if (IS_ERR(led->aux_gpio))
+ return dev_err_probe(dev, PTR_ERR(led->aux_gpio), "cannot get aux-gpios\n");
+
+ led->regulator = devm_regulator_get(dev, "vin");
+ if (IS_ERR(led->regulator))
+ led->regulator = NULL;
+
+ if (led->regulator) {
+ ret = regulator_enable(led->regulator);
+ if (ret) {
+ dev_err(dev, "Failed to enable supply: %d\n", ret);
+ } else {
+ ret = devm_add_action_or_reset(dev,
+ regulator_disable_action, dev);
+ if (ret)
+ return ret;
+ }
+ }
+
+ child_node = of_get_next_available_child(np, NULL);
+ if (!child_node) {
+ dev_err(dev, "No DT child node found for connected LED.\n");
+ return -EINVAL;
+ }
+
+ led->fled_cdev.led_cdev.name =
+ of_get_property(child_node, "label", NULL) ? : child_node->name;
+
+ ret = of_property_read_u32(child_node, "led-max-microamp",
+ &cfg->movie_max_microamp);
+ if (ret) {
+ dev_err(dev, "failed to parse led-max-microamp\n");
+ goto err_parse_dt;
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-microamp",
+ &cfg->flash_max_microamp);
+ if (ret) {
+ dev_err(dev, "failed to parse flash-max-microamp\n");
+ goto err_parse_dt;
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-timeout-us",
+ &cfg->flash_max_timeout);
+ if (ret) {
+ dev_err(dev, "failed to parse flash-max-timeout-us\n");
+ goto err_parse_dt;
+ }
+
+err_parse_dt:
+ of_node_put(child_node);
+ return ret;
+}
+
+static const struct led_flash_ops flash_ops = {
+ .strobe_set = ktd2692_led_flash_strobe_set,
+ .timeout_set = ktd2692_led_flash_timeout_set,
+};
+
+static int ktd2692_probe(struct platform_device *pdev)
+{
+ struct ktd2692_context *led;
+ struct led_classdev *led_cdev;
+ struct led_classdev_flash *fled_cdev;
+ struct ktd2692_led_config_data led_cfg;
+ int ret;
+
+ led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ fled_cdev = &led->fled_cdev;
+ led_cdev = &fled_cdev->led_cdev;
+
+ ret = ktd2692_parse_dt(led, &pdev->dev, &led_cfg);
+ if (ret)
+ return ret;
+
+ ktd2692_init_flash_timeout(fled_cdev, &led_cfg);
+ ktd2692_init_movie_current_max(&led_cfg);
+
+ fled_cdev->ops = &flash_ops;
+
+ led_cdev->max_brightness = led_cfg.max_brightness;
+ led_cdev->brightness_set_blocking = ktd2692_led_brightness_set;
+ led_cdev->flags |= LED_CORE_SUSPENDRESUME | LED_DEV_CAP_FLASH;
+
+ mutex_init(&led->lock);
+
+ platform_set_drvdata(pdev, led);
+
+ ret = led_classdev_flash_register(&pdev->dev, fled_cdev);
+ if (ret) {
+ dev_err(&pdev->dev, "can't register LED %s\n", led_cdev->name);
+ mutex_destroy(&led->lock);
+ return ret;
+ }
+
+ ktd2692_setup(led);
+
+ return 0;
+}
+
+static int ktd2692_remove(struct platform_device *pdev)
+{
+ struct ktd2692_context *led = platform_get_drvdata(pdev);
+
+ led_classdev_flash_unregister(&led->fled_cdev);
+
+ mutex_destroy(&led->lock);
+
+ return 0;
+}
+
+static const struct of_device_id ktd2692_match[] = {
+ { .compatible = "kinetic,ktd2692", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ktd2692_match);
+
+static struct platform_driver ktd2692_driver = {
+ .driver = {
+ .name = "ktd2692",
+ .of_match_table = ktd2692_match,
+ },
+ .probe = ktd2692_probe,
+ .remove = ktd2692_remove,
+};
+
+module_platform_driver(ktd2692_driver);
+
+MODULE_AUTHOR("Ingi Kim <ingi2.kim@samsung.com>");
+MODULE_DESCRIPTION("Kinetic KTD2692 LED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-lm3601x.c b/drivers/leds/flash/leds-lm3601x.c
new file mode 100644
index 000000000..78730e066
--- /dev/null
+++ b/drivers/leds/flash/leds-lm3601x.c
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: GPL-2.0
+// Flash and torch driver for Texas Instruments LM3601X LED
+// Flash driver chip family
+// Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define LM3601X_LED_IR 0x0
+#define LM3601X_LED_TORCH 0x1
+
+/* Registers */
+#define LM3601X_ENABLE_REG 0x01
+#define LM3601X_CFG_REG 0x02
+#define LM3601X_LED_FLASH_REG 0x03
+#define LM3601X_LED_TORCH_REG 0x04
+#define LM3601X_FLAGS_REG 0x05
+#define LM3601X_DEV_ID_REG 0x06
+
+#define LM3601X_SW_RESET BIT(7)
+
+/* Enable Mode bits */
+#define LM3601X_MODE_STANDBY 0x00
+#define LM3601X_MODE_IR_DRV BIT(0)
+#define LM3601X_MODE_TORCH BIT(1)
+#define LM3601X_MODE_STROBE (BIT(0) | BIT(1))
+#define LM3601X_STRB_EN BIT(2)
+#define LM3601X_STRB_EDGE_TRIG BIT(3)
+#define LM3601X_IVFM_EN BIT(4)
+
+#define LM36010_BOOST_LIMIT_28 BIT(5)
+#define LM36010_BOOST_FREQ_4MHZ BIT(6)
+#define LM36010_BOOST_MODE_PASS BIT(7)
+
+/* Flag Mask */
+#define LM3601X_FLASH_TIME_OUT BIT(0)
+#define LM3601X_UVLO_FAULT BIT(1)
+#define LM3601X_THERM_SHUTDOWN BIT(2)
+#define LM3601X_THERM_CURR BIT(3)
+#define LM36010_CURR_LIMIT BIT(4)
+#define LM3601X_SHORT_FAULT BIT(5)
+#define LM3601X_IVFM_TRIP BIT(6)
+#define LM36010_OVP_FAULT BIT(7)
+
+#define LM3601X_MAX_TORCH_I_UA 376000
+#define LM3601X_MIN_TORCH_I_UA 2400
+#define LM3601X_TORCH_REG_DIV 2965
+
+#define LM3601X_MAX_STROBE_I_UA 1500000
+#define LM3601X_MIN_STROBE_I_UA 11000
+#define LM3601X_STROBE_REG_DIV 11800
+
+#define LM3601X_TIMEOUT_MASK 0x1e
+#define LM3601X_ENABLE_MASK (LM3601X_MODE_IR_DRV | LM3601X_MODE_TORCH)
+
+#define LM3601X_LOWER_STEP_US 40000
+#define LM3601X_UPPER_STEP_US 200000
+#define LM3601X_MIN_TIMEOUT_US 40000
+#define LM3601X_MAX_TIMEOUT_US 1600000
+#define LM3601X_TIMEOUT_XOVER_US 400000
+
+enum lm3601x_type {
+ CHIP_LM36010 = 0,
+ CHIP_LM36011,
+};
+
+/**
+ * struct lm3601x_led -
+ * @fled_cdev: flash LED class device pointer
+ * @client: Pointer to the I2C client
+ * @regmap: Devices register map
+ * @lock: Lock for reading/writing the device
+ * @led_name: LED label for the Torch or IR LED
+ * @flash_timeout: the timeout for the flash
+ * @last_flag: last known flags register value
+ * @torch_current_max: maximum current for the torch
+ * @flash_current_max: maximum current for the flash
+ * @max_flash_timeout: maximum timeout for the flash
+ * @led_mode: The mode to enable either IR or Torch
+ */
+struct lm3601x_led {
+ struct led_classdev_flash fled_cdev;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct mutex lock;
+
+ unsigned int flash_timeout;
+ unsigned int last_flag;
+
+ u32 torch_current_max;
+ u32 flash_current_max;
+ u32 max_flash_timeout;
+
+ u32 led_mode;
+};
+
+static const struct reg_default lm3601x_regmap_defs[] = {
+ { LM3601X_ENABLE_REG, 0x20 },
+ { LM3601X_CFG_REG, 0x15 },
+ { LM3601X_LED_FLASH_REG, 0x00 },
+ { LM3601X_LED_TORCH_REG, 0x00 },
+};
+
+static bool lm3601x_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case LM3601X_FLAGS_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config lm3601x_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LM3601X_DEV_ID_REG,
+ .reg_defaults = lm3601x_regmap_defs,
+ .num_reg_defaults = ARRAY_SIZE(lm3601x_regmap_defs),
+ .cache_type = REGCACHE_RBTREE,
+ .volatile_reg = lm3601x_volatile_reg,
+};
+
+static struct lm3601x_led *fled_cdev_to_led(struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct lm3601x_led, fled_cdev);
+}
+
+static int lm3601x_read_faults(struct lm3601x_led *led)
+{
+ int flags_val;
+ int ret;
+
+ ret = regmap_read(led->regmap, LM3601X_FLAGS_REG, &flags_val);
+ if (ret < 0)
+ return -EIO;
+
+ led->last_flag = 0;
+
+ if (flags_val & LM36010_OVP_FAULT)
+ led->last_flag |= LED_FAULT_OVER_VOLTAGE;
+
+ if (flags_val & (LM3601X_THERM_SHUTDOWN | LM3601X_THERM_CURR))
+ led->last_flag |= LED_FAULT_OVER_TEMPERATURE;
+
+ if (flags_val & LM3601X_SHORT_FAULT)
+ led->last_flag |= LED_FAULT_SHORT_CIRCUIT;
+
+ if (flags_val & LM36010_CURR_LIMIT)
+ led->last_flag |= LED_FAULT_OVER_CURRENT;
+
+ if (flags_val & LM3601X_UVLO_FAULT)
+ led->last_flag |= LED_FAULT_UNDER_VOLTAGE;
+
+ if (flags_val & LM3601X_IVFM_TRIP)
+ led->last_flag |= LED_FAULT_INPUT_VOLTAGE;
+
+ if (flags_val & LM3601X_THERM_SHUTDOWN)
+ led->last_flag |= LED_FAULT_LED_OVER_TEMPERATURE;
+
+ return led->last_flag;
+}
+
+static int lm3601x_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(cdev);
+ struct lm3601x_led *led = fled_cdev_to_led(fled_cdev);
+ int ret, led_mode_val;
+
+ mutex_lock(&led->lock);
+
+ ret = lm3601x_read_faults(led);
+ if (ret < 0)
+ goto out;
+
+ if (led->led_mode == LM3601X_LED_TORCH)
+ led_mode_val = LM3601X_MODE_TORCH;
+ else
+ led_mode_val = LM3601X_MODE_IR_DRV;
+
+ if (brightness == LED_OFF) {
+ ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG,
+ led_mode_val, LED_OFF);
+ goto out;
+ }
+
+ ret = regmap_write(led->regmap, LM3601X_LED_TORCH_REG, brightness);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG,
+ LM3601X_MODE_TORCH | LM3601X_MODE_IR_DRV,
+ led_mode_val);
+out:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int lm3601x_strobe_set(struct led_classdev_flash *fled_cdev,
+ bool state)
+{
+ struct lm3601x_led *led = fled_cdev_to_led(fled_cdev);
+ int timeout_reg_val;
+ int current_timeout;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ ret = regmap_read(led->regmap, LM3601X_CFG_REG, &current_timeout);
+ if (ret < 0)
+ goto out;
+
+ if (led->flash_timeout >= LM3601X_TIMEOUT_XOVER_US)
+ timeout_reg_val = led->flash_timeout / LM3601X_UPPER_STEP_US + 0x07;
+ else
+ timeout_reg_val = led->flash_timeout / LM3601X_LOWER_STEP_US - 0x01;
+
+ if (led->flash_timeout != current_timeout)
+ ret = regmap_update_bits(led->regmap, LM3601X_CFG_REG,
+ LM3601X_TIMEOUT_MASK, timeout_reg_val);
+
+ if (state)
+ ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG,
+ LM3601X_MODE_TORCH | LM3601X_MODE_IR_DRV,
+ LM3601X_MODE_STROBE);
+ else
+ ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG,
+ LM3601X_MODE_STROBE, LED_OFF);
+
+ ret = lm3601x_read_faults(led);
+out:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int lm3601x_flash_brightness_set(struct led_classdev_flash *fled_cdev,
+ u32 brightness)
+{
+ struct lm3601x_led *led = fled_cdev_to_led(fled_cdev);
+ u8 brightness_val;
+ int ret;
+
+ mutex_lock(&led->lock);
+ ret = lm3601x_read_faults(led);
+ if (ret < 0)
+ goto out;
+
+ if (brightness == LED_OFF) {
+ ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG,
+ LM3601X_MODE_STROBE, LED_OFF);
+ goto out;
+ }
+
+ brightness_val = brightness / LM3601X_STROBE_REG_DIV;
+
+ ret = regmap_write(led->regmap, LM3601X_LED_FLASH_REG, brightness_val);
+out:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int lm3601x_flash_timeout_set(struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ struct lm3601x_led *led = fled_cdev_to_led(fled_cdev);
+
+ mutex_lock(&led->lock);
+
+ led->flash_timeout = timeout;
+
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int lm3601x_strobe_get(struct led_classdev_flash *fled_cdev, bool *state)
+{
+ struct lm3601x_led *led = fled_cdev_to_led(fled_cdev);
+ int strobe_state;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ ret = regmap_read(led->regmap, LM3601X_ENABLE_REG, &strobe_state);
+ if (ret < 0)
+ goto out;
+
+ *state = strobe_state & LM3601X_MODE_STROBE;
+
+out:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int lm3601x_flash_fault_get(struct led_classdev_flash *fled_cdev,
+ u32 *fault)
+{
+ struct lm3601x_led *led = fled_cdev_to_led(fled_cdev);
+
+ lm3601x_read_faults(led);
+
+ *fault = led->last_flag;
+
+ return 0;
+}
+
+static const struct led_flash_ops flash_ops = {
+ .flash_brightness_set = lm3601x_flash_brightness_set,
+ .strobe_set = lm3601x_strobe_set,
+ .strobe_get = lm3601x_strobe_get,
+ .timeout_set = lm3601x_flash_timeout_set,
+ .fault_get = lm3601x_flash_fault_get,
+};
+
+static int lm3601x_register_leds(struct lm3601x_led *led,
+ struct fwnode_handle *fwnode)
+{
+ struct led_classdev *led_cdev;
+ struct led_flash_setting *setting;
+ struct led_init_data init_data = {};
+
+ led->fled_cdev.ops = &flash_ops;
+
+ setting = &led->fled_cdev.timeout;
+ setting->min = LM3601X_MIN_TIMEOUT_US;
+ setting->max = led->max_flash_timeout;
+ setting->step = LM3601X_LOWER_STEP_US;
+ setting->val = led->max_flash_timeout;
+
+ setting = &led->fled_cdev.brightness;
+ setting->min = LM3601X_MIN_STROBE_I_UA;
+ setting->max = led->flash_current_max;
+ setting->step = LM3601X_TORCH_REG_DIV;
+ setting->val = led->flash_current_max;
+
+ led_cdev = &led->fled_cdev.led_cdev;
+ led_cdev->brightness_set_blocking = lm3601x_brightness_set;
+ led_cdev->max_brightness = DIV_ROUND_UP(led->torch_current_max,
+ LM3601X_TORCH_REG_DIV);
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+
+ init_data.fwnode = fwnode;
+ init_data.devicename = led->client->name;
+ init_data.default_label = (led->led_mode == LM3601X_LED_TORCH) ?
+ "torch" : "infrared";
+ return devm_led_classdev_flash_register_ext(&led->client->dev,
+ &led->fled_cdev, &init_data);
+}
+
+static int lm3601x_parse_node(struct lm3601x_led *led,
+ struct fwnode_handle **fwnode)
+{
+ struct fwnode_handle *child = NULL;
+ int ret = -ENODEV;
+
+ child = device_get_next_child_node(&led->client->dev, child);
+ if (!child) {
+ dev_err(&led->client->dev, "No LED Child node\n");
+ return ret;
+ }
+
+ ret = fwnode_property_read_u32(child, "reg", &led->led_mode);
+ if (ret) {
+ dev_err(&led->client->dev, "reg DT property missing\n");
+ goto out_err;
+ }
+
+ if (led->led_mode > LM3601X_LED_TORCH ||
+ led->led_mode < LM3601X_LED_IR) {
+ dev_warn(&led->client->dev, "Invalid led mode requested\n");
+ ret = -EINVAL;
+ goto out_err;
+ }
+
+ ret = fwnode_property_read_u32(child, "led-max-microamp",
+ &led->torch_current_max);
+ if (ret) {
+ dev_warn(&led->client->dev,
+ "led-max-microamp DT property missing\n");
+ goto out_err;
+ }
+
+ ret = fwnode_property_read_u32(child, "flash-max-microamp",
+ &led->flash_current_max);
+ if (ret) {
+ dev_warn(&led->client->dev,
+ "flash-max-microamp DT property missing\n");
+ goto out_err;
+ }
+
+ ret = fwnode_property_read_u32(child, "flash-max-timeout-us",
+ &led->max_flash_timeout);
+ if (ret) {
+ dev_warn(&led->client->dev,
+ "flash-max-timeout-us DT property missing\n");
+ goto out_err;
+ }
+
+ *fwnode = child;
+
+out_err:
+ fwnode_handle_put(child);
+ return ret;
+}
+
+static int lm3601x_probe(struct i2c_client *client)
+{
+ struct lm3601x_led *led;
+ struct fwnode_handle *fwnode;
+ int ret;
+
+ led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->client = client;
+ i2c_set_clientdata(client, led);
+
+ ret = lm3601x_parse_node(led, &fwnode);
+ if (ret)
+ return -ENODEV;
+
+ led->regmap = devm_regmap_init_i2c(client, &lm3601x_regmap);
+ if (IS_ERR(led->regmap)) {
+ ret = PTR_ERR(led->regmap);
+ dev_err(&client->dev,
+ "Failed to allocate register map: %d\n", ret);
+ return ret;
+ }
+
+ mutex_init(&led->lock);
+
+ return lm3601x_register_leds(led, fwnode);
+}
+
+static void lm3601x_remove(struct i2c_client *client)
+{
+ struct lm3601x_led *led = i2c_get_clientdata(client);
+ int ret;
+
+ ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG,
+ LM3601X_ENABLE_MASK, LM3601X_MODE_STANDBY);
+ if (ret)
+ dev_warn(&client->dev,
+ "Failed to put into standby (%pe)\n", ERR_PTR(ret));
+}
+
+static const struct i2c_device_id lm3601x_id[] = {
+ { "LM36010", CHIP_LM36010 },
+ { "LM36011", CHIP_LM36011 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lm3601x_id);
+
+static const struct of_device_id of_lm3601x_leds_match[] = {
+ { .compatible = "ti,lm36010", },
+ { .compatible = "ti,lm36011", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, of_lm3601x_leds_match);
+
+static struct i2c_driver lm3601x_i2c_driver = {
+ .driver = {
+ .name = "lm3601x",
+ .of_match_table = of_lm3601x_leds_match,
+ },
+ .probe_new = lm3601x_probe,
+ .remove = lm3601x_remove,
+ .id_table = lm3601x_id,
+};
+module_i2c_driver(lm3601x_i2c_driver);
+
+MODULE_DESCRIPTION("Texas Instruments Flash Lighting driver for LM3601X");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-max77693.c b/drivers/leds/flash/leds-max77693.c
new file mode 100644
index 000000000..5c1faeb55
--- /dev/null
+++ b/drivers/leds/flash/leds-max77693.c
@@ -0,0 +1,1059 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED Flash class driver for the flash cell of max77693 mfd.
+ *
+ * Copyright (C) 2015, Samsung Electronics Co., Ltd.
+ *
+ * Authors: Jacek Anaszewski <j.anaszewski@samsung.com>
+ * Andrzej Hajda <a.hajda@samsung.com>
+ */
+
+#include <linux/led-class-flash.h>
+#include <linux/mfd/max77693.h>
+#include <linux/mfd/max77693-common.h>
+#include <linux/mfd/max77693-private.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define MODE_OFF 0
+#define MODE_FLASH(a) (1 << (a))
+#define MODE_TORCH(a) (1 << (2 + (a)))
+#define MODE_FLASH_EXTERNAL(a) (1 << (4 + (a)))
+
+#define MODE_FLASH_MASK (MODE_FLASH(FLED1) | MODE_FLASH(FLED2) | \
+ MODE_FLASH_EXTERNAL(FLED1) | \
+ MODE_FLASH_EXTERNAL(FLED2))
+#define MODE_TORCH_MASK (MODE_TORCH(FLED1) | MODE_TORCH(FLED2))
+
+#define FLED1_IOUT (1 << 0)
+#define FLED2_IOUT (1 << 1)
+
+enum max77693_fled {
+ FLED1,
+ FLED2,
+};
+
+enum max77693_led_mode {
+ FLASH,
+ TORCH,
+};
+
+struct max77693_led_config_data {
+ const char *label[2];
+ u32 iout_torch_max[2];
+ u32 iout_flash_max[2];
+ u32 flash_timeout_max[2];
+ u32 num_leds;
+ u32 boost_mode;
+ u32 boost_vout;
+ u32 low_vsys;
+};
+
+struct max77693_sub_led {
+ /* corresponding FLED output identifier */
+ int fled_id;
+ /* corresponding LED Flash class device */
+ struct led_classdev_flash fled_cdev;
+ /* V4L2 Flash device */
+ struct v4l2_flash *v4l2_flash;
+
+ /* brightness cache */
+ unsigned int torch_brightness;
+ /* flash timeout cache */
+ unsigned int flash_timeout;
+ /* flash faults that may have occurred */
+ u32 flash_faults;
+};
+
+struct max77693_led_device {
+ /* parent mfd regmap */
+ struct regmap *regmap;
+ /* platform device data */
+ struct platform_device *pdev;
+ /* secures access to the device */
+ struct mutex lock;
+
+ /* sub led data */
+ struct max77693_sub_led sub_leds[2];
+
+ /* maximum torch current values for FLED outputs */
+ u32 iout_torch_max[2];
+ /* maximum flash current values for FLED outputs */
+ u32 iout_flash_max[2];
+
+ /* current flash timeout cache */
+ unsigned int current_flash_timeout;
+ /* ITORCH register cache */
+ u8 torch_iout_reg;
+ /* mode of fled outputs */
+ unsigned int mode_flags;
+ /* recently strobed fled */
+ int strobing_sub_led_id;
+ /* bitmask of FLED outputs use state (bit 0. - FLED1, bit 1. - FLED2) */
+ u8 fled_mask;
+ /* FLED modes that can be set */
+ u8 allowed_modes;
+
+ /* arrangement of current outputs */
+ bool iout_joint;
+};
+
+static u8 max77693_led_iout_to_reg(u32 ua)
+{
+ if (ua < FLASH_IOUT_MIN)
+ ua = FLASH_IOUT_MIN;
+ return (ua - FLASH_IOUT_MIN) / FLASH_IOUT_STEP;
+}
+
+static u8 max77693_flash_timeout_to_reg(u32 us)
+{
+ return (us - FLASH_TIMEOUT_MIN) / FLASH_TIMEOUT_STEP;
+}
+
+static inline struct max77693_sub_led *flcdev_to_sub_led(
+ struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct max77693_sub_led, fled_cdev);
+}
+
+static inline struct max77693_led_device *sub_led_to_led(
+ struct max77693_sub_led *sub_led)
+{
+ return container_of(sub_led, struct max77693_led_device,
+ sub_leds[sub_led->fled_id]);
+}
+
+static inline u8 max77693_led_vsys_to_reg(u32 mv)
+{
+ return ((mv - MAX_FLASH1_VSYS_MIN) / MAX_FLASH1_VSYS_STEP) << 2;
+}
+
+static inline u8 max77693_led_vout_to_reg(u32 mv)
+{
+ return (mv - FLASH_VOUT_MIN) / FLASH_VOUT_STEP + FLASH_VOUT_RMIN;
+}
+
+static inline bool max77693_fled_used(struct max77693_led_device *led,
+ int fled_id)
+{
+ u8 fled_bit = (fled_id == FLED1) ? FLED1_IOUT : FLED2_IOUT;
+
+ return led->fled_mask & fled_bit;
+}
+
+static int max77693_set_mode_reg(struct max77693_led_device *led, u8 mode)
+{
+ struct regmap *rmap = led->regmap;
+ int ret, v = 0, i;
+
+ for (i = FLED1; i <= FLED2; ++i) {
+ if (mode & MODE_TORCH(i))
+ v |= FLASH_EN_ON << TORCH_EN_SHIFT(i);
+
+ if (mode & MODE_FLASH(i)) {
+ v |= FLASH_EN_ON << FLASH_EN_SHIFT(i);
+ } else if (mode & MODE_FLASH_EXTERNAL(i)) {
+ v |= FLASH_EN_FLASH << FLASH_EN_SHIFT(i);
+ /*
+ * Enable hw triggering also for torch mode, as some
+ * camera sensors use torch led to fathom ambient light
+ * conditions before strobing the flash.
+ */
+ v |= FLASH_EN_TORCH << TORCH_EN_SHIFT(i);
+ }
+ }
+
+ /* Reset the register only prior setting flash modes */
+ if (mode & ~(MODE_TORCH(FLED1) | MODE_TORCH(FLED2))) {
+ ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_EN, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ return regmap_write(rmap, MAX77693_LED_REG_FLASH_EN, v);
+}
+
+static int max77693_add_mode(struct max77693_led_device *led, u8 mode)
+{
+ u8 new_mode_flags;
+ int i, ret;
+
+ if (led->iout_joint)
+ /* Span the mode on FLED2 for joint iouts case */
+ mode |= (mode << 1);
+
+ /*
+ * FLASH_EXTERNAL mode activates FLASHEN and TORCHEN pins in the device.
+ * Corresponding register bit fields interfere with SW triggered modes,
+ * thus clear them to ensure proper device configuration.
+ */
+ for (i = FLED1; i <= FLED2; ++i)
+ if (mode & MODE_FLASH_EXTERNAL(i))
+ led->mode_flags &= (~MODE_TORCH(i) & ~MODE_FLASH(i));
+
+ new_mode_flags = mode | led->mode_flags;
+ new_mode_flags &= led->allowed_modes;
+
+ if (new_mode_flags ^ led->mode_flags)
+ led->mode_flags = new_mode_flags;
+ else
+ return 0;
+
+ ret = max77693_set_mode_reg(led, led->mode_flags);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Clear flash mode flag after setting the mode to avoid spurious flash
+ * strobing on each subsequent torch mode setting.
+ */
+ if (mode & MODE_FLASH_MASK)
+ led->mode_flags &= ~mode;
+
+ return ret;
+}
+
+static int max77693_clear_mode(struct max77693_led_device *led,
+ u8 mode)
+{
+ if (led->iout_joint)
+ /* Clear mode also on FLED2 for joint iouts case */
+ mode |= (mode << 1);
+
+ led->mode_flags &= ~mode;
+
+ return max77693_set_mode_reg(led, led->mode_flags);
+}
+
+static void max77693_add_allowed_modes(struct max77693_led_device *led,
+ int fled_id, enum max77693_led_mode mode)
+{
+ if (mode == FLASH)
+ led->allowed_modes |= (MODE_FLASH(fled_id) |
+ MODE_FLASH_EXTERNAL(fled_id));
+ else
+ led->allowed_modes |= MODE_TORCH(fled_id);
+}
+
+static void max77693_distribute_currents(struct max77693_led_device *led,
+ int fled_id, enum max77693_led_mode mode,
+ u32 micro_amp, u32 iout_max[2], u32 iout[2])
+{
+ if (!led->iout_joint) {
+ iout[fled_id] = micro_amp;
+ max77693_add_allowed_modes(led, fled_id, mode);
+ return;
+ }
+
+ iout[FLED1] = min(micro_amp, iout_max[FLED1]);
+ iout[FLED2] = micro_amp - iout[FLED1];
+
+ if (mode == FLASH)
+ led->allowed_modes &= ~MODE_FLASH_MASK;
+ else
+ led->allowed_modes &= ~MODE_TORCH_MASK;
+
+ max77693_add_allowed_modes(led, FLED1, mode);
+
+ if (iout[FLED2])
+ max77693_add_allowed_modes(led, FLED2, mode);
+}
+
+static int max77693_set_torch_current(struct max77693_led_device *led,
+ int fled_id, u32 micro_amp)
+{
+ struct regmap *rmap = led->regmap;
+ u8 iout1_reg = 0, iout2_reg = 0;
+ u32 iout[2];
+
+ max77693_distribute_currents(led, fled_id, TORCH, micro_amp,
+ led->iout_torch_max, iout);
+
+ if (fled_id == FLED1 || led->iout_joint) {
+ iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
+ led->torch_iout_reg &= TORCH_IOUT_MASK(TORCH_IOUT2_SHIFT);
+ }
+ if (fled_id == FLED2 || led->iout_joint) {
+ iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
+ led->torch_iout_reg &= TORCH_IOUT_MASK(TORCH_IOUT1_SHIFT);
+ }
+
+ led->torch_iout_reg |= ((iout1_reg << TORCH_IOUT1_SHIFT) |
+ (iout2_reg << TORCH_IOUT2_SHIFT));
+
+ return regmap_write(rmap, MAX77693_LED_REG_ITORCH,
+ led->torch_iout_reg);
+}
+
+static int max77693_set_flash_current(struct max77693_led_device *led,
+ int fled_id,
+ u32 micro_amp)
+{
+ struct regmap *rmap = led->regmap;
+ u8 iout1_reg, iout2_reg;
+ u32 iout[2];
+ int ret = -EINVAL;
+
+ max77693_distribute_currents(led, fled_id, FLASH, micro_amp,
+ led->iout_flash_max, iout);
+
+ if (fled_id == FLED1 || led->iout_joint) {
+ iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
+ ret = regmap_write(rmap, MAX77693_LED_REG_IFLASH1,
+ iout1_reg);
+ if (ret < 0)
+ return ret;
+ }
+ if (fled_id == FLED2 || led->iout_joint) {
+ iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
+ ret = regmap_write(rmap, MAX77693_LED_REG_IFLASH2,
+ iout2_reg);
+ }
+
+ return ret;
+}
+
+static int max77693_set_timeout(struct max77693_led_device *led, u32 microsec)
+{
+ struct regmap *rmap = led->regmap;
+ u8 v;
+ int ret;
+
+ v = max77693_flash_timeout_to_reg(microsec) | FLASH_TMR_LEVEL;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
+ if (ret < 0)
+ return ret;
+
+ led->current_flash_timeout = microsec;
+
+ return 0;
+}
+
+static int max77693_get_strobe_status(struct max77693_led_device *led,
+ bool *state)
+{
+ struct regmap *rmap = led->regmap;
+ unsigned int v;
+ int ret;
+
+ ret = regmap_read(rmap, MAX77693_LED_REG_FLASH_STATUS, &v);
+ if (ret < 0)
+ return ret;
+
+ *state = v & FLASH_STATUS_FLASH_ON;
+
+ return ret;
+}
+
+static int max77693_get_flash_faults(struct max77693_sub_led *sub_led)
+{
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ struct regmap *rmap = led->regmap;
+ unsigned int v;
+ u8 fault_open_mask, fault_short_mask;
+ int ret;
+
+ sub_led->flash_faults = 0;
+
+ if (led->iout_joint) {
+ fault_open_mask = FLASH_INT_FLED1_OPEN | FLASH_INT_FLED2_OPEN;
+ fault_short_mask = FLASH_INT_FLED1_SHORT |
+ FLASH_INT_FLED2_SHORT;
+ } else {
+ fault_open_mask = (sub_led->fled_id == FLED1) ?
+ FLASH_INT_FLED1_OPEN :
+ FLASH_INT_FLED2_OPEN;
+ fault_short_mask = (sub_led->fled_id == FLED1) ?
+ FLASH_INT_FLED1_SHORT :
+ FLASH_INT_FLED2_SHORT;
+ }
+
+ ret = regmap_read(rmap, MAX77693_LED_REG_FLASH_INT, &v);
+ if (ret < 0)
+ return ret;
+
+ if (v & fault_open_mask)
+ sub_led->flash_faults |= LED_FAULT_OVER_VOLTAGE;
+ if (v & fault_short_mask)
+ sub_led->flash_faults |= LED_FAULT_SHORT_CIRCUIT;
+ if (v & FLASH_INT_OVER_CURRENT)
+ sub_led->flash_faults |= LED_FAULT_OVER_CURRENT;
+
+ return 0;
+}
+
+static int max77693_setup(struct max77693_led_device *led,
+ struct max77693_led_config_data *led_cfg)
+{
+ struct regmap *rmap = led->regmap;
+ int i, first_led, last_led, ret;
+ u32 max_flash_curr[2];
+ u8 v;
+
+ /*
+ * Initialize only flash current. Torch current doesn't
+ * require initialization as ITORCH register is written with
+ * new value each time brightness_set op is called.
+ */
+ if (led->iout_joint) {
+ first_led = FLED1;
+ last_led = FLED1;
+ max_flash_curr[FLED1] = led_cfg->iout_flash_max[FLED1] +
+ led_cfg->iout_flash_max[FLED2];
+ } else {
+ first_led = max77693_fled_used(led, FLED1) ? FLED1 : FLED2;
+ last_led = max77693_fled_used(led, FLED2) ? FLED2 : FLED1;
+ max_flash_curr[FLED1] = led_cfg->iout_flash_max[FLED1];
+ max_flash_curr[FLED2] = led_cfg->iout_flash_max[FLED2];
+ }
+
+ for (i = first_led; i <= last_led; ++i) {
+ ret = max77693_set_flash_current(led, i,
+ max_flash_curr[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ v = TORCH_TMR_NO_TIMER | MAX77693_LED_TRIG_TYPE_LEVEL;
+ ret = regmap_write(rmap, MAX77693_LED_REG_ITORCHTIMER, v);
+ if (ret < 0)
+ return ret;
+
+ if (led_cfg->low_vsys > 0)
+ v = max77693_led_vsys_to_reg(led_cfg->low_vsys) |
+ MAX_FLASH1_MAX_FL_EN;
+ else
+ v = 0;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_MAX_FLASH1, v);
+ if (ret < 0)
+ return ret;
+ ret = regmap_write(rmap, MAX77693_LED_REG_MAX_FLASH2, 0);
+ if (ret < 0)
+ return ret;
+
+ if (led_cfg->boost_mode == MAX77693_LED_BOOST_FIXED)
+ v = FLASH_BOOST_FIXED;
+ else
+ v = led_cfg->boost_mode | led_cfg->boost_mode << 1;
+
+ if (max77693_fled_used(led, FLED1) && max77693_fled_used(led, FLED2))
+ v |= FLASH_BOOST_LEDNUM_2;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_CNTL, v);
+ if (ret < 0)
+ return ret;
+
+ v = max77693_led_vout_to_reg(led_cfg->boost_vout);
+ ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
+ if (ret < 0)
+ return ret;
+
+ return max77693_set_mode_reg(led, MODE_OFF);
+}
+
+/* LED subsystem callbacks */
+static int max77693_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id, ret;
+
+ mutex_lock(&led->lock);
+
+ if (value == 0) {
+ ret = max77693_clear_mode(led, MODE_TORCH(fled_id));
+ if (ret < 0)
+ dev_dbg(&led->pdev->dev,
+ "Failed to clear torch mode (%d)\n",
+ ret);
+ goto unlock;
+ }
+
+ ret = max77693_set_torch_current(led, fled_id, value * TORCH_IOUT_STEP);
+ if (ret < 0) {
+ dev_dbg(&led->pdev->dev,
+ "Failed to set torch current (%d)\n",
+ ret);
+ goto unlock;
+ }
+
+ ret = max77693_add_mode(led, MODE_TORCH(fled_id));
+ if (ret < 0)
+ dev_dbg(&led->pdev->dev,
+ "Failed to set torch mode (%d)\n",
+ ret);
+unlock:
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_flash_brightness_set(
+ struct led_classdev_flash *fled_cdev,
+ u32 brightness)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int ret;
+
+ mutex_lock(&led->lock);
+ ret = max77693_set_flash_current(led, sub_led->fled_id, brightness);
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_flash_strobe_set(
+ struct led_classdev_flash *fled_cdev,
+ bool state)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (!state) {
+ ret = max77693_clear_mode(led, MODE_FLASH(fled_id));
+ goto unlock;
+ }
+
+ if (sub_led->flash_timeout != led->current_flash_timeout) {
+ ret = max77693_set_timeout(led, sub_led->flash_timeout);
+ if (ret < 0)
+ goto unlock;
+ }
+
+ led->strobing_sub_led_id = fled_id;
+
+ ret = max77693_add_mode(led, MODE_FLASH(fled_id));
+ if (ret < 0)
+ goto unlock;
+
+ ret = max77693_get_flash_faults(sub_led);
+
+unlock:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int max77693_led_flash_fault_get(
+ struct led_classdev_flash *fled_cdev,
+ u32 *fault)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+
+ *fault = sub_led->flash_faults;
+
+ return 0;
+}
+
+static int max77693_led_flash_strobe_get(
+ struct led_classdev_flash *fled_cdev,
+ bool *state)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int ret;
+
+ if (!state)
+ return -EINVAL;
+
+ mutex_lock(&led->lock);
+
+ ret = max77693_get_strobe_status(led, state);
+
+ *state = !!(*state && (led->strobing_sub_led_id == sub_led->fled_id));
+
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_flash_timeout_set(
+ struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+
+ mutex_lock(&led->lock);
+ sub_led->flash_timeout = timeout;
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int max77693_led_parse_dt(struct max77693_led_device *led,
+ struct max77693_led_config_data *cfg,
+ struct device_node **sub_nodes)
+{
+ struct device *dev = &led->pdev->dev;
+ struct max77693_sub_led *sub_leds = led->sub_leds;
+ struct device_node *node = dev_of_node(dev), *child_node;
+ struct property *prop;
+ u32 led_sources[2];
+ int i, ret, fled_id;
+
+ of_property_read_u32(node, "maxim,boost-mode", &cfg->boost_mode);
+ of_property_read_u32(node, "maxim,boost-mvout", &cfg->boost_vout);
+ of_property_read_u32(node, "maxim,mvsys-min", &cfg->low_vsys);
+
+ for_each_available_child_of_node(node, child_node) {
+ prop = of_find_property(child_node, "led-sources", NULL);
+ if (prop) {
+ const __be32 *srcs = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(led_sources); ++i) {
+ srcs = of_prop_next_u32(prop, srcs,
+ &led_sources[i]);
+ if (!srcs)
+ break;
+ }
+ } else {
+ dev_err(dev,
+ "led-sources DT property missing\n");
+ of_node_put(child_node);
+ return -EINVAL;
+ }
+
+ if (i == 2) {
+ fled_id = FLED1;
+ led->fled_mask = FLED1_IOUT | FLED2_IOUT;
+ } else if (led_sources[0] == FLED1) {
+ fled_id = FLED1;
+ led->fled_mask |= FLED1_IOUT;
+ } else if (led_sources[0] == FLED2) {
+ fled_id = FLED2;
+ led->fled_mask |= FLED2_IOUT;
+ } else {
+ dev_err(dev,
+ "Wrong led-sources DT property value.\n");
+ of_node_put(child_node);
+ return -EINVAL;
+ }
+
+ if (sub_nodes[fled_id]) {
+ dev_err(dev,
+ "Conflicting \"led-sources\" DT properties\n");
+ of_node_put(child_node);
+ return -EINVAL;
+ }
+
+ sub_nodes[fled_id] = child_node;
+ sub_leds[fled_id].fled_id = fled_id;
+
+ cfg->label[fled_id] =
+ of_get_property(child_node, "label", NULL) ? :
+ child_node->name;
+
+ ret = of_property_read_u32(child_node, "led-max-microamp",
+ &cfg->iout_torch_max[fled_id]);
+ if (ret < 0) {
+ cfg->iout_torch_max[fled_id] = TORCH_IOUT_MIN;
+ dev_warn(dev, "led-max-microamp DT property missing\n");
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-microamp",
+ &cfg->iout_flash_max[fled_id]);
+ if (ret < 0) {
+ cfg->iout_flash_max[fled_id] = FLASH_IOUT_MIN;
+ dev_warn(dev,
+ "flash-max-microamp DT property missing\n");
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-timeout-us",
+ &cfg->flash_timeout_max[fled_id]);
+ if (ret < 0) {
+ cfg->flash_timeout_max[fled_id] = FLASH_TIMEOUT_MIN;
+ dev_warn(dev,
+ "flash-max-timeout-us DT property missing\n");
+ }
+
+ if (++cfg->num_leds == 2 ||
+ (max77693_fled_used(led, FLED1) &&
+ max77693_fled_used(led, FLED2))) {
+ of_node_put(child_node);
+ break;
+ }
+ }
+
+ if (cfg->num_leds == 0) {
+ dev_err(dev, "No DT child node found for connected LED(s).\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void clamp_align(u32 *v, u32 min, u32 max, u32 step)
+{
+ *v = clamp_val(*v, min, max);
+ if (step > 1)
+ *v = (*v - min) / step * step + min;
+}
+
+static void max77693_align_iout_current(struct max77693_led_device *led,
+ u32 *iout, u32 min, u32 max, u32 step)
+{
+ int i;
+
+ if (led->iout_joint) {
+ if (iout[FLED1] > min) {
+ iout[FLED1] /= 2;
+ iout[FLED2] = iout[FLED1];
+ } else {
+ iout[FLED1] = min;
+ iout[FLED2] = 0;
+ return;
+ }
+ }
+
+ for (i = FLED1; i <= FLED2; ++i)
+ if (max77693_fled_used(led, i))
+ clamp_align(&iout[i], min, max, step);
+ else
+ iout[i] = 0;
+}
+
+static void max77693_led_validate_configuration(struct max77693_led_device *led,
+ struct max77693_led_config_data *cfg)
+{
+ u32 flash_iout_max = cfg->boost_mode ? FLASH_IOUT_MAX_2LEDS :
+ FLASH_IOUT_MAX_1LED;
+ int i;
+
+ if (cfg->num_leds == 1 &&
+ max77693_fled_used(led, FLED1) && max77693_fled_used(led, FLED2))
+ led->iout_joint = true;
+
+ cfg->boost_mode = clamp_val(cfg->boost_mode, MAX77693_LED_BOOST_NONE,
+ MAX77693_LED_BOOST_FIXED);
+
+ /* Boost must be enabled if both current outputs are used */
+ if ((cfg->boost_mode == MAX77693_LED_BOOST_NONE) && led->iout_joint)
+ cfg->boost_mode = MAX77693_LED_BOOST_FIXED;
+
+ max77693_align_iout_current(led, cfg->iout_torch_max,
+ TORCH_IOUT_MIN, TORCH_IOUT_MAX, TORCH_IOUT_STEP);
+
+ max77693_align_iout_current(led, cfg->iout_flash_max,
+ FLASH_IOUT_MIN, flash_iout_max, FLASH_IOUT_STEP);
+
+ for (i = 0; i < ARRAY_SIZE(cfg->flash_timeout_max); ++i)
+ clamp_align(&cfg->flash_timeout_max[i], FLASH_TIMEOUT_MIN,
+ FLASH_TIMEOUT_MAX, FLASH_TIMEOUT_STEP);
+
+ clamp_align(&cfg->boost_vout, FLASH_VOUT_MIN, FLASH_VOUT_MAX,
+ FLASH_VOUT_STEP);
+
+ if (cfg->low_vsys)
+ clamp_align(&cfg->low_vsys, MAX_FLASH1_VSYS_MIN,
+ MAX_FLASH1_VSYS_MAX, MAX_FLASH1_VSYS_STEP);
+}
+
+static int max77693_led_get_configuration(struct max77693_led_device *led,
+ struct max77693_led_config_data *cfg,
+ struct device_node **sub_nodes)
+{
+ int ret;
+
+ ret = max77693_led_parse_dt(led, cfg, sub_nodes);
+ if (ret < 0)
+ return ret;
+
+ max77693_led_validate_configuration(led, cfg);
+
+ memcpy(led->iout_torch_max, cfg->iout_torch_max,
+ sizeof(led->iout_torch_max));
+ memcpy(led->iout_flash_max, cfg->iout_flash_max,
+ sizeof(led->iout_flash_max));
+
+ return 0;
+}
+
+static const struct led_flash_ops flash_ops = {
+ .flash_brightness_set = max77693_led_flash_brightness_set,
+ .strobe_set = max77693_led_flash_strobe_set,
+ .strobe_get = max77693_led_flash_strobe_get,
+ .timeout_set = max77693_led_flash_timeout_set,
+ .fault_get = max77693_led_flash_fault_get,
+};
+
+static void max77693_init_flash_settings(struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg)
+{
+ struct led_classdev_flash *fled_cdev = &sub_led->fled_cdev;
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ struct led_flash_setting *setting;
+
+ /* Init flash intensity setting */
+ setting = &fled_cdev->brightness;
+ setting->min = FLASH_IOUT_MIN;
+ setting->max = led->iout_joint ?
+ led_cfg->iout_flash_max[FLED1] +
+ led_cfg->iout_flash_max[FLED2] :
+ led_cfg->iout_flash_max[fled_id];
+ setting->step = FLASH_IOUT_STEP;
+ setting->val = setting->max;
+
+ /* Init flash timeout setting */
+ setting = &fled_cdev->timeout;
+ setting->min = FLASH_TIMEOUT_MIN;
+ setting->max = led_cfg->flash_timeout_max[fled_id];
+ setting->step = FLASH_TIMEOUT_STEP;
+ setting->val = setting->max;
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+
+static int max77693_led_external_strobe_set(
+ struct v4l2_flash *v4l2_flash,
+ bool enable)
+{
+ struct max77693_sub_led *sub_led =
+ flcdev_to_sub_led(v4l2_flash->fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (enable)
+ ret = max77693_add_mode(led, MODE_FLASH_EXTERNAL(fled_id));
+ else
+ ret = max77693_clear_mode(led, MODE_FLASH_EXTERNAL(fled_id));
+
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static void max77693_init_v4l2_flash_config(struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ struct device *dev = &led->pdev->dev;
+ struct max77693_dev *iodev = dev_get_drvdata(dev->parent);
+ struct i2c_client *i2c = iodev->i2c;
+ struct led_flash_setting *s;
+
+ snprintf(v4l2_sd_cfg->dev_name, sizeof(v4l2_sd_cfg->dev_name),
+ "%s %d-%04x", sub_led->fled_cdev.led_cdev.name,
+ i2c_adapter_id(i2c->adapter), i2c->addr);
+
+ s = &v4l2_sd_cfg->intensity;
+ s->min = TORCH_IOUT_MIN;
+ s->max = sub_led->fled_cdev.led_cdev.max_brightness * TORCH_IOUT_STEP;
+ s->step = TORCH_IOUT_STEP;
+ s->val = s->max;
+
+ /* Init flash faults config */
+ v4l2_sd_cfg->flash_faults = LED_FAULT_OVER_VOLTAGE |
+ LED_FAULT_SHORT_CIRCUIT |
+ LED_FAULT_OVER_CURRENT;
+
+ v4l2_sd_cfg->has_external_strobe = true;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = max77693_led_external_strobe_set,
+};
+#else
+static inline void max77693_init_v4l2_flash_config(
+ struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+static const struct v4l2_flash_ops v4l2_flash_ops;
+#endif
+
+static void max77693_init_fled_cdev(struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg)
+{
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ struct led_classdev_flash *fled_cdev;
+ struct led_classdev *led_cdev;
+
+ /* Initialize LED Flash class device */
+ fled_cdev = &sub_led->fled_cdev;
+ fled_cdev->ops = &flash_ops;
+ led_cdev = &fled_cdev->led_cdev;
+
+ led_cdev->name = led_cfg->label[fled_id];
+
+ led_cdev->brightness_set_blocking = max77693_led_brightness_set;
+ led_cdev->max_brightness = (led->iout_joint ?
+ led_cfg->iout_torch_max[FLED1] +
+ led_cfg->iout_torch_max[FLED2] :
+ led_cfg->iout_torch_max[fled_id]) /
+ TORCH_IOUT_STEP;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+
+ max77693_init_flash_settings(sub_led, led_cfg);
+
+ /* Init flash timeout cache */
+ sub_led->flash_timeout = fled_cdev->timeout.val;
+}
+
+static int max77693_register_led(struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg,
+ struct device_node *sub_node)
+{
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ struct led_classdev_flash *fled_cdev = &sub_led->fled_cdev;
+ struct device *dev = &led->pdev->dev;
+ struct v4l2_flash_config v4l2_sd_cfg = {};
+ int ret;
+
+ /* Register in the LED subsystem */
+ ret = led_classdev_flash_register(dev, fled_cdev);
+ if (ret < 0)
+ return ret;
+
+ max77693_init_v4l2_flash_config(sub_led, led_cfg, &v4l2_sd_cfg);
+
+ /* Register in the V4L2 subsystem. */
+ sub_led->v4l2_flash = v4l2_flash_init(dev, of_fwnode_handle(sub_node),
+ fled_cdev, &v4l2_flash_ops,
+ &v4l2_sd_cfg);
+ if (IS_ERR(sub_led->v4l2_flash)) {
+ ret = PTR_ERR(sub_led->v4l2_flash);
+ goto err_v4l2_flash_init;
+ }
+
+ return 0;
+
+err_v4l2_flash_init:
+ led_classdev_flash_unregister(fled_cdev);
+ return ret;
+}
+
+static int max77693_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct max77693_dev *iodev = dev_get_drvdata(dev->parent);
+ struct max77693_led_device *led;
+ struct max77693_sub_led *sub_leds;
+ struct device_node *sub_nodes[2] = {};
+ struct max77693_led_config_data led_cfg = {};
+ int init_fled_cdev[2], i, ret;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pdev = pdev;
+ led->regmap = iodev->regmap;
+ led->allowed_modes = MODE_FLASH_MASK;
+ sub_leds = led->sub_leds;
+
+ platform_set_drvdata(pdev, led);
+ ret = max77693_led_get_configuration(led, &led_cfg, sub_nodes);
+ if (ret < 0)
+ return ret;
+
+ ret = max77693_setup(led, &led_cfg);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&led->lock);
+
+ init_fled_cdev[FLED1] =
+ led->iout_joint || max77693_fled_used(led, FLED1);
+ init_fled_cdev[FLED2] =
+ !led->iout_joint && max77693_fled_used(led, FLED2);
+
+ for (i = FLED1; i <= FLED2; ++i) {
+ if (!init_fled_cdev[i])
+ continue;
+
+ /* Initialize LED Flash class device */
+ max77693_init_fled_cdev(&sub_leds[i], &led_cfg);
+
+ /*
+ * Register LED Flash class device and corresponding
+ * V4L2 Flash device.
+ */
+ ret = max77693_register_led(&sub_leds[i], &led_cfg,
+ sub_nodes[i]);
+ if (ret < 0) {
+ /*
+ * At this moment FLED1 might have been already
+ * registered and it needs to be released.
+ */
+ if (i == FLED2)
+ goto err_register_led2;
+ else
+ goto err_register_led1;
+ }
+ }
+
+ return 0;
+
+err_register_led2:
+ /* It is possible than only FLED2 was to be registered */
+ if (!init_fled_cdev[FLED1])
+ goto err_register_led1;
+ v4l2_flash_release(sub_leds[FLED1].v4l2_flash);
+ led_classdev_flash_unregister(&sub_leds[FLED1].fled_cdev);
+err_register_led1:
+ mutex_destroy(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_remove(struct platform_device *pdev)
+{
+ struct max77693_led_device *led = platform_get_drvdata(pdev);
+ struct max77693_sub_led *sub_leds = led->sub_leds;
+
+ if (led->iout_joint || max77693_fled_used(led, FLED1)) {
+ v4l2_flash_release(sub_leds[FLED1].v4l2_flash);
+ led_classdev_flash_unregister(&sub_leds[FLED1].fled_cdev);
+ }
+
+ if (!led->iout_joint && max77693_fled_used(led, FLED2)) {
+ v4l2_flash_release(sub_leds[FLED2].v4l2_flash);
+ led_classdev_flash_unregister(&sub_leds[FLED2].fled_cdev);
+ }
+
+ mutex_destroy(&led->lock);
+
+ return 0;
+}
+
+static const struct of_device_id max77693_led_dt_match[] = {
+ { .compatible = "maxim,max77693-led" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, max77693_led_dt_match);
+
+static struct platform_driver max77693_led_driver = {
+ .probe = max77693_led_probe,
+ .remove = max77693_led_remove,
+ .driver = {
+ .name = "max77693-led",
+ .of_match_table = max77693_led_dt_match,
+ },
+};
+
+module_platform_driver(max77693_led_driver);
+
+MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
+MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
+MODULE_DESCRIPTION("Maxim MAX77693 led flash driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-mt6360.c b/drivers/leds/flash/leds-mt6360.c
new file mode 100644
index 000000000..e1066a52d
--- /dev/null
+++ b/drivers/leds/flash/leds-mt6360.c
@@ -0,0 +1,910 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/led-class-flash.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <media/v4l2-flash-led-class.h>
+
+enum {
+ MT6360_LED_ISNK1 = 0,
+ MT6360_LED_ISNK2,
+ MT6360_LED_ISNK3,
+ MT6360_LED_ISNKML,
+ MT6360_LED_FLASH1,
+ MT6360_LED_FLASH2,
+ MT6360_MAX_LEDS
+};
+
+#define MT6360_REG_RGBEN 0x380
+#define MT6360_REG_ISNK(_led_no) (0x381 + (_led_no))
+#define MT6360_ISNK_ENMASK(_led_no) BIT(7 - (_led_no))
+#define MT6360_ISNK_MASK GENMASK(4, 0)
+#define MT6360_CHRINDSEL_MASK BIT(3)
+
+/* Virtual definition for multicolor */
+#define MT6360_VIRTUAL_MULTICOLOR (MT6360_MAX_LEDS + 1)
+#define MULTICOLOR_NUM_CHANNELS 3
+
+#define MT6360_REG_FLEDEN 0x37E
+#define MT6360_REG_STRBTO 0x373
+#define MT6360_REG_FLEDBASE(_id) (0x372 + 4 * (_id - MT6360_LED_FLASH1))
+#define MT6360_REG_FLEDISTRB(_id) (MT6360_REG_FLEDBASE(_id) + 2)
+#define MT6360_REG_FLEDITOR(_id) (MT6360_REG_FLEDBASE(_id) + 3)
+#define MT6360_REG_CHGSTAT2 0x3E1
+#define MT6360_REG_FLEDSTAT1 0x3E9
+#define MT6360_ITORCH_MASK GENMASK(4, 0)
+#define MT6360_ISTROBE_MASK GENMASK(6, 0)
+#define MT6360_STRBTO_MASK GENMASK(6, 0)
+#define MT6360_TORCHEN_MASK BIT(3)
+#define MT6360_STROBEN_MASK BIT(2)
+#define MT6360_FLCSEN_MASK(_id) BIT(MT6360_LED_FLASH2 - _id)
+#define MT6360_FLEDCHGVINOVP_MASK BIT(3)
+#define MT6360_FLED1STRBTO_MASK BIT(11)
+#define MT6360_FLED2STRBTO_MASK BIT(10)
+#define MT6360_FLED1STRB_MASK BIT(9)
+#define MT6360_FLED2STRB_MASK BIT(8)
+#define MT6360_FLED1SHORT_MASK BIT(7)
+#define MT6360_FLED2SHORT_MASK BIT(6)
+#define MT6360_FLEDLVF_MASK BIT(3)
+
+#define MT6360_ISNKRGB_STEPUA 2000
+#define MT6360_ISNKRGB_MAXUA 24000
+#define MT6360_ISNKML_STEPUA 5000
+#define MT6360_ISNKML_MAXUA 150000
+
+#define MT6360_ITORCH_MINUA 25000
+#define MT6360_ITORCH_STEPUA 12500
+#define MT6360_ITORCH_MAXUA 400000
+#define MT6360_ISTRB_MINUA 50000
+#define MT6360_ISTRB_STEPUA 12500
+#define MT6360_ISTRB_MAXUA 1500000
+#define MT6360_STRBTO_MINUS 64000
+#define MT6360_STRBTO_STEPUS 32000
+#define MT6360_STRBTO_MAXUS 2432000
+
+#define STATE_OFF 0
+#define STATE_KEEP 1
+#define STATE_ON 2
+
+struct mt6360_led {
+ union {
+ struct led_classdev isnk;
+ struct led_classdev_mc mc;
+ struct led_classdev_flash flash;
+ };
+ struct v4l2_flash *v4l2_flash;
+ struct mt6360_priv *priv;
+ u32 led_no;
+ u32 default_state;
+};
+
+struct mt6360_priv {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex lock;
+ unsigned int fled_strobe_used;
+ unsigned int fled_torch_used;
+ unsigned int leds_active;
+ unsigned int leds_count;
+ struct mt6360_led leds[];
+};
+
+static int mt6360_mc_brightness_set(struct led_classdev *lcdev,
+ enum led_brightness level)
+{
+ struct led_classdev_mc *mccdev = lcdev_to_mccdev(lcdev);
+ struct mt6360_led *led = container_of(mccdev, struct mt6360_led, mc);
+ struct mt6360_priv *priv = led->priv;
+ u32 real_bright, enable_mask = 0, enable = 0;
+ int i, ret;
+
+ mutex_lock(&priv->lock);
+
+ led_mc_calc_color_components(mccdev, level);
+
+ for (i = 0; i < mccdev->num_colors; i++) {
+ struct mc_subled *subled = mccdev->subled_info + i;
+
+ real_bright = min(lcdev->max_brightness, subled->brightness);
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_ISNK(i),
+ MT6360_ISNK_MASK, real_bright);
+ if (ret)
+ goto out;
+
+ enable_mask |= MT6360_ISNK_ENMASK(subled->channel);
+ if (real_bright)
+ enable |= MT6360_ISNK_ENMASK(subled->channel);
+ }
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_RGBEN, enable_mask,
+ enable);
+
+out:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int mt6360_isnk_brightness_set(struct led_classdev *lcdev,
+ enum led_brightness level)
+{
+ struct mt6360_led *led = container_of(lcdev, struct mt6360_led, isnk);
+ struct mt6360_priv *priv = led->priv;
+ u32 enable_mask = MT6360_ISNK_ENMASK(led->led_no);
+ u32 val = level ? MT6360_ISNK_ENMASK(led->led_no) : 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_ISNK(led->led_no),
+ MT6360_ISNK_MASK, level);
+ if (ret)
+ goto out;
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_RGBEN, enable_mask,
+ val);
+
+out:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int mt6360_torch_brightness_set(struct led_classdev *lcdev,
+ enum led_brightness level)
+{
+ struct mt6360_led *led =
+ container_of(lcdev, struct mt6360_led, flash.led_cdev);
+ struct mt6360_priv *priv = led->priv;
+ u32 enable_mask = MT6360_TORCHEN_MASK | MT6360_FLCSEN_MASK(led->led_no);
+ u32 val = level ? MT6360_FLCSEN_MASK(led->led_no) : 0;
+ u32 prev = priv->fled_torch_used, curr;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ /*
+ * Only one set of flash control logic, use the flag to avoid strobe is
+ * currently used.
+ */
+ if (priv->fled_strobe_used) {
+ dev_warn(lcdev->dev, "Please disable strobe first [%d]\n",
+ priv->fled_strobe_used);
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (level)
+ curr = prev | BIT(led->led_no);
+ else
+ curr = prev & ~BIT(led->led_no);
+
+ if (curr)
+ val |= MT6360_TORCHEN_MASK;
+
+ if (level) {
+ ret = regmap_update_bits(priv->regmap,
+ MT6360_REG_FLEDITOR(led->led_no),
+ MT6360_ITORCH_MASK, level - 1);
+ if (ret)
+ goto unlock;
+ }
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_FLEDEN, enable_mask,
+ val);
+ if (ret)
+ goto unlock;
+
+ priv->fled_torch_used = curr;
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int mt6360_flash_brightness_set(struct led_classdev_flash *fl_cdev,
+ u32 brightness)
+{
+ /*
+ * Due to the current spike when turning on flash, let brightness to be
+ * kept by framework.
+ * This empty function is used to prevent led_classdev_flash register
+ * ops check failure.
+ */
+ return 0;
+}
+
+static int _mt6360_flash_brightness_set(struct led_classdev_flash *fl_cdev,
+ u32 brightness)
+{
+ struct mt6360_led *led =
+ container_of(fl_cdev, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+ struct led_flash_setting *s = &fl_cdev->brightness;
+ u32 val = (brightness - s->min) / s->step;
+
+ return regmap_update_bits(priv->regmap,
+ MT6360_REG_FLEDISTRB(led->led_no),
+ MT6360_ISTROBE_MASK, val);
+}
+
+static int mt6360_strobe_set(struct led_classdev_flash *fl_cdev, bool state)
+{
+ struct mt6360_led *led =
+ container_of(fl_cdev, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+ struct led_classdev *lcdev = &fl_cdev->led_cdev;
+ struct led_flash_setting *s = &fl_cdev->brightness;
+ u32 enable_mask = MT6360_STROBEN_MASK | MT6360_FLCSEN_MASK(led->led_no);
+ u32 val = state ? MT6360_FLCSEN_MASK(led->led_no) : 0;
+ u32 prev = priv->fled_strobe_used, curr;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ /*
+ * Only one set of flash control logic, use the flag to avoid torch is
+ * currently used
+ */
+ if (priv->fled_torch_used) {
+ dev_warn(lcdev->dev, "Please disable torch first [0x%x]\n",
+ priv->fled_torch_used);
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (state)
+ curr = prev | BIT(led->led_no);
+ else
+ curr = prev & ~BIT(led->led_no);
+
+ if (curr)
+ val |= MT6360_STROBEN_MASK;
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_FLEDEN, enable_mask,
+ val);
+ if (ret) {
+ dev_err(lcdev->dev, "[%d] control current source %d fail\n",
+ led->led_no, state);
+ goto unlock;
+ }
+
+ /*
+ * If the flash need to be on, config the flash current ramping up to
+ * the setting value.
+ * Else, always recover back to the minimum one
+ */
+ ret = _mt6360_flash_brightness_set(fl_cdev, state ? s->val : s->min);
+ if (ret)
+ goto unlock;
+
+ /*
+ * For the flash turn on/off, HW rampping up/down time is 5ms/500us,
+ * respectively.
+ */
+ if (!prev && curr)
+ usleep_range(5000, 6000);
+ else if (prev && !curr)
+ udelay(500);
+
+ priv->fled_strobe_used = curr;
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int mt6360_strobe_get(struct led_classdev_flash *fl_cdev, bool *state)
+{
+ struct mt6360_led *led =
+ container_of(fl_cdev, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+
+ mutex_lock(&priv->lock);
+ *state = !!(priv->fled_strobe_used & BIT(led->led_no));
+ mutex_unlock(&priv->lock);
+
+ return 0;
+}
+
+static int mt6360_timeout_set(struct led_classdev_flash *fl_cdev, u32 timeout)
+{
+ struct mt6360_led *led =
+ container_of(fl_cdev, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+ struct led_flash_setting *s = &fl_cdev->timeout;
+ u32 val = (timeout - s->min) / s->step;
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_STRBTO,
+ MT6360_STRBTO_MASK, val);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int mt6360_fault_get(struct led_classdev_flash *fl_cdev, u32 *fault)
+{
+ struct mt6360_led *led =
+ container_of(fl_cdev, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+ u16 fled_stat;
+ unsigned int chg_stat, strobe_timeout_mask, fled_short_mask;
+ u32 rfault = 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = regmap_read(priv->regmap, MT6360_REG_CHGSTAT2, &chg_stat);
+ if (ret)
+ goto unlock;
+
+ ret = regmap_raw_read(priv->regmap, MT6360_REG_FLEDSTAT1, &fled_stat,
+ sizeof(fled_stat));
+ if (ret)
+ goto unlock;
+
+ if (led->led_no == MT6360_LED_FLASH1) {
+ strobe_timeout_mask = MT6360_FLED1STRBTO_MASK;
+ fled_short_mask = MT6360_FLED1SHORT_MASK;
+ } else {
+ strobe_timeout_mask = MT6360_FLED2STRBTO_MASK;
+ fled_short_mask = MT6360_FLED2SHORT_MASK;
+ }
+
+ if (chg_stat & MT6360_FLEDCHGVINOVP_MASK)
+ rfault |= LED_FAULT_INPUT_VOLTAGE;
+
+ if (fled_stat & strobe_timeout_mask)
+ rfault |= LED_FAULT_TIMEOUT;
+
+ if (fled_stat & fled_short_mask)
+ rfault |= LED_FAULT_SHORT_CIRCUIT;
+
+ if (fled_stat & MT6360_FLEDLVF_MASK)
+ rfault |= LED_FAULT_UNDER_VOLTAGE;
+
+ *fault = rfault;
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static const struct led_flash_ops mt6360_flash_ops = {
+ .flash_brightness_set = mt6360_flash_brightness_set,
+ .strobe_set = mt6360_strobe_set,
+ .strobe_get = mt6360_strobe_get,
+ .timeout_set = mt6360_timeout_set,
+ .fault_get = mt6360_fault_get,
+};
+
+static int mt6360_isnk_init_default_state(struct mt6360_led *led)
+{
+ struct mt6360_priv *priv = led->priv;
+ unsigned int regval;
+ u32 level;
+ int ret;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_ISNK(led->led_no), &regval);
+ if (ret)
+ return ret;
+ level = regval & MT6360_ISNK_MASK;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_RGBEN, &regval);
+ if (ret)
+ return ret;
+
+ if (!(regval & MT6360_ISNK_ENMASK(led->led_no)))
+ level = LED_OFF;
+
+ switch (led->default_state) {
+ case STATE_ON:
+ led->isnk.brightness = led->isnk.max_brightness;
+ break;
+ case STATE_KEEP:
+ led->isnk.brightness = min(level, led->isnk.max_brightness);
+ break;
+ default:
+ led->isnk.brightness = LED_OFF;
+ }
+
+ return mt6360_isnk_brightness_set(&led->isnk, led->isnk.brightness);
+}
+
+static int mt6360_flash_init_default_state(struct mt6360_led *led)
+{
+ struct led_classdev_flash *flash = &led->flash;
+ struct mt6360_priv *priv = led->priv;
+ u32 enable_mask = MT6360_TORCHEN_MASK | MT6360_FLCSEN_MASK(led->led_no);
+ u32 level;
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_FLEDITOR(led->led_no),
+ &regval);
+ if (ret)
+ return ret;
+ level = regval & MT6360_ITORCH_MASK;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_FLEDEN, &regval);
+ if (ret)
+ return ret;
+
+ if ((regval & enable_mask) == enable_mask)
+ level += 1;
+ else
+ level = LED_OFF;
+
+ switch (led->default_state) {
+ case STATE_ON:
+ flash->led_cdev.brightness = flash->led_cdev.max_brightness;
+ break;
+ case STATE_KEEP:
+ flash->led_cdev.brightness =
+ min(level, flash->led_cdev.max_brightness);
+ break;
+ default:
+ flash->led_cdev.brightness = LED_OFF;
+ }
+
+ return mt6360_torch_brightness_set(&flash->led_cdev,
+ flash->led_cdev.brightness);
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static int mt6360_flash_external_strobe_set(struct v4l2_flash *v4l2_flash,
+ bool enable)
+{
+ struct led_classdev_flash *flash = v4l2_flash->fled_cdev;
+ struct mt6360_led *led = container_of(flash, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+ u32 mask = MT6360_FLCSEN_MASK(led->led_no);
+ u32 val = enable ? mask : 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_FLEDEN, mask, val);
+ if (ret)
+ goto unlock;
+
+ if (enable)
+ priv->fled_strobe_used |= BIT(led->led_no);
+ else
+ priv->fled_strobe_used &= ~BIT(led->led_no);
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = mt6360_flash_external_strobe_set,
+};
+
+static void mt6360_init_v4l2_flash_config(struct mt6360_led *led,
+ struct v4l2_flash_config *config)
+{
+ struct led_classdev *lcdev;
+ struct led_flash_setting *s = &config->intensity;
+
+ lcdev = &led->flash.led_cdev;
+
+ s->min = MT6360_ITORCH_MINUA;
+ s->step = MT6360_ITORCH_STEPUA;
+ s->val = s->max = s->min + (lcdev->max_brightness - 1) * s->step;
+
+ config->has_external_strobe = 1;
+ strscpy(config->dev_name, lcdev->dev->kobj.name,
+ sizeof(config->dev_name));
+
+ config->flash_faults = LED_FAULT_SHORT_CIRCUIT | LED_FAULT_TIMEOUT |
+ LED_FAULT_INPUT_VOLTAGE |
+ LED_FAULT_UNDER_VOLTAGE;
+}
+#else
+static const struct v4l2_flash_ops v4l2_flash_ops;
+static void mt6360_init_v4l2_flash_config(struct mt6360_led *led,
+ struct v4l2_flash_config *config)
+{
+}
+#endif
+
+static int mt6360_led_register(struct device *parent, struct mt6360_led *led,
+ struct led_init_data *init_data)
+{
+ struct mt6360_priv *priv = led->priv;
+ struct v4l2_flash_config v4l2_config = {0};
+ int ret;
+
+ if ((led->led_no == MT6360_LED_ISNK1 ||
+ led->led_no == MT6360_VIRTUAL_MULTICOLOR) &&
+ (priv->leds_active & BIT(MT6360_LED_ISNK1))) {
+ /*
+ * Change isink1 to SW control mode, disconnect it with
+ * charger state
+ */
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_RGBEN,
+ MT6360_CHRINDSEL_MASK,
+ MT6360_CHRINDSEL_MASK);
+ if (ret) {
+ dev_err(parent, "Failed to config ISNK1 to SW mode\n");
+ return ret;
+ }
+ }
+
+ switch (led->led_no) {
+ case MT6360_VIRTUAL_MULTICOLOR:
+ ret = mt6360_mc_brightness_set(&led->mc.led_cdev, LED_OFF);
+ if (ret) {
+ dev_err(parent,
+ "Failed to init multicolor brightness\n");
+ return ret;
+ }
+
+ ret = devm_led_classdev_multicolor_register_ext(parent,
+ &led->mc, init_data);
+ if (ret) {
+ dev_err(parent, "Couldn't register multicolor\n");
+ return ret;
+ }
+ break;
+ case MT6360_LED_ISNK1 ... MT6360_LED_ISNKML:
+ ret = mt6360_isnk_init_default_state(led);
+ if (ret) {
+ dev_err(parent, "Failed to init %d isnk state\n",
+ led->led_no);
+ return ret;
+ }
+
+ ret = devm_led_classdev_register_ext(parent, &led->isnk,
+ init_data);
+ if (ret) {
+ dev_err(parent, "Couldn't register isink %d\n",
+ led->led_no);
+ return ret;
+ }
+ break;
+ default:
+ ret = mt6360_flash_init_default_state(led);
+ if (ret) {
+ dev_err(parent, "Failed to init %d flash state\n",
+ led->led_no);
+ return ret;
+ }
+
+ ret = devm_led_classdev_flash_register_ext(parent, &led->flash,
+ init_data);
+ if (ret) {
+ dev_err(parent, "Couldn't register flash %d\n",
+ led->led_no);
+ return ret;
+ }
+
+ mt6360_init_v4l2_flash_config(led, &v4l2_config);
+ led->v4l2_flash = v4l2_flash_init(parent, init_data->fwnode,
+ &led->flash,
+ &v4l2_flash_ops,
+ &v4l2_config);
+ if (IS_ERR(led->v4l2_flash)) {
+ dev_err(parent, "Failed to register %d v4l2 sd\n",
+ led->led_no);
+ return PTR_ERR(led->v4l2_flash);
+ }
+ }
+
+ return 0;
+}
+
+static u32 clamp_align(u32 val, u32 min, u32 max, u32 step)
+{
+ u32 retval;
+
+ retval = clamp_val(val, min, max);
+ if (step > 1)
+ retval = rounddown(retval - min, step) + min;
+
+ return retval;
+}
+
+static int mt6360_init_isnk_properties(struct mt6360_led *led,
+ struct led_init_data *init_data)
+{
+ struct led_classdev *lcdev;
+ struct mt6360_priv *priv = led->priv;
+ struct fwnode_handle *child;
+ u32 step_uA = MT6360_ISNKRGB_STEPUA, max_uA = MT6360_ISNKRGB_MAXUA;
+ u32 val;
+ int num_color = 0, ret;
+
+ if (led->led_no == MT6360_VIRTUAL_MULTICOLOR) {
+ struct mc_subled *sub_led;
+
+ sub_led = devm_kzalloc(priv->dev,
+ sizeof(*sub_led) * MULTICOLOR_NUM_CHANNELS, GFP_KERNEL);
+ if (!sub_led)
+ return -ENOMEM;
+
+ fwnode_for_each_child_node(init_data->fwnode, child) {
+ u32 reg, color;
+
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret || reg > MT6360_LED_ISNK3 ||
+ priv->leds_active & BIT(reg))
+ return -EINVAL;
+
+ ret = fwnode_property_read_u32(child, "color", &color);
+ if (ret) {
+ dev_err(priv->dev,
+ "led %d, no color specified\n",
+ led->led_no);
+ return ret;
+ }
+
+ priv->leds_active |= BIT(reg);
+ sub_led[num_color].color_index = color;
+ sub_led[num_color].channel = reg;
+ num_color++;
+ }
+
+ if (num_color < 2) {
+ dev_err(priv->dev,
+ "Multicolor must include 2 or more led channel\n");
+ return -EINVAL;
+ }
+
+ led->mc.num_colors = num_color;
+ led->mc.subled_info = sub_led;
+
+ lcdev = &led->mc.led_cdev;
+ lcdev->brightness_set_blocking = mt6360_mc_brightness_set;
+ } else {
+ if (led->led_no == MT6360_LED_ISNKML) {
+ step_uA = MT6360_ISNKML_STEPUA;
+ max_uA = MT6360_ISNKML_MAXUA;
+ }
+
+ lcdev = &led->isnk;
+ lcdev->brightness_set_blocking = mt6360_isnk_brightness_set;
+ }
+
+ ret = fwnode_property_read_u32(init_data->fwnode, "led-max-microamp",
+ &val);
+ if (ret) {
+ dev_warn(priv->dev,
+ "Not specified led-max-microamp, config to the minimum\n");
+ val = step_uA;
+ } else
+ val = clamp_align(val, 0, max_uA, step_uA);
+
+ lcdev->max_brightness = val / step_uA;
+
+ fwnode_property_read_string(init_data->fwnode, "linux,default-trigger",
+ &lcdev->default_trigger);
+
+ return 0;
+}
+
+static int mt6360_init_flash_properties(struct mt6360_led *led,
+ struct led_init_data *init_data)
+{
+ struct led_classdev_flash *flash = &led->flash;
+ struct led_classdev *lcdev = &flash->led_cdev;
+ struct mt6360_priv *priv = led->priv;
+ struct led_flash_setting *s;
+ u32 val;
+ int ret;
+
+ ret = fwnode_property_read_u32(init_data->fwnode, "led-max-microamp",
+ &val);
+ if (ret) {
+ dev_warn(priv->dev,
+ "Not specified led-max-microamp, config to the minimum\n");
+ val = MT6360_ITORCH_MINUA;
+ } else
+ val = clamp_align(val, MT6360_ITORCH_MINUA, MT6360_ITORCH_MAXUA,
+ MT6360_ITORCH_STEPUA);
+
+ lcdev->max_brightness =
+ (val - MT6360_ITORCH_MINUA) / MT6360_ITORCH_STEPUA + 1;
+ lcdev->brightness_set_blocking = mt6360_torch_brightness_set;
+ lcdev->flags |= LED_DEV_CAP_FLASH;
+
+ ret = fwnode_property_read_u32(init_data->fwnode, "flash-max-microamp",
+ &val);
+ if (ret) {
+ dev_warn(priv->dev,
+ "Not specified flash-max-microamp, config to the minimum\n");
+ val = MT6360_ISTRB_MINUA;
+ } else
+ val = clamp_align(val, MT6360_ISTRB_MINUA, MT6360_ISTRB_MAXUA,
+ MT6360_ISTRB_STEPUA);
+
+ s = &flash->brightness;
+ s->min = MT6360_ISTRB_MINUA;
+ s->step = MT6360_ISTRB_STEPUA;
+ s->val = s->max = val;
+
+ /*
+ * Always configure as min level when off to prevent flash current
+ * spike.
+ */
+ ret = _mt6360_flash_brightness_set(flash, s->min);
+ if (ret)
+ return ret;
+
+ ret = fwnode_property_read_u32(init_data->fwnode,
+ "flash-max-timeout-us", &val);
+ if (ret) {
+ dev_warn(priv->dev,
+ "Not specified flash-max-timeout-us, config to the minimum\n");
+ val = MT6360_STRBTO_MINUS;
+ } else
+ val = clamp_align(val, MT6360_STRBTO_MINUS, MT6360_STRBTO_MAXUS,
+ MT6360_STRBTO_STEPUS);
+
+ s = &flash->timeout;
+ s->min = MT6360_STRBTO_MINUS;
+ s->step = MT6360_STRBTO_STEPUS;
+ s->val = s->max = val;
+
+ flash->ops = &mt6360_flash_ops;
+
+ return 0;
+}
+
+static int mt6360_init_common_properties(struct mt6360_led *led,
+ struct led_init_data *init_data)
+{
+ const char *const states[] = { "off", "keep", "on" };
+ const char *str;
+ int ret;
+
+ if (!fwnode_property_read_string(init_data->fwnode,
+ "default-state", &str)) {
+ ret = match_string(states, ARRAY_SIZE(states), str);
+ if (ret < 0)
+ ret = STATE_OFF;
+
+ led->default_state = ret;
+ }
+
+ return 0;
+}
+
+static void mt6360_v4l2_flash_release(struct mt6360_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < priv->leds_count; i++) {
+ struct mt6360_led *led = priv->leds + i;
+
+ if (led->v4l2_flash)
+ v4l2_flash_release(led->v4l2_flash);
+ }
+}
+
+static int mt6360_led_probe(struct platform_device *pdev)
+{
+ struct mt6360_priv *priv;
+ struct fwnode_handle *child;
+ size_t count;
+ int i = 0, ret;
+
+ count = device_get_child_node_count(&pdev->dev);
+ if (!count || count > MT6360_MAX_LEDS) {
+ dev_err(&pdev->dev,
+ "No child node or node count over max led number %zu\n",
+ count);
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(&pdev->dev,
+ struct_size(priv, leds, count), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->leds_count = count;
+ priv->dev = &pdev->dev;
+ mutex_init(&priv->lock);
+
+ priv->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!priv->regmap) {
+ dev_err(&pdev->dev, "Failed to get parent regmap\n");
+ return -ENODEV;
+ }
+
+ device_for_each_child_node(&pdev->dev, child) {
+ struct mt6360_led *led = priv->leds + i;
+ struct led_init_data init_data = { .fwnode = child, };
+ u32 reg, led_color;
+
+ ret = fwnode_property_read_u32(child, "color", &led_color);
+ if (ret)
+ goto out_flash_release;
+
+ if (led_color == LED_COLOR_ID_RGB ||
+ led_color == LED_COLOR_ID_MULTI)
+ reg = MT6360_VIRTUAL_MULTICOLOR;
+ else {
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret)
+ goto out_flash_release;
+
+ if (reg >= MT6360_MAX_LEDS) {
+ ret = -EINVAL;
+ goto out_flash_release;
+ }
+ }
+
+ if (priv->leds_active & BIT(reg)) {
+ ret = -EINVAL;
+ goto out_flash_release;
+ }
+ priv->leds_active |= BIT(reg);
+
+ led->led_no = reg;
+ led->priv = priv;
+
+ ret = mt6360_init_common_properties(led, &init_data);
+ if (ret)
+ goto out_flash_release;
+
+ if (reg == MT6360_VIRTUAL_MULTICOLOR ||
+ reg <= MT6360_LED_ISNKML)
+ ret = mt6360_init_isnk_properties(led, &init_data);
+ else
+ ret = mt6360_init_flash_properties(led, &init_data);
+
+ if (ret)
+ goto out_flash_release;
+
+ ret = mt6360_led_register(&pdev->dev, led, &init_data);
+ if (ret)
+ goto out_flash_release;
+
+ i++;
+ }
+
+ platform_set_drvdata(pdev, priv);
+ return 0;
+
+out_flash_release:
+ mt6360_v4l2_flash_release(priv);
+ return ret;
+}
+
+static int mt6360_led_remove(struct platform_device *pdev)
+{
+ struct mt6360_priv *priv = platform_get_drvdata(pdev);
+
+ mt6360_v4l2_flash_release(priv);
+ return 0;
+}
+
+static const struct of_device_id __maybe_unused mt6360_led_of_id[] = {
+ { .compatible = "mediatek,mt6360-led", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mt6360_led_of_id);
+
+static struct platform_driver mt6360_led_driver = {
+ .driver = {
+ .name = "mt6360-led",
+ .of_match_table = mt6360_led_of_id,
+ },
+ .probe = mt6360_led_probe,
+ .remove = mt6360_led_remove,
+};
+module_platform_driver(mt6360_led_driver);
+
+MODULE_AUTHOR("Gene Chen <gene_chen@richtek.com>");
+MODULE_DESCRIPTION("MT6360 LED Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-rt4505.c b/drivers/leds/flash/leds-rt4505.c
new file mode 100644
index 000000000..e404fe8b0
--- /dev/null
+++ b/drivers/leds/flash/leds-rt4505.c
@@ -0,0 +1,429 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitops.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define RT4505_REG_RESET 0x0
+#define RT4505_REG_CONFIG 0x8
+#define RT4505_REG_ILED 0x9
+#define RT4505_REG_ENABLE 0xA
+#define RT4505_REG_FLAGS 0xB
+
+#define RT4505_RESET_MASK BIT(7)
+#define RT4505_FLASHTO_MASK GENMASK(2, 0)
+#define RT4505_ITORCH_MASK GENMASK(7, 5)
+#define RT4505_ITORCH_SHIFT 5
+#define RT4505_IFLASH_MASK GENMASK(4, 0)
+#define RT4505_ENABLE_MASK GENMASK(5, 0)
+#define RT4505_TORCH_SET (BIT(0) | BIT(4))
+#define RT4505_FLASH_SET (BIT(0) | BIT(1) | BIT(2) | BIT(4))
+#define RT4505_EXT_FLASH_SET (BIT(0) | BIT(1) | BIT(4) | BIT(5))
+#define RT4505_FLASH_GET (BIT(0) | BIT(1) | BIT(4))
+#define RT4505_OVP_MASK BIT(3)
+#define RT4505_SHORT_MASK BIT(2)
+#define RT4505_OTP_MASK BIT(1)
+#define RT4505_TIMEOUT_MASK BIT(0)
+
+#define RT4505_ITORCH_MINUA 46000
+#define RT4505_ITORCH_MAXUA 375000
+#define RT4505_ITORCH_STPUA 47000
+#define RT4505_IFLASH_MINUA 93750
+#define RT4505_IFLASH_MAXUA 1500000
+#define RT4505_IFLASH_STPUA 93750
+#define RT4505_FLASHTO_MINUS 100000
+#define RT4505_FLASHTO_MAXUS 800000
+#define RT4505_FLASHTO_STPUS 100000
+
+struct rt4505_priv {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex lock;
+ struct led_classdev_flash flash;
+ struct v4l2_flash *v4l2_flash;
+};
+
+static int rt4505_torch_brightness_set(struct led_classdev *lcdev,
+ enum led_brightness level)
+{
+ struct rt4505_priv *priv =
+ container_of(lcdev, struct rt4505_priv, flash.led_cdev);
+ u32 val = 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ if (level != LED_OFF) {
+ ret = regmap_update_bits(priv->regmap,
+ RT4505_REG_ILED, RT4505_ITORCH_MASK,
+ (level - 1) << RT4505_ITORCH_SHIFT);
+ if (ret)
+ goto unlock;
+
+ val = RT4505_TORCH_SET;
+ }
+
+ ret = regmap_update_bits(priv->regmap, RT4505_REG_ENABLE,
+ RT4505_ENABLE_MASK, val);
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static enum led_brightness rt4505_torch_brightness_get(
+ struct led_classdev *lcdev)
+{
+ struct rt4505_priv *priv =
+ container_of(lcdev, struct rt4505_priv, flash.led_cdev);
+ u32 val;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = regmap_read(priv->regmap, RT4505_REG_ENABLE, &val);
+ if (ret) {
+ dev_err(lcdev->dev, "Failed to get LED enable\n");
+ ret = LED_OFF;
+ goto unlock;
+ }
+
+ if ((val & RT4505_ENABLE_MASK) != RT4505_TORCH_SET) {
+ ret = LED_OFF;
+ goto unlock;
+ }
+
+ ret = regmap_read(priv->regmap, RT4505_REG_ILED, &val);
+ if (ret) {
+ dev_err(lcdev->dev, "Failed to get LED brightness\n");
+ ret = LED_OFF;
+ goto unlock;
+ }
+
+ ret = ((val & RT4505_ITORCH_MASK) >> RT4505_ITORCH_SHIFT) + 1;
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int rt4505_flash_brightness_set(struct led_classdev_flash *fled_cdev,
+ u32 brightness)
+{
+ struct rt4505_priv *priv =
+ container_of(fled_cdev, struct rt4505_priv, flash);
+ struct led_flash_setting *s = &fled_cdev->brightness;
+ u32 val = (brightness - s->min) / s->step;
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = regmap_update_bits(priv->regmap, RT4505_REG_ILED,
+ RT4505_IFLASH_MASK, val);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int rt4505_flash_strobe_set(struct led_classdev_flash *fled_cdev,
+ bool state)
+{
+ struct rt4505_priv *priv =
+ container_of(fled_cdev, struct rt4505_priv, flash);
+ u32 val = state ? RT4505_FLASH_SET : 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = regmap_update_bits(priv->regmap, RT4505_REG_ENABLE,
+ RT4505_ENABLE_MASK, val);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int rt4505_flash_strobe_get(struct led_classdev_flash *fled_cdev,
+ bool *state)
+{
+ struct rt4505_priv *priv =
+ container_of(fled_cdev, struct rt4505_priv, flash);
+ u32 val;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = regmap_read(priv->regmap, RT4505_REG_ENABLE, &val);
+ if (ret)
+ goto unlock;
+
+ *state = (val & RT4505_FLASH_GET) == RT4505_FLASH_GET;
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int rt4505_flash_timeout_set(struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ struct rt4505_priv *priv =
+ container_of(fled_cdev, struct rt4505_priv, flash);
+ struct led_flash_setting *s = &fled_cdev->timeout;
+ u32 val = (timeout - s->min) / s->step;
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = regmap_update_bits(priv->regmap, RT4505_REG_CONFIG,
+ RT4505_FLASHTO_MASK, val);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int rt4505_fault_get(struct led_classdev_flash *fled_cdev, u32 *fault)
+{
+ struct rt4505_priv *priv =
+ container_of(fled_cdev, struct rt4505_priv, flash);
+ u32 val, led_faults = 0;
+ int ret;
+
+ ret = regmap_read(priv->regmap, RT4505_REG_FLAGS, &val);
+ if (ret)
+ return ret;
+
+ if (val & RT4505_OVP_MASK)
+ led_faults |= LED_FAULT_OVER_VOLTAGE;
+
+ if (val & RT4505_SHORT_MASK)
+ led_faults |= LED_FAULT_SHORT_CIRCUIT;
+
+ if (val & RT4505_OTP_MASK)
+ led_faults |= LED_FAULT_OVER_TEMPERATURE;
+
+ if (val & RT4505_TIMEOUT_MASK)
+ led_faults |= LED_FAULT_TIMEOUT;
+
+ *fault = led_faults;
+ return 0;
+}
+
+static const struct led_flash_ops rt4505_flash_ops = {
+ .flash_brightness_set = rt4505_flash_brightness_set,
+ .strobe_set = rt4505_flash_strobe_set,
+ .strobe_get = rt4505_flash_strobe_get,
+ .timeout_set = rt4505_flash_timeout_set,
+ .fault_get = rt4505_fault_get,
+};
+
+static bool rt4505_is_accessible_reg(struct device *dev, unsigned int reg)
+{
+ if (reg == RT4505_REG_RESET ||
+ (reg >= RT4505_REG_CONFIG && reg <= RT4505_REG_FLAGS))
+ return true;
+ return false;
+}
+
+static const struct regmap_config rt4505_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = RT4505_REG_FLAGS,
+
+ .readable_reg = rt4505_is_accessible_reg,
+ .writeable_reg = rt4505_is_accessible_reg,
+};
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static int rt4505_flash_external_strobe_set(struct v4l2_flash *v4l2_flash,
+ bool enable)
+{
+ struct led_classdev_flash *flash = v4l2_flash->fled_cdev;
+ struct rt4505_priv *priv =
+ container_of(flash, struct rt4505_priv, flash);
+ u32 val = enable ? RT4505_EXT_FLASH_SET : 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = regmap_update_bits(priv->regmap, RT4505_REG_ENABLE,
+ RT4505_ENABLE_MASK, val);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = rt4505_flash_external_strobe_set,
+};
+
+static void rt4505_init_v4l2_config(struct rt4505_priv *priv,
+ struct v4l2_flash_config *config)
+{
+ struct led_classdev_flash *flash = &priv->flash;
+ struct led_classdev *lcdev = &flash->led_cdev;
+ struct led_flash_setting *s;
+
+ strscpy(config->dev_name, lcdev->dev->kobj.name,
+ sizeof(config->dev_name));
+
+ s = &config->intensity;
+ s->min = RT4505_ITORCH_MINUA;
+ s->step = RT4505_ITORCH_STPUA;
+ s->max = s->val = s->min + (lcdev->max_brightness - 1) * s->step;
+
+ config->flash_faults = LED_FAULT_OVER_VOLTAGE |
+ LED_FAULT_SHORT_CIRCUIT |
+ LED_FAULT_LED_OVER_TEMPERATURE |
+ LED_FAULT_TIMEOUT;
+ config->has_external_strobe = 1;
+}
+#else
+static const struct v4l2_flash_ops v4l2_flash_ops;
+static void rt4505_init_v4l2_config(struct rt4505_priv *priv,
+ struct v4l2_flash_config *config)
+{
+}
+#endif
+
+static void rt4505_init_flash_properties(struct rt4505_priv *priv,
+ struct fwnode_handle *child)
+{
+ struct led_classdev_flash *flash = &priv->flash;
+ struct led_classdev *lcdev = &flash->led_cdev;
+ struct led_flash_setting *s;
+ u32 val;
+ int ret;
+
+ ret = fwnode_property_read_u32(child, "led-max-microamp", &val);
+ if (ret) {
+ dev_warn(priv->dev, "led-max-microamp DT property missing\n");
+ val = RT4505_ITORCH_MINUA;
+ } else
+ val = clamp_val(val, RT4505_ITORCH_MINUA, RT4505_ITORCH_MAXUA);
+
+ lcdev->max_brightness =
+ (val - RT4505_ITORCH_MINUA) / RT4505_ITORCH_STPUA + 1;
+ lcdev->brightness_set_blocking = rt4505_torch_brightness_set;
+ lcdev->brightness_get = rt4505_torch_brightness_get;
+ lcdev->flags |= LED_DEV_CAP_FLASH;
+
+ ret = fwnode_property_read_u32(child, "flash-max-microamp", &val);
+ if (ret) {
+ dev_warn(priv->dev, "flash-max-microamp DT property missing\n");
+ val = RT4505_IFLASH_MINUA;
+ } else
+ val = clamp_val(val, RT4505_IFLASH_MINUA, RT4505_IFLASH_MAXUA);
+
+ s = &flash->brightness;
+ s->min = RT4505_IFLASH_MINUA;
+ s->step = RT4505_IFLASH_STPUA;
+ s->max = s->val = val;
+
+ ret = fwnode_property_read_u32(child, "flash-max-timeout-us", &val);
+ if (ret) {
+ dev_warn(priv->dev,
+ "flash-max-timeout-us DT property missing\n");
+ val = RT4505_FLASHTO_MINUS;
+ } else
+ val = clamp_val(val, RT4505_FLASHTO_MINUS,
+ RT4505_FLASHTO_MAXUS);
+
+ s = &flash->timeout;
+ s->min = RT4505_FLASHTO_MINUS;
+ s->step = RT4505_FLASHTO_STPUS;
+ s->max = s->val = val;
+
+ flash->ops = &rt4505_flash_ops;
+}
+
+static int rt4505_probe(struct i2c_client *client)
+{
+ struct rt4505_priv *priv;
+ struct fwnode_handle *child;
+ struct led_init_data init_data = {};
+ struct v4l2_flash_config v4l2_config = {};
+ int ret;
+
+ priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = &client->dev;
+ mutex_init(&priv->lock);
+
+ priv->regmap = devm_regmap_init_i2c(client, &rt4505_regmap_config);
+ if (IS_ERR(priv->regmap)) {
+ dev_err(priv->dev, "Failed to allocate register map\n");
+ return PTR_ERR(priv->regmap);
+ }
+
+ ret = regmap_write(priv->regmap, RT4505_REG_RESET, RT4505_RESET_MASK);
+ if (ret) {
+ dev_err(priv->dev, "Failed to reset registers\n");
+ return ret;
+ }
+
+ child = fwnode_get_next_available_child_node(client->dev.fwnode, NULL);
+ if (!child) {
+ dev_err(priv->dev, "Failed to get child node\n");
+ return -EINVAL;
+ }
+ init_data.fwnode = child;
+
+ rt4505_init_flash_properties(priv, child);
+ ret = devm_led_classdev_flash_register_ext(priv->dev, &priv->flash,
+ &init_data);
+ if (ret) {
+ dev_err(priv->dev, "Failed to register flash\n");
+ return ret;
+ }
+
+ rt4505_init_v4l2_config(priv, &v4l2_config);
+ priv->v4l2_flash = v4l2_flash_init(priv->dev, init_data.fwnode,
+ &priv->flash, &v4l2_flash_ops,
+ &v4l2_config);
+ if (IS_ERR(priv->v4l2_flash)) {
+ dev_err(priv->dev, "Failed to register v4l2 flash\n");
+ return PTR_ERR(priv->v4l2_flash);
+ }
+
+ i2c_set_clientdata(client, priv);
+ return 0;
+}
+
+static void rt4505_remove(struct i2c_client *client)
+{
+ struct rt4505_priv *priv = i2c_get_clientdata(client);
+
+ v4l2_flash_release(priv->v4l2_flash);
+}
+
+static void rt4505_shutdown(struct i2c_client *client)
+{
+ struct rt4505_priv *priv = i2c_get_clientdata(client);
+
+ /* Reset registers to make sure all off before shutdown */
+ regmap_write(priv->regmap, RT4505_REG_RESET, RT4505_RESET_MASK);
+}
+
+static const struct of_device_id __maybe_unused rt4505_leds_match[] = {
+ { .compatible = "richtek,rt4505", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, rt4505_leds_match);
+
+static struct i2c_driver rt4505_driver = {
+ .driver = {
+ .name = "rt4505",
+ .of_match_table = of_match_ptr(rt4505_leds_match),
+ },
+ .probe_new = rt4505_probe,
+ .remove = rt4505_remove,
+ .shutdown = rt4505_shutdown,
+};
+module_i2c_driver(rt4505_driver);
+
+MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-rt8515.c b/drivers/leds/flash/leds-rt8515.c
new file mode 100644
index 000000000..44904fdee
--- /dev/null
+++ b/drivers/leds/flash/leds-rt8515.c
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LED driver for Richtek RT8515 flash/torch white LEDs
+ * found on some Samsung mobile phones.
+ *
+ * This is a 1.5A Boost dual channel driver produced around 2011.
+ *
+ * The component lacks a datasheet, but in the schematic picture
+ * from the LG P970 service manual you can see the connections
+ * from the RT8515 to the LED, with two resistors connected
+ * from the pins "RFS" and "RTS" to ground.
+ *
+ * On the LG P970:
+ * RFS (resistance flash setting?) is 20 kOhm
+ * RTS (resistance torch setting?) is 39 kOhm
+ *
+ * Some sleuthing finds us the RT9387A which we have a datasheet for:
+ * https://static5.arrow.com/pdfs/2014/7/27/8/21/12/794/rtt_/manual/94download_ds.jspprt9387a.jspprt9387a.pdf
+ * This apparently works the same way so in theory this driver
+ * should cover RT9387A as well. This has not been tested, please
+ * update the compatibles if you add RT9387A support.
+ *
+ * Linus Walleij <linus.walleij@linaro.org>
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/led-class-flash.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+
+#include <media/v4l2-flash-led-class.h>
+
+/* We can provide 15-700 mA out to the LED */
+#define RT8515_MIN_IOUT_MA 15
+#define RT8515_MAX_IOUT_MA 700
+/* The maximum intensity is 1-16 for flash and 1-100 for torch */
+#define RT8515_FLASH_MAX 16
+#define RT8515_TORCH_MAX 100
+
+#define RT8515_TIMEOUT_US 250000U
+#define RT8515_MAX_TIMEOUT_US 300000U
+
+struct rt8515 {
+ struct led_classdev_flash fled;
+ struct device *dev;
+ struct v4l2_flash *v4l2_flash;
+ struct mutex lock;
+ struct regulator *reg;
+ struct gpio_desc *enable_torch;
+ struct gpio_desc *enable_flash;
+ struct timer_list powerdown_timer;
+ u32 max_timeout; /* Flash max timeout */
+ int flash_max_intensity;
+ int torch_max_intensity;
+};
+
+static struct rt8515 *to_rt8515(struct led_classdev_flash *fled)
+{
+ return container_of(fled, struct rt8515, fled);
+}
+
+static void rt8515_gpio_led_off(struct rt8515 *rt)
+{
+ gpiod_set_value(rt->enable_flash, 0);
+ gpiod_set_value(rt->enable_torch, 0);
+}
+
+static void rt8515_gpio_brightness_commit(struct gpio_desc *gpiod,
+ int brightness)
+{
+ int i;
+
+ /*
+ * Toggling a GPIO line with a small delay increases the
+ * brightness one step at a time.
+ */
+ for (i = 0; i < brightness; i++) {
+ gpiod_set_value(gpiod, 0);
+ udelay(1);
+ gpiod_set_value(gpiod, 1);
+ udelay(1);
+ }
+}
+
+/* This is setting the torch light level */
+static int rt8515_led_brightness_set(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled = lcdev_to_flcdev(led);
+ struct rt8515 *rt = to_rt8515(fled);
+
+ mutex_lock(&rt->lock);
+
+ if (brightness == LED_OFF) {
+ /* Off */
+ rt8515_gpio_led_off(rt);
+ } else if (brightness < RT8515_TORCH_MAX) {
+ /* Step it up to movie mode brightness using the flash pin */
+ rt8515_gpio_brightness_commit(rt->enable_torch, brightness);
+ } else {
+ /* Max torch brightness requested */
+ gpiod_set_value(rt->enable_torch, 1);
+ }
+
+ mutex_unlock(&rt->lock);
+
+ return 0;
+}
+
+static int rt8515_led_flash_strobe_set(struct led_classdev_flash *fled,
+ bool state)
+{
+ struct rt8515 *rt = to_rt8515(fled);
+ struct led_flash_setting *timeout = &fled->timeout;
+ int brightness = rt->flash_max_intensity;
+
+ mutex_lock(&rt->lock);
+
+ if (state) {
+ /* Enable LED flash mode and set brightness */
+ rt8515_gpio_brightness_commit(rt->enable_flash, brightness);
+ /* Set timeout */
+ mod_timer(&rt->powerdown_timer,
+ jiffies + usecs_to_jiffies(timeout->val));
+ } else {
+ del_timer_sync(&rt->powerdown_timer);
+ /* Turn the LED off */
+ rt8515_gpio_led_off(rt);
+ }
+
+ fled->led_cdev.brightness = LED_OFF;
+ /* After this the torch LED will be disabled */
+
+ mutex_unlock(&rt->lock);
+
+ return 0;
+}
+
+static int rt8515_led_flash_strobe_get(struct led_classdev_flash *fled,
+ bool *state)
+{
+ struct rt8515 *rt = to_rt8515(fled);
+
+ *state = timer_pending(&rt->powerdown_timer);
+
+ return 0;
+}
+
+static int rt8515_led_flash_timeout_set(struct led_classdev_flash *fled,
+ u32 timeout)
+{
+ /* The timeout is stored in the led-class-flash core */
+ return 0;
+}
+
+static const struct led_flash_ops rt8515_flash_ops = {
+ .strobe_set = rt8515_led_flash_strobe_set,
+ .strobe_get = rt8515_led_flash_strobe_get,
+ .timeout_set = rt8515_led_flash_timeout_set,
+};
+
+static void rt8515_powerdown_timer(struct timer_list *t)
+{
+ struct rt8515 *rt = from_timer(rt, t, powerdown_timer);
+
+ /* Turn the LED off */
+ rt8515_gpio_led_off(rt);
+}
+
+static void rt8515_init_flash_timeout(struct rt8515 *rt)
+{
+ struct led_classdev_flash *fled = &rt->fled;
+ struct led_flash_setting *s;
+
+ /* Init flash timeout setting */
+ s = &fled->timeout;
+ s->min = 1;
+ s->max = rt->max_timeout;
+ s->step = 1;
+ /*
+ * Set default timeout to RT8515_TIMEOUT_US except if
+ * max_timeout from DT is lower.
+ */
+ s->val = min(rt->max_timeout, RT8515_TIMEOUT_US);
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+/* Configure the V2L2 flash subdevice */
+static void rt8515_init_v4l2_flash_config(struct rt8515 *rt,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+ struct led_classdev *led = &rt->fled.led_cdev;
+ struct led_flash_setting *s;
+
+ strscpy(v4l2_sd_cfg->dev_name, led->dev->kobj.name,
+ sizeof(v4l2_sd_cfg->dev_name));
+
+ /*
+ * Init flash intensity setting: this is a linear scale
+ * capped from the device tree max intensity setting
+ * 1..flash_max_intensity
+ */
+ s = &v4l2_sd_cfg->intensity;
+ s->min = 1;
+ s->max = rt->flash_max_intensity;
+ s->step = 1;
+ s->val = s->max;
+}
+
+static void rt8515_v4l2_flash_release(struct rt8515 *rt)
+{
+ v4l2_flash_release(rt->v4l2_flash);
+}
+
+#else
+static void rt8515_init_v4l2_flash_config(struct rt8515 *rt,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+
+static void rt8515_v4l2_flash_release(struct rt8515 *rt)
+{
+}
+#endif
+
+static void rt8515_determine_max_intensity(struct rt8515 *rt,
+ struct fwnode_handle *led,
+ const char *resistance,
+ const char *max_ua_prop, int hw_max,
+ int *max_intensity_setting)
+{
+ u32 res = 0; /* Can't be 0 so 0 is undefined */
+ u32 ua;
+ u32 max_ma;
+ int max_intensity;
+ int ret;
+
+ fwnode_property_read_u32(rt->dev->fwnode, resistance, &res);
+ ret = fwnode_property_read_u32(led, max_ua_prop, &ua);
+
+ /* Missing info in DT, OK go with hardware maxima */
+ if (ret || res == 0) {
+ dev_err(rt->dev,
+ "either %s or %s missing from DT, using HW max\n",
+ resistance, max_ua_prop);
+ max_ma = RT8515_MAX_IOUT_MA;
+ max_intensity = hw_max;
+ goto out_assign_max;
+ }
+
+ /*
+ * Formula from the datasheet, this is the maximum current
+ * defined by the hardware.
+ */
+ max_ma = (5500 * 1000) / res;
+ /*
+ * Calculate max intensity (linear scaling)
+ * Formula is ((ua / 1000) / max_ma) * 100, then simplified
+ */
+ max_intensity = (ua / 10) / max_ma;
+
+ dev_info(rt->dev,
+ "current restricted from %u to %u mA, max intensity %d/100\n",
+ max_ma, (ua / 1000), max_intensity);
+
+out_assign_max:
+ dev_info(rt->dev, "max intensity %d/%d = %d mA\n",
+ max_intensity, hw_max, max_ma);
+ *max_intensity_setting = max_intensity;
+}
+
+static int rt8515_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fwnode_handle *child;
+ struct rt8515 *rt;
+ struct led_classdev *led;
+ struct led_classdev_flash *fled;
+ struct led_init_data init_data = {};
+ struct v4l2_flash_config v4l2_sd_cfg = {};
+ int ret;
+
+ rt = devm_kzalloc(dev, sizeof(*rt), GFP_KERNEL);
+ if (!rt)
+ return -ENOMEM;
+
+ rt->dev = dev;
+ fled = &rt->fled;
+ led = &fled->led_cdev;
+
+ /* ENF - Enable Flash line */
+ rt->enable_flash = devm_gpiod_get(dev, "enf", GPIOD_OUT_LOW);
+ if (IS_ERR(rt->enable_flash))
+ return dev_err_probe(dev, PTR_ERR(rt->enable_flash),
+ "cannot get ENF (enable flash) GPIO\n");
+
+ /* ENT - Enable Torch line */
+ rt->enable_torch = devm_gpiod_get(dev, "ent", GPIOD_OUT_LOW);
+ if (IS_ERR(rt->enable_torch))
+ return dev_err_probe(dev, PTR_ERR(rt->enable_torch),
+ "cannot get ENT (enable torch) GPIO\n");
+
+ child = fwnode_get_next_available_child_node(dev->fwnode, NULL);
+ if (!child) {
+ dev_err(dev,
+ "No fwnode child node found for connected LED.\n");
+ return -EINVAL;
+ }
+ init_data.fwnode = child;
+
+ rt8515_determine_max_intensity(rt, child, "richtek,rfs-ohms",
+ "flash-max-microamp",
+ RT8515_FLASH_MAX,
+ &rt->flash_max_intensity);
+ rt8515_determine_max_intensity(rt, child, "richtek,rts-ohms",
+ "led-max-microamp",
+ RT8515_TORCH_MAX,
+ &rt->torch_max_intensity);
+
+ ret = fwnode_property_read_u32(child, "flash-max-timeout-us",
+ &rt->max_timeout);
+ if (ret) {
+ rt->max_timeout = RT8515_MAX_TIMEOUT_US;
+ dev_warn(dev,
+ "flash-max-timeout-us property missing\n");
+ }
+ timer_setup(&rt->powerdown_timer, rt8515_powerdown_timer, 0);
+ rt8515_init_flash_timeout(rt);
+
+ fled->ops = &rt8515_flash_ops;
+
+ led->max_brightness = rt->torch_max_intensity;
+ led->brightness_set_blocking = rt8515_led_brightness_set;
+ led->flags |= LED_CORE_SUSPENDRESUME | LED_DEV_CAP_FLASH;
+
+ mutex_init(&rt->lock);
+
+ platform_set_drvdata(pdev, rt);
+
+ ret = devm_led_classdev_flash_register_ext(dev, fled, &init_data);
+ if (ret) {
+ fwnode_handle_put(child);
+ mutex_destroy(&rt->lock);
+ dev_err(dev, "can't register LED %s\n", led->name);
+ return ret;
+ }
+
+ rt8515_init_v4l2_flash_config(rt, &v4l2_sd_cfg);
+
+ /* Create a V4L2 Flash device if V4L2 flash is enabled */
+ rt->v4l2_flash = v4l2_flash_init(dev, child, fled, NULL, &v4l2_sd_cfg);
+ if (IS_ERR(rt->v4l2_flash)) {
+ ret = PTR_ERR(rt->v4l2_flash);
+ dev_err(dev, "failed to register V4L2 flash device (%d)\n",
+ ret);
+ /*
+ * Continue without the V4L2 flash
+ * (we still have the classdev)
+ */
+ }
+
+ fwnode_handle_put(child);
+ return 0;
+}
+
+static int rt8515_remove(struct platform_device *pdev)
+{
+ struct rt8515 *rt = platform_get_drvdata(pdev);
+
+ rt8515_v4l2_flash_release(rt);
+ del_timer_sync(&rt->powerdown_timer);
+ mutex_destroy(&rt->lock);
+
+ return 0;
+}
+
+static const struct of_device_id rt8515_match[] = {
+ { .compatible = "richtek,rt8515", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rt8515_match);
+
+static struct platform_driver rt8515_driver = {
+ .driver = {
+ .name = "rt8515",
+ .of_match_table = rt8515_match,
+ },
+ .probe = rt8515_probe,
+ .remove = rt8515_remove,
+};
+module_platform_driver(rt8515_driver);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("Richtek RT8515 LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/flash/leds-sgm3140.c b/drivers/leds/flash/leds-sgm3140.c
new file mode 100644
index 000000000..d3a30ad94
--- /dev/null
+++ b/drivers/leds/flash/leds-sgm3140.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2020 Luca Weiss <luca@z3ntu.xyz>
+
+#include <linux/gpio/consumer.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/platform_device.h>
+
+#include <media/v4l2-flash-led-class.h>
+
+#define FLASH_TIMEOUT_DEFAULT 250000U /* 250ms */
+#define FLASH_MAX_TIMEOUT_DEFAULT 300000U /* 300ms */
+
+struct sgm3140 {
+ struct led_classdev_flash fled_cdev;
+ struct v4l2_flash *v4l2_flash;
+
+ struct timer_list powerdown_timer;
+
+ struct gpio_desc *flash_gpio;
+ struct gpio_desc *enable_gpio;
+ struct regulator *vin_regulator;
+
+ bool enabled;
+
+ /* current timeout in us */
+ u32 timeout;
+ /* maximum timeout in us */
+ u32 max_timeout;
+};
+
+static struct sgm3140 *flcdev_to_sgm3140(struct led_classdev_flash *flcdev)
+{
+ return container_of(flcdev, struct sgm3140, fled_cdev);
+}
+
+static int sgm3140_strobe_set(struct led_classdev_flash *fled_cdev, bool state)
+{
+ struct sgm3140 *priv = flcdev_to_sgm3140(fled_cdev);
+ int ret;
+
+ if (priv->enabled == state)
+ return 0;
+
+ if (state) {
+ ret = regulator_enable(priv->vin_regulator);
+ if (ret) {
+ dev_err(fled_cdev->led_cdev.dev,
+ "failed to enable regulator: %d\n", ret);
+ return ret;
+ }
+ gpiod_set_value_cansleep(priv->flash_gpio, 1);
+ gpiod_set_value_cansleep(priv->enable_gpio, 1);
+ mod_timer(&priv->powerdown_timer,
+ jiffies + usecs_to_jiffies(priv->timeout));
+ } else {
+ del_timer_sync(&priv->powerdown_timer);
+ gpiod_set_value_cansleep(priv->enable_gpio, 0);
+ gpiod_set_value_cansleep(priv->flash_gpio, 0);
+ ret = regulator_disable(priv->vin_regulator);
+ if (ret) {
+ dev_err(fled_cdev->led_cdev.dev,
+ "failed to disable regulator: %d\n", ret);
+ return ret;
+ }
+ }
+
+ priv->enabled = state;
+
+ return 0;
+}
+
+static int sgm3140_strobe_get(struct led_classdev_flash *fled_cdev, bool *state)
+{
+ struct sgm3140 *priv = flcdev_to_sgm3140(fled_cdev);
+
+ *state = timer_pending(&priv->powerdown_timer);
+
+ return 0;
+}
+
+static int sgm3140_timeout_set(struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ struct sgm3140 *priv = flcdev_to_sgm3140(fled_cdev);
+
+ priv->timeout = timeout;
+
+ return 0;
+}
+
+static const struct led_flash_ops sgm3140_flash_ops = {
+ .strobe_set = sgm3140_strobe_set,
+ .strobe_get = sgm3140_strobe_get,
+ .timeout_set = sgm3140_timeout_set,
+};
+
+static int sgm3140_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct sgm3140 *priv = flcdev_to_sgm3140(fled_cdev);
+ bool enable = brightness == LED_ON;
+ int ret;
+
+ if (priv->enabled == enable)
+ return 0;
+
+ if (enable) {
+ ret = regulator_enable(priv->vin_regulator);
+ if (ret) {
+ dev_err(led_cdev->dev,
+ "failed to enable regulator: %d\n", ret);
+ return ret;
+ }
+ gpiod_set_value_cansleep(priv->enable_gpio, 1);
+ } else {
+ gpiod_set_value_cansleep(priv->enable_gpio, 0);
+ ret = regulator_disable(priv->vin_regulator);
+ if (ret) {
+ dev_err(led_cdev->dev,
+ "failed to disable regulator: %d\n", ret);
+ return ret;
+ }
+ }
+
+ priv->enabled = enable;
+
+ return 0;
+}
+
+static void sgm3140_powerdown_timer(struct timer_list *t)
+{
+ struct sgm3140 *priv = from_timer(priv, t, powerdown_timer);
+
+ gpiod_set_value(priv->enable_gpio, 0);
+ gpiod_set_value(priv->flash_gpio, 0);
+ regulator_disable(priv->vin_regulator);
+
+ priv->enabled = false;
+}
+
+static void sgm3140_init_flash_timeout(struct sgm3140 *priv)
+{
+ struct led_classdev_flash *fled_cdev = &priv->fled_cdev;
+ struct led_flash_setting *s;
+
+ /* Init flash timeout setting */
+ s = &fled_cdev->timeout;
+ s->min = 1;
+ s->max = priv->max_timeout;
+ s->step = 1;
+ s->val = FLASH_TIMEOUT_DEFAULT;
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static void sgm3140_init_v4l2_flash_config(struct sgm3140 *priv,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+ struct led_classdev *led_cdev = &priv->fled_cdev.led_cdev;
+ struct led_flash_setting *s;
+
+ strscpy(v4l2_sd_cfg->dev_name, led_cdev->dev->kobj.name,
+ sizeof(v4l2_sd_cfg->dev_name));
+
+ /* Init flash intensity setting */
+ s = &v4l2_sd_cfg->intensity;
+ s->min = 0;
+ s->max = 1;
+ s->step = 1;
+ s->val = 1;
+}
+
+#else
+static void sgm3140_init_v4l2_flash_config(struct sgm3140 *priv,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+#endif
+
+static int sgm3140_probe(struct platform_device *pdev)
+{
+ struct sgm3140 *priv;
+ struct led_classdev *led_cdev;
+ struct led_classdev_flash *fled_cdev;
+ struct led_init_data init_data = {};
+ struct fwnode_handle *child_node;
+ struct v4l2_flash_config v4l2_sd_cfg = {};
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->flash_gpio = devm_gpiod_get(&pdev->dev, "flash", GPIOD_OUT_LOW);
+ ret = PTR_ERR_OR_ZERO(priv->flash_gpio);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to request flash gpio\n");
+
+ priv->enable_gpio = devm_gpiod_get(&pdev->dev, "enable", GPIOD_OUT_LOW);
+ ret = PTR_ERR_OR_ZERO(priv->enable_gpio);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to request enable gpio\n");
+
+ priv->vin_regulator = devm_regulator_get(&pdev->dev, "vin");
+ ret = PTR_ERR_OR_ZERO(priv->vin_regulator);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to request regulator\n");
+
+ child_node = fwnode_get_next_available_child_node(pdev->dev.fwnode,
+ NULL);
+ if (!child_node) {
+ dev_err(&pdev->dev,
+ "No fwnode child node found for connected LED.\n");
+ return -EINVAL;
+ }
+
+ ret = fwnode_property_read_u32(child_node, "flash-max-timeout-us",
+ &priv->max_timeout);
+ if (ret) {
+ priv->max_timeout = FLASH_MAX_TIMEOUT_DEFAULT;
+ dev_warn(&pdev->dev,
+ "flash-max-timeout-us property missing\n");
+ }
+
+ /*
+ * Set default timeout to FLASH_DEFAULT_TIMEOUT except if max_timeout
+ * from DT is lower.
+ */
+ priv->timeout = min(priv->max_timeout, FLASH_TIMEOUT_DEFAULT);
+
+ timer_setup(&priv->powerdown_timer, sgm3140_powerdown_timer, 0);
+
+ fled_cdev = &priv->fled_cdev;
+ led_cdev = &fled_cdev->led_cdev;
+
+ fled_cdev->ops = &sgm3140_flash_ops;
+
+ led_cdev->brightness_set_blocking = sgm3140_brightness_set;
+ led_cdev->max_brightness = LED_ON;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+
+ sgm3140_init_flash_timeout(priv);
+
+ init_data.fwnode = child_node;
+
+ platform_set_drvdata(pdev, priv);
+
+ /* Register in the LED subsystem */
+ ret = devm_led_classdev_flash_register_ext(&pdev->dev,
+ fled_cdev, &init_data);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register flash device: %d\n",
+ ret);
+ goto err;
+ }
+
+ sgm3140_init_v4l2_flash_config(priv, &v4l2_sd_cfg);
+
+ /* Create V4L2 Flash subdev */
+ priv->v4l2_flash = v4l2_flash_init(&pdev->dev,
+ child_node,
+ fled_cdev, NULL,
+ &v4l2_sd_cfg);
+ if (IS_ERR(priv->v4l2_flash)) {
+ ret = PTR_ERR(priv->v4l2_flash);
+ goto err;
+ }
+
+ return ret;
+
+err:
+ fwnode_handle_put(child_node);
+ return ret;
+}
+
+static int sgm3140_remove(struct platform_device *pdev)
+{
+ struct sgm3140 *priv = platform_get_drvdata(pdev);
+
+ del_timer_sync(&priv->powerdown_timer);
+
+ v4l2_flash_release(priv->v4l2_flash);
+
+ return 0;
+}
+
+static const struct of_device_id sgm3140_dt_match[] = {
+ { .compatible = "ocs,ocp8110" },
+ { .compatible = "sgmicro,sgm3140" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sgm3140_dt_match);
+
+static struct platform_driver sgm3140_driver = {
+ .probe = sgm3140_probe,
+ .remove = sgm3140_remove,
+ .driver = {
+ .name = "sgm3140",
+ .of_match_table = sgm3140_dt_match,
+ },
+};
+
+module_platform_driver(sgm3140_driver);
+
+MODULE_AUTHOR("Luca Weiss <luca@z3ntu.xyz>");
+MODULE_DESCRIPTION("SG Micro SGM3140 charge pump LED driver");
+MODULE_LICENSE("GPL v2");