diff options
| author | Adrian Betschart <adrian.betschart@cinemaone.ch> | 2026-09-11 17:13:14 +0200 |
|---|---|---|
| committer | Alex Deucher <alexander.deucher@amd.com> | 2026-10-01 17:18:05 -0400 |
| commit | 106b4a2a78b2a2f14885604891957b84bba502db (patch) | |
| tree | 5d40eef2428fb1a4791ab46a4b19091a5910eac2 /drivers/leds/flash | |
| download | linux-stable-106b4a2a78b2a2f14885604891957b84bba502db.tar.gz linux-stable-106b4a2a78b2a2f14885604891957b84bba502db.zip | |
drm/amd/display: Try RGB before YCbCr 4:4:4 in stream validationgrafted
amdgpu_dm_create_validate_stream_for_sink() walks encoding_order[] and
uses the first encoding that validates. YCbCr 4:4:4 is listed before
RGB, so an HDMI sink that advertises 4:4:4 gets YCbCr 4:4:4 whenever the
"color format" property is left at AUTO, even though RGB fits the same
link.
That contradicts the documented AUTO behaviour for HDMI in enum
drm_connector_color_format (RGB, falling back to YCbCr 4:2:0 only when
the bandwidth is not available or the mode is 4:2:0-only), which the
amdgpu implementation of the property also describes. It also leaves
the "Broadcast RGB" property without effect on such sinks, since the
quantization range it selects only applies to RGB output.
Try RGB first. The mask still holds every encoding the sink supports,
so a mode that cannot carry RGB falls back exactly as before.
For reference, v7.2 picked RGB here unless YCbCr 4:4:4 was forced
through debugfs, while earlier kernels picked YCbCr 4:4:4 for any HDMI
sink that advertised it.
Fixes: 0b0ff65d3ca1 ("drm/amd/display: Refactor stream validation")
Suggested-by: Adolfo Rodrigues <adolfotregosa@gmail.com>
Assisted-by: Claude Code:claude-fable-5-1
Signed-off-by: Adrian Betschart <adrian.betschart@cinemaone.ch>
Reviewed-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Adolfo Rodrigues <adolfotregosa@gmail.com>
Tested-by: Daniel Wheeler <daniel.wheeler@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
(cherry picked from commit 7de432bc753133764dc40d37f9388da56de251e7)
Diffstat (limited to 'drivers/leds/flash')
| -rw-r--r-- | drivers/leds/flash/Kconfig | 158 | ||||
| -rw-r--r-- | drivers/leds/flash/Makefile | 16 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-aat1290.c | 550 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-as3645a.c | 770 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-ktd2692.c | 355 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-lm3601x.c | 494 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-max77693.c | 1057 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-mt6360.c | 895 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-mt6370-flash.c | 568 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-qcom-flash.c | 989 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-rt4505.c | 430 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-rt8515.c | 396 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-s2m-flash.c | 350 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-sgm3140.c | 313 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-sy7802.c | 539 | ||||
| -rw-r--r-- | drivers/leds/flash/leds-tps6131x.c | 815 |
16 files changed, 8695 insertions, 0 deletions
diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig new file mode 100644 index 000000000..9b6dd3f1f --- /dev/null +++ b/drivers/leds/flash/Kconfig @@ -0,0 +1,158 @@ +# 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 + select LEDS_EXPRESSWIRE + 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 + depends on 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_MT6370_FLASH + tristate "Flash LED Support for MediaTek MT6370 PMIC" + depends on LEDS_CLASS + depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS + depends on MFD_MT6370 + help + Support 2 channels and torch/strobe mode. + Say Y here to enable support for + MT6370_FLASH_LED device. + + This driver can also be built as a module. If so, the module + will be called "leds-mt6370-flash". + +config LEDS_QCOM_FLASH + tristate "LED support for flash module inside Qualcomm PMIC" + depends on MFD_SPMI_PMIC || COMPILE_TEST + depends on LEDS_CLASS && OF + depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS + select REGMAP + help + This option enables support for the flash module found in Qualcomm + Technologies, Inc. PMICs. The flash module can have 3 or 4 flash LED + channels and each channel is programmable to support up to 1.5 A full + scale current. It also supports connecting two channels' output together + to supply one LED component to achieve current up to 2 A. In such case, + the total LED current will be split symmetrically on each channel and + they will be enabled/disabled at the same time. + + This driver can be built as a module, it will be called "leds-qcom-flash". + +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_S2M_FLASH + tristate "Samsung S2M series PMICs flash/torch LED support" + depends on LEDS_CLASS + depends on MFD_SEC_CORE + depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS + help + This option enables support for the flash/torch LEDs found in certain + Samsung S2M series PMICs, such as the S2MU005. It has a LED channel + dedicated for every physical LED. The LEDs can be controlled in flash + and torch modes. + +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. + +config LEDS_SY7802 + tristate "LED support for the Silergy SY7802" + depends on I2C && OF + depends on GPIOLIB + select REGMAP_I2C + help + This option enables support for the SY7802 flash LED controller. + SY7802 includes torch and flash functions with programmable current. + + This driver can be built as a module, it will be called "leds-sy7802". + +config LEDS_TPS6131X + tristate "LED support for TI TPS6131x flash LED driver" + depends on I2C && OF + depends on GPIOLIB + depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS + select REGMAP_I2C + help + This option enables support for Texas Instruments TPS61310/TPS61311 + flash LED driver. + + This driver can be built as a module, it will be called "leds-tps6131x". + +endif # LEDS_CLASS_FLASH diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile new file mode 100644 index 000000000..44e6c1b4b --- /dev/null +++ b/drivers/leds/flash/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_LEDS_MT6360) += leds-mt6360.o +obj-$(CONFIG_LEDS_MT6370_FLASH) += leds-mt6370-flash.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_QCOM_FLASH) += leds-qcom-flash.o +obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o +obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o +obj-$(CONFIG_LEDS_S2M_FLASH) += leds-s2m-flash.o +obj-$(CONFIG_LEDS_SGM3140) += leds-sgm3140.o +obj-$(CONFIG_LEDS_SY7802) += leds-sy7802.o +obj-$(CONFIG_LEDS_TPS6131X) += leds-tps6131x.o diff --git a/drivers/leds/flash/leds-aat1290.c b/drivers/leds/flash/leds-aat1290.c new file mode 100644 index 000000000..49251cfd3 --- /dev/null +++ b/drivers/leds/flash/leds-aat1290.c @@ -0,0 +1,550 @@ +// 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/cleanup.h> +#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; +}; + +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; +#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 + + struct device_node *child_node __free(device_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"); + return ret; + } + + 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"); + return ret; + } + + *sub_node = child_node; + + return 0; +} + +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; + + strscpy(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 void 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); +} + +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..5fefcaef3 --- /dev/null +++ b/drivers/leds/flash/leds-as3645a.c @@ -0,0 +1,770 @@ +// 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 device *dev, struct as3645a *flash) +{ + struct as3645a_config *cfg = &flash->cfg; + int rval; + + device_for_each_child_node_scoped(dev, 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, + }, + }; + + strscpy(cfg.dev_name, led->dev->kobj.name, sizeof(cfg.dev_name)); + strscpy(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(&client->dev, flash); + 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[] = { + { .name = AS_NAME }, + { } +}; +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 = 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..22fbfccd4 --- /dev/null +++ b/drivers/leds/flash/leds-ktd2692.c @@ -0,0 +1,355 @@ +// 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/cleanup.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/leds-expresswire.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 + +/* KTD2692 default length of name */ +#define KTD2692_NAME_LENGTH 20 + +/* 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; +}; + +static const struct expresswire_timing ktd2692_timing = { + .poweroff_us = 700, + .data_start_us = 10, + .end_of_data_low_us = 10, + .end_of_data_high_us = 350, + .short_bitset_us = 4, + .long_bitset_us = 12 +}; + +struct ktd2692_context { + /* Common ExpressWire properties (ctrl GPIO and timing) */ + struct expresswire_common_props props; + + /* 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; + + 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 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, 0); + } else { + expresswire_write_u8(&led->props, brightness | + KTD2692_REG_MOVIE_CURRENT_BASE); + led->mode = KTD2692_MODE_MOVIE; + } + + expresswire_write_u8(&led->props, 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); + expresswire_write_u8(&led->props, flash_tm_reg + | KTD2692_REG_FLASH_TIMEOUT_BASE); + + led->mode = KTD2692_MODE_FLASH; + gpiod_direction_output(led->aux_gpio, 1); + } else { + led->mode = KTD2692_MODE_DISABLE; + gpiod_direction_output(led->aux_gpio, 0); + } + + expresswire_write_u8(&led->props, 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; + expresswire_power_off(&led->props); + gpiod_direction_output(led->aux_gpio, 0); + + expresswire_write_u8(&led->props, (KTD2692_MM_MIN_CURR_THRESHOLD_SCALE - 1) + | KTD2692_REG_MM_MIN_CURR_THRESHOLD_BASE); + expresswire_write_u8(&led->props, 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); + int ret; + + if (!np) + return -ENXIO; + + led->props.ctrl_gpio = devm_gpiod_get(dev, "ctrl", GPIOD_ASIS); + ret = PTR_ERR_OR_ZERO(led->props.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; + } + } + + struct device_node *child_node __free(device_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"); + return ret; + } + + 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"); + return ret; + } + + 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"); + return ret; + } + + return 0; +} + +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; + led->props.timing = ktd2692_timing; + + 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 void 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); +} + +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_IMPORT_NS("EXPRESSWIRE"); +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..8d00510c8 --- /dev/null +++ b/drivers/leds/flash/leds-lm3601x.c @@ -0,0 +1,494 @@ +// 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 - private lm3601x LED data + * @fled_cdev: flash LED class device pointer + * @client: Pointer to the I2C client + * @regmap: Devices register map + * @lock: Lock for reading/writing the device + * @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_MAPLE, + .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 - 1); + 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, ¤t_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_MIN_TORCH_I_UA + 1, + 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; + } + + if (led->torch_current_max > LM3601X_MAX_TORCH_I_UA) { + dev_warn(&led->client->dev, + "Max torch current set too high (%d vs %d)\n", + led->torch_current_max, + LM3601X_MAX_TORCH_I_UA); + led->torch_current_max = LM3601X_MAX_TORCH_I_UA; + } + + 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; + } + + ret = regmap_write(led->regmap, LM3601X_DEV_ID_REG, LM3601X_SW_RESET); + if (ret) + dev_warn(&client->dev, "Failed to reset the LED controller\n"); + + 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[] = { + { .name = "LM36010", .driver_data = CHIP_LM36010 }, + { .name = "LM36011", .driver_data = 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 = 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..daee10986 --- /dev/null +++ b/drivers/leds/flash/leds-max77693.c @@ -0,0 +1,1057 @@ +// 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); + 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_scoped(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"); + 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"); + return -EINVAL; + } + + if (sub_nodes[fled_id]) { + dev_err(dev, + "Conflicting \"led-sources\" DT properties\n"); + return -EINVAL; + } + + sub_nodes[fled_id] = of_node_get(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))) + 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) + goto err_setup; + + 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; + } + of_node_put(sub_nodes[i]); + sub_nodes[i] = NULL; + } + + 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); + +err_setup: + for (i = FLED1; i <= FLED2; i++) + of_node_put(sub_nodes[i]); + return ret; +} + +static void 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); +} + +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..462a902f5 --- /dev/null +++ b/drivers/leds/flash/leds-mt6360.c @@ -0,0 +1,895 @@ +// 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 + +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; + enum led_default_state 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[] __counted_by(leds_count); +}; + +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 = 0; + + mutex_lock(&priv->lock); + + /* + * If the state of the upcoming change is the same as the current LED + * device state, then skip the subsequent code to avoid conflict + * with the flow of turning on LED torch mode in V4L2. + */ + if (state == !!(BIT(led->led_no) & prev)) { + dev_info(lcdev->dev, "No change in strobe state [0x%x]\n", prev); + goto unlock; + } + + /* + * 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), ®val); + if (ret) + return ret; + level = regval & MT6360_ISNK_MASK; + + ret = regmap_read(priv->regmap, MT6360_REG_RGBEN, ®val); + if (ret) + return ret; + + if (!(regval & MT6360_ISNK_ENMASK(led->led_no))) + level = LED_OFF; + + switch (led->default_state) { + case LEDS_DEFSTATE_ON: + led->isnk.brightness = led->isnk.max_brightness; + break; + case LEDS_DEFSTATE_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), + ®val); + if (ret) + return ret; + level = regval & MT6360_ITORCH_MASK; + + ret = regmap_read(priv->regmap, MT6360_REG_FLEDEN, ®val); + if (ret) + return ret; + + if ((regval & enable_mask) == enable_mask) + level += 1; + else + level = LED_OFF; + + switch (led->default_state) { + case LEDS_DEFSTATE_ON: + flash->led_cdev.brightness = flash->led_cdev.max_brightness; + break; + case LEDS_DEFSTATE_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", ®); + if (ret || reg > MT6360_LED_ISNK3 || + priv->leds_active & BIT(reg)) { + fwnode_handle_put(child); + 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); + fwnode_handle_put(child); + 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 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; + 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_scoped(&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", ®); + 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; + led->default_state = led_init_default_state_get(child); + + 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 void mt6360_led_remove(struct platform_device *pdev) +{ + struct mt6360_priv *priv = platform_get_drvdata(pdev); + + mt6360_v4l2_flash_release(priv); +} + +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-mt6370-flash.c b/drivers/leds/flash/leds-mt6370-flash.c new file mode 100644 index 000000000..dbdbe9230 --- /dev/null +++ b/drivers/leds/flash/leds-mt6370-flash.c @@ -0,0 +1,568 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2023 Richtek Technology Corp. + * + * Authors: + * Alice Chen <alice_chen@richtek.com> + * ChiYuan Huang <cy_huang@richtek.com> + */ + +#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/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 { + MT6370_LED_FLASH1 = 0, + MT6370_LED_FLASH2, + MT6370_MAX_LEDS +}; + +/* Virtual definition for multicolor */ + +#define MT6370_REG_FLEDEN 0x17E +#define MT6370_REG_STRBTO 0x173 +#define MT6370_REG_CHGSTAT2 0x1D1 +#define MT6370_REG_FLEDSTAT1 0x1D9 +#define MT6370_REG_FLEDISTRB(_id) (0x174 + 4 * (_id)) +#define MT6370_REG_FLEDITOR(_id) (0x175 + 4 * (_id)) +#define MT6370_ITORCH_MASK GENMASK(4, 0) +#define MT6370_ISTROBE_MASK GENMASK(6, 0) +#define MT6370_STRBTO_MASK GENMASK(6, 0) +#define MT6370_TORCHEN_MASK BIT(3) +#define MT6370_STROBEN_MASK BIT(2) +#define MT6370_FLCSEN_MASK(_id) BIT(MT6370_LED_FLASH2 - (_id)) +#define MT6370_FLCSEN_MASK_ALL GENMASK(1, 0) +#define MT6370_FLEDCHGVINOVP_MASK BIT(3) +#define MT6370_FLED1STRBTO_MASK BIT(11) +#define MT6370_FLED2STRBTO_MASK BIT(10) +#define MT6370_FLED1STRB_MASK BIT(9) +#define MT6370_FLED2STRB_MASK BIT(8) +#define MT6370_FLED1SHORT_MASK BIT(7) +#define MT6370_FLED2SHORT_MASK BIT(6) +#define MT6370_FLEDLVF_MASK BIT(3) + +#define MT6370_LED_JOINT 2 +#define MT6370_RANGE_FLED_REG 4 +#define MT6370_ITORCH_MIN_uA 25000 +#define MT6370_ITORCH_STEP_uA 12500 +#define MT6370_ITORCH_MAX_uA 400000 +#define MT6370_ITORCH_DOUBLE_MAX_uA 800000 +#define MT6370_ISTRB_MIN_uA 50000 +#define MT6370_ISTRB_STEP_uA 12500 +#define MT6370_ISTRB_MAX_uA 1500000 +#define MT6370_ISTRB_DOUBLE_MAX_uA 3000000 +#define MT6370_STRBTO_MIN_US 64000 +#define MT6370_STRBTO_STEP_US 32000 +#define MT6370_STRBTO_MAX_US 2432000 + +#define to_mt6370_led(ptr, member) container_of(ptr, struct mt6370_led, member) + +struct mt6370_led { + struct led_classdev_flash flash; + struct v4l2_flash *v4l2_flash; + struct mt6370_priv *priv; + u8 led_no; +}; + +struct mt6370_priv { + 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 mt6370_led leds[] __counted_by(leds_count); +}; + +static int mt6370_torch_brightness_set(struct led_classdev *lcdev, enum led_brightness level) +{ + struct mt6370_led *led = to_mt6370_led(lcdev, flash.led_cdev); + struct mt6370_priv *priv = led->priv; + u32 led_enable_mask = led->led_no == MT6370_LED_JOINT ? MT6370_FLCSEN_MASK_ALL : + MT6370_FLCSEN_MASK(led->led_no); + u32 enable_mask = MT6370_TORCHEN_MASK | led_enable_mask; + u32 val = level ? led_enable_mask : 0; + u32 curr; + int ret, i; + + mutex_lock(&priv->lock); + + /* + * There is only one set of flash control logic, and this flag is used to check if 'strobe' + * is currently being 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 = priv->fled_torch_used | BIT(led->led_no); + else + curr = priv->fled_torch_used & ~BIT(led->led_no); + + if (curr) + val |= MT6370_TORCHEN_MASK; + + if (level) { + level -= 1; + if (led->led_no == MT6370_LED_JOINT) { + u32 flevel[MT6370_MAX_LEDS]; + + /* + * There're two flash channels in MT6370. If joint flash output is used, + * torch current will be averaged output from both channels. + */ + flevel[0] = level / 2; + flevel[1] = level - flevel[0]; + for (i = 0; i < MT6370_MAX_LEDS; i++) { + ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDITOR(i), + MT6370_ITORCH_MASK, flevel[i]); + if (ret) + goto unlock; + } + } else { + ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDITOR(led->led_no), + MT6370_ITORCH_MASK, level); + if (ret) + goto unlock; + } + } + + ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDEN, enable_mask, val); + if (ret) + goto unlock; + + priv->fled_torch_used = curr; + +unlock: + mutex_unlock(&priv->lock); + return ret; +} + +static int mt6370_flash_brightness_set(struct led_classdev_flash *fl_cdev, u32 brightness) +{ + /* + * Because of the current spikes when turning on the flash, the brightness should be kept + * by the LED framework. This empty function is used to prevent checking failure when + * led_classdev_flash registers ops. + */ + return 0; +} + +static int _mt6370_flash_brightness_set(struct led_classdev_flash *fl_cdev, u32 brightness) +{ + struct mt6370_led *led = to_mt6370_led(fl_cdev, flash); + struct mt6370_priv *priv = led->priv; + struct led_flash_setting *setting = &fl_cdev->brightness; + u32 val = (brightness - setting->min) / setting->step; + int ret, i; + + if (led->led_no == MT6370_LED_JOINT) { + u32 flevel[MT6370_MAX_LEDS]; + + /* + * There're two flash channels in MT6370. If joint flash output is used, storbe + * current will be averaged output from both channels. + */ + flevel[0] = val / 2; + flevel[1] = val - flevel[0]; + for (i = 0; i < MT6370_MAX_LEDS; i++) { + ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDISTRB(i), + MT6370_ISTROBE_MASK, flevel[i]); + if (ret) + break; + } + } else { + ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDISTRB(led->led_no), + MT6370_ISTROBE_MASK, val); + } + + return ret; +} + +static int mt6370_strobe_set(struct led_classdev_flash *fl_cdev, bool state) +{ + struct mt6370_led *led = to_mt6370_led(fl_cdev, flash); + struct mt6370_priv *priv = led->priv; + struct led_classdev *lcdev = &fl_cdev->led_cdev; + struct led_flash_setting *s = &fl_cdev->brightness; + u32 led_enable_mask = led->led_no == MT6370_LED_JOINT ? MT6370_FLCSEN_MASK_ALL : + MT6370_FLCSEN_MASK(led->led_no); + u32 enable_mask = MT6370_STROBEN_MASK | led_enable_mask; + u32 val = state ? led_enable_mask : 0; + u32 curr; + int ret; + + mutex_lock(&priv->lock); + + /* + * There is only one set of flash control logic, and this flag is used to check if 'torch' + * is currently being 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 = priv->fled_strobe_used | BIT(led->led_no); + else + curr = priv->fled_strobe_used & ~BIT(led->led_no); + + if (curr) + val |= MT6370_STROBEN_MASK; + + ret = regmap_update_bits(priv->regmap, MT6370_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 needs to turn on, configure the flash current to ramp up to the setting + * value. Otherwise, always revert to the minimum one. + */ + ret = _mt6370_flash_brightness_set(fl_cdev, state ? s->val : s->min); + if (ret) { + dev_err(lcdev->dev, "[%d] Failed to set brightness\n", led->led_no); + goto unlock; + } + + /* + * For the flash to turn on/off, we must wait for HW ramping up/down time 5ms/500us to + * prevent the unexpected problem. + */ + if (!priv->fled_strobe_used && curr) + usleep_range(5000, 6000); + else if (priv->fled_strobe_used && !curr) + usleep_range(500, 600); + + priv->fled_strobe_used = curr; + +unlock: + mutex_unlock(&priv->lock); + return ret; +} + +static int mt6370_strobe_get(struct led_classdev_flash *fl_cdev, bool *state) +{ + struct mt6370_led *led = to_mt6370_led(fl_cdev, flash); + struct mt6370_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 mt6370_timeout_set(struct led_classdev_flash *fl_cdev, u32 timeout) +{ + struct mt6370_led *led = to_mt6370_led(fl_cdev, flash); + struct mt6370_priv *priv = led->priv; + struct led_flash_setting *s = &fl_cdev->timeout; + u32 val = (timeout - s->min) / s->step; + + return regmap_update_bits(priv->regmap, MT6370_REG_STRBTO, MT6370_STRBTO_MASK, val); +} + +static int mt6370_fault_get(struct led_classdev_flash *fl_cdev, u32 *fault) +{ + struct mt6370_led *led = to_mt6370_led(fl_cdev, flash); + struct mt6370_priv *priv = led->priv; + u16 fled_stat; + unsigned int chg_stat, strobe_timeout_mask, fled_short_mask; + u32 rfault = 0; + int ret; + + ret = regmap_read(priv->regmap, MT6370_REG_CHGSTAT2, &chg_stat); + if (ret) + return ret; + + ret = regmap_raw_read(priv->regmap, MT6370_REG_FLEDSTAT1, &fled_stat, sizeof(fled_stat)); + if (ret) + return ret; + + switch (led->led_no) { + case MT6370_LED_FLASH1: + strobe_timeout_mask = MT6370_FLED1STRBTO_MASK; + fled_short_mask = MT6370_FLED1SHORT_MASK; + break; + + case MT6370_LED_FLASH2: + strobe_timeout_mask = MT6370_FLED2STRBTO_MASK; + fled_short_mask = MT6370_FLED2SHORT_MASK; + break; + + case MT6370_LED_JOINT: + strobe_timeout_mask = MT6370_FLED1STRBTO_MASK | MT6370_FLED2STRBTO_MASK; + fled_short_mask = MT6370_FLED1SHORT_MASK | MT6370_FLED2SHORT_MASK; + break; + default: + return -EINVAL; + } + + if (chg_stat & MT6370_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 & MT6370_FLEDLVF_MASK) + rfault |= LED_FAULT_UNDER_VOLTAGE; + + *fault = rfault; + return ret; +} + +static const struct led_flash_ops mt6370_flash_ops = { + .flash_brightness_set = mt6370_flash_brightness_set, + .strobe_set = mt6370_strobe_set, + .strobe_get = mt6370_strobe_get, + .timeout_set = mt6370_timeout_set, + .fault_get = mt6370_fault_get, +}; + +#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS) +static int mt6370_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, + bool enable) +{ + struct led_classdev_flash *flash = v4l2_flash->fled_cdev; + struct mt6370_led *led = to_mt6370_led(flash, flash); + struct mt6370_priv *priv = led->priv; + u32 mask = led->led_no == MT6370_LED_JOINT ? MT6370_FLCSEN_MASK_ALL : + MT6370_FLCSEN_MASK(led->led_no); + u32 val = enable ? mask : 0; + int ret; + + mutex_lock(&priv->lock); + + ret = regmap_update_bits(priv->regmap, MT6370_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 = mt6370_flash_external_strobe_set, +}; + +static void mt6370_init_v4l2_flash_config(struct mt6370_led *led, struct v4l2_flash_config *cfg) +{ + struct led_classdev *lcdev; + struct led_flash_setting *s = &cfg->intensity; + + lcdev = &led->flash.led_cdev; + + s->min = MT6370_ITORCH_MIN_uA; + s->step = MT6370_ITORCH_STEP_uA; + s->val = s->max = s->min + (lcdev->max_brightness - 1) * s->step; + + cfg->has_external_strobe = 1; + strscpy(cfg->dev_name, dev_name(lcdev->dev), sizeof(cfg->dev_name)); + + cfg->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 mt6370_init_v4l2_flash_config(struct mt6370_led *led, struct v4l2_flash_config *cfg) +{ +} +#endif + +static void mt6370_v4l2_flash_release(void *v4l2_flash) +{ + v4l2_flash_release(v4l2_flash); +} + +static int mt6370_led_register(struct device *parent, struct mt6370_led *led, + struct fwnode_handle *fwnode) +{ + struct led_init_data init_data = { .fwnode = fwnode }; + struct v4l2_flash_config v4l2_config = {}; + int ret; + + ret = devm_led_classdev_flash_register_ext(parent, &led->flash, &init_data); + if (ret) + return dev_err_probe(parent, ret, "Couldn't register flash %d\n", led->led_no); + + mt6370_init_v4l2_flash_config(led, &v4l2_config); + led->v4l2_flash = v4l2_flash_init(parent, fwnode, &led->flash, &v4l2_flash_ops, + &v4l2_config); + if (IS_ERR(led->v4l2_flash)) + return dev_err_probe(parent, PTR_ERR(led->v4l2_flash), + "Failed to register %d v4l2 sd\n", led->led_no); + + return devm_add_action_or_reset(parent, mt6370_v4l2_flash_release, led->v4l2_flash); +} + +static u32 mt6370_clamp(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 mt6370_init_flash_properties(struct device *dev, struct mt6370_led *led, + struct fwnode_handle *fwnode) +{ + struct led_classdev_flash *flash = &led->flash; + struct led_classdev *lcdev = &flash->led_cdev; + struct mt6370_priv *priv = led->priv; + struct led_flash_setting *s; + u32 sources[MT6370_MAX_LEDS]; + u32 max_ua, val; + int i, ret, num; + + num = fwnode_property_count_u32(fwnode, "led-sources"); + if (num < 1) + return dev_err_probe(dev, -EINVAL, + "Not specified or wrong number of led-sources\n"); + + ret = fwnode_property_read_u32_array(fwnode, "led-sources", sources, num); + if (ret) + return ret; + + for (i = 0; i < num; i++) { + if (sources[i] >= MT6370_MAX_LEDS) + return -EINVAL; + if (priv->leds_active & BIT(sources[i])) + return -EINVAL; + priv->leds_active |= BIT(sources[i]); + } + + /* If both channels are specified in 'led-sources', joint flash output mode is used */ + led->led_no = num == 2 ? MT6370_LED_JOINT : sources[0]; + + max_ua = num == 2 ? MT6370_ITORCH_DOUBLE_MAX_uA : MT6370_ITORCH_MAX_uA; + val = MT6370_ITORCH_MIN_uA; + ret = fwnode_property_read_u32(fwnode, "led-max-microamp", &val); + if (!ret) + val = mt6370_clamp(val, MT6370_ITORCH_MIN_uA, max_ua, MT6370_ITORCH_STEP_uA); + + lcdev->max_brightness = (val - MT6370_ITORCH_MIN_uA) / MT6370_ITORCH_STEP_uA + 1; + lcdev->brightness_set_blocking = mt6370_torch_brightness_set; + lcdev->flags |= LED_DEV_CAP_FLASH; + + max_ua = num == 2 ? MT6370_ISTRB_DOUBLE_MAX_uA : MT6370_ISTRB_MAX_uA; + val = MT6370_ISTRB_MIN_uA; + ret = fwnode_property_read_u32(fwnode, "flash-max-microamp", &val); + if (!ret) + val = mt6370_clamp(val, MT6370_ISTRB_MIN_uA, max_ua, MT6370_ISTRB_STEP_uA); + + s = &flash->brightness; + s->min = MT6370_ISTRB_MIN_uA; + s->step = MT6370_ISTRB_STEP_uA; + s->val = s->max = val; + + /* Always configure to the minimum level when off to prevent flash current spikes. */ + ret = _mt6370_flash_brightness_set(flash, s->min); + if (ret) + return ret; + + val = MT6370_STRBTO_MIN_US; + ret = fwnode_property_read_u32(fwnode, "flash-max-timeout-us", &val); + if (!ret) + val = mt6370_clamp(val, MT6370_STRBTO_MIN_US, MT6370_STRBTO_MAX_US, + MT6370_STRBTO_STEP_US); + + s = &flash->timeout; + s->min = MT6370_STRBTO_MIN_US; + s->step = MT6370_STRBTO_STEP_US; + s->val = s->max = val; + + flash->ops = &mt6370_flash_ops; + + return 0; +} + +static int mt6370_led_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mt6370_priv *priv; + size_t count; + int i = 0, ret; + + count = device_get_child_node_count(dev); + if (!count || count > MT6370_MAX_LEDS) + return dev_err_probe(dev, -EINVAL, + "No child node or node count over max led number %zu\n", count); + + priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->leds_count = count; + mutex_init(&priv->lock); + + priv->regmap = dev_get_regmap(dev->parent, NULL); + if (!priv->regmap) + return dev_err_probe(dev, -ENODEV, "Failed to get parent regmap\n"); + + device_for_each_child_node_scoped(dev, child) { + struct mt6370_led *led = priv->leds + i; + + led->priv = priv; + + ret = mt6370_init_flash_properties(dev, led, child); + if (ret) + return ret; + + ret = mt6370_led_register(dev, led, child); + if (ret) + return ret; + + i++; + } + + return 0; +} + +static const struct of_device_id mt6370_led_of_id[] = { + { .compatible = "mediatek,mt6370-flashlight" }, + {} +}; +MODULE_DEVICE_TABLE(of, mt6370_led_of_id); + +static struct platform_driver mt6370_led_driver = { + .driver = { + .name = "mt6370-flashlight", + .of_match_table = mt6370_led_of_id, + }, + .probe = mt6370_led_probe, +}; +module_platform_driver(mt6370_led_driver); + +MODULE_AUTHOR("Alice Chen <alice_chen@richtek.com>"); +MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>"); +MODULE_DESCRIPTION("MT6370 FLASH LED Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/flash/leds-qcom-flash.c b/drivers/leds/flash/leds-qcom-flash.c new file mode 100644 index 000000000..b03a6833e --- /dev/null +++ b/drivers/leds/flash/leds-qcom-flash.c @@ -0,0 +1,989 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022, 2024-2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/leds.h> +#include <linux/led-class-flash.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <media/v4l2-flash-led-class.h> + +/* registers definitions */ +#define FLASH_REVISION_REG 0x00 +#define FLASH_4CH_REVISION_V0P1 0x01 + +#define FLASH_TYPE_REG 0x04 +#define FLASH_TYPE_VAL 0x18 + +#define FLASH_SUBTYPE_REG 0x05 +#define FLASH_SUBTYPE_3CH_PM8150_VAL 0x04 +#define FLASH_SUBTYPE_3CH_PMI8998_VAL 0x03 +#define FLASH_SUBTYPE_4CH_VAL 0x07 + +#define FLASH_STS_3CH_OTST1 BIT(0) +#define FLASH_STS_3CH_OTST2 BIT(1) +#define FLASH_STS_3CH_OTST3 BIT(2) +#define FLASH_STS_3CH_BOB_THM_OVERLOAD BIT(3) +#define FLASH_STS_3CH_VPH_DROOP BIT(4) +#define FLASH_STS_3CH_BOB_ILIM_S1 BIT(5) +#define FLASH_STS_3CH_BOB_ILIM_S2 BIT(6) +#define FLASH_STS_3CH_BCL_IBAT BIT(7) + +#define FLASH_STS_4CH_VPH_LOW BIT(0) +#define FLASH_STS_4CH_BCL_IBAT BIT(1) +#define FLASH_STS_4CH_BOB_ILIM_S1 BIT(2) +#define FLASH_STS_4CH_BOB_ILIM_S2 BIT(3) +#define FLASH_STS_4CH_OTST2 BIT(4) +#define FLASH_STS_4CH_OTST1 BIT(5) +#define FLASH_STS_4CHG_BOB_THM_OVERLOAD BIT(6) + +#define FLASH_TIMER_EN_BIT BIT(7) +#define FLASH_TIMER_VAL_MASK GENMASK(6, 0) +#define FLASH_TIMER_STEP_MS 10 + +#define FLASH_STROBE_HW_SW_SEL_BIT BIT(2) +#define SW_STROBE_VAL 0 +#define HW_STROBE_VAL 1 +#define FLASH_HW_STROBE_TRIGGER_SEL_BIT BIT(1) +#define STROBE_LEVEL_TRIGGER_VAL 0 +#define STROBE_EDGE_TRIGGER_VAL 1 +#define FLASH_STROBE_POLARITY_BIT BIT(0) +#define STROBE_ACTIVE_HIGH_VAL 1 + +#define FLASH_IRES_MASK_4CH BIT(0) +#define FLASH_IRES_MASK_3CH GENMASK(1, 0) +#define FLASH_IRES_12P5MA_VAL 0 +#define FLASH_IRES_5MA_VAL_4CH 1 +#define FLASH_IRES_5MA_VAL_3CH 3 + +/* constants */ +#define FLASH_CURRENT_MAX_UA 1500000 +#define TORCH_CURRENT_MAX_UA 500000 +#define FLASH_TOTAL_CURRENT_MAX_UA 2000000 +#define FLASH_CURRENT_DEFAULT_UA 1000000 +#define TORCH_CURRENT_DEFAULT_UA 200000 + +#define TORCH_IRES_UA 5000 +#define FLASH_IRES_UA 12500 + +#define FLASH_TIMEOUT_MAX_US 1280000 +#define FLASH_TIMEOUT_STEP_US 10000 + +#define UA_PER_MA 1000 + +/* thermal threshold constants */ +#define OTST_3CH_MIN_VAL 3 +#define OTST1_4CH_MIN_VAL 0 +#define OTST1_4CH_V0P1_MIN_VAL 3 +#define OTST2_4CH_MIN_VAL 0 + +#define OTST1_MAX_CURRENT_MA 1000 +#define OTST2_MAX_CURRENT_MA 500 +#define OTST3_MAX_CURRENT_MA 200 + +enum hw_type { + QCOM_MVFLASH_3CH, + QCOM_MVFLASH_4CH, +}; + +enum led_mode { + FLASH_MODE, + TORCH_MODE, +}; + +enum led_strobe { + SW_STROBE, + HW_STROBE, +}; + +enum { + REG_STATUS1, + REG_STATUS2, + REG_STATUS3, + REG_CHAN_TIMER, + REG_ITARGET, + REG_MODULE_EN, + REG_IRESOLUTION, + REG_CHAN_STROBE, + REG_CHAN_EN, + REG_THERM_THRSH1, + REG_THERM_THRSH2, + REG_THERM_THRSH3, + REG_TORCH_CLAMP, + REG_MAX_COUNT, +}; + +static const struct reg_field mvflash_3ch_pmi8998_regs[REG_MAX_COUNT] = { + [REG_STATUS1] = REG_FIELD(0x08, 0, 5), + [REG_STATUS2] = REG_FIELD(0x09, 0, 7), + [REG_STATUS3] = REG_FIELD(0x0a, 0, 7), + [REG_CHAN_TIMER] = REG_FIELD_ID(0x40, 0, 7, 3, 1), + [REG_ITARGET] = REG_FIELD_ID(0x43, 0, 6, 3, 1), + [REG_MODULE_EN] = REG_FIELD(0x46, 7, 7), + [REG_IRESOLUTION] = REG_FIELD(0x47, 0, 5), + [REG_CHAN_STROBE] = REG_FIELD_ID(0x49, 0, 2, 3, 1), + [REG_CHAN_EN] = REG_FIELD(0x4c, 0, 2), + [REG_THERM_THRSH1] = REG_FIELD(0x56, 0, 2), + [REG_THERM_THRSH2] = REG_FIELD(0x57, 0, 2), + [REG_THERM_THRSH3] = REG_FIELD(0x58, 0, 2), + [REG_TORCH_CLAMP] = REG_FIELD(0xea, 0, 6), +}; + +static const struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = { + [REG_STATUS1] = REG_FIELD(0x08, 0, 7), + [REG_STATUS2] = REG_FIELD(0x09, 0, 7), + [REG_STATUS3] = REG_FIELD(0x0a, 0, 7), + [REG_CHAN_TIMER] = REG_FIELD_ID(0x40, 0, 7, 3, 1), + [REG_ITARGET] = REG_FIELD_ID(0x43, 0, 6, 3, 1), + [REG_MODULE_EN] = REG_FIELD(0x46, 7, 7), + [REG_IRESOLUTION] = REG_FIELD(0x47, 0, 5), + [REG_CHAN_STROBE] = REG_FIELD_ID(0x49, 0, 2, 3, 1), + [REG_CHAN_EN] = REG_FIELD(0x4c, 0, 2), + [REG_THERM_THRSH1] = REG_FIELD(0x56, 0, 2), + [REG_THERM_THRSH2] = REG_FIELD(0x57, 0, 2), + [REG_THERM_THRSH3] = REG_FIELD(0x58, 0, 2), + [REG_TORCH_CLAMP] = REG_FIELD(0xec, 0, 6), +}; + +static const struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = { + [REG_STATUS1] = REG_FIELD(0x06, 0, 7), + [REG_STATUS2] = REG_FIELD(0x07, 0, 6), + [REG_STATUS3] = REG_FIELD(0x09, 0, 7), + [REG_CHAN_TIMER] = REG_FIELD_ID(0x3e, 0, 7, 4, 1), + [REG_ITARGET] = REG_FIELD_ID(0x42, 0, 6, 4, 1), + [REG_MODULE_EN] = REG_FIELD(0x46, 7, 7), + [REG_IRESOLUTION] = REG_FIELD(0x49, 0, 3), + [REG_CHAN_STROBE] = REG_FIELD_ID(0x4a, 0, 6, 4, 1), + [REG_CHAN_EN] = REG_FIELD(0x4e, 0, 3), + [REG_THERM_THRSH1] = REG_FIELD(0x7a, 0, 2), + [REG_THERM_THRSH2] = REG_FIELD(0x78, 0, 2), + [REG_TORCH_CLAMP] = REG_FIELD(0xed, 0, 6), +}; + +struct qcom_flash_data { + struct v4l2_flash **v4l2_flash; + struct regmap_field *r_fields[REG_MAX_COUNT]; + struct mutex lock; + enum hw_type hw_type; + u32 total_ma; + u8 leds_count; + u8 max_channels; + u8 chan_en_bits; + u8 revision; + u8 torch_clamp; +}; + +struct qcom_flash_led { + struct qcom_flash_data *flash_data; + struct led_classdev_flash flash; + u32 max_flash_current_ma; + u32 max_torch_current_ma; + u32 max_timeout_ms; + u32 flash_current_ma; + u32 flash_timeout_ms; + u32 current_in_use_ma; + u8 *chan_id; + u8 chan_count; + bool enabled; +}; + +static int set_flash_module_en(struct qcom_flash_led *led, bool en) +{ + struct qcom_flash_data *flash_data = led->flash_data; + u8 led_mask = 0, enable; + int i, rc; + + for (i = 0; i < led->chan_count; i++) + led_mask |= BIT(led->chan_id[i]); + + mutex_lock(&flash_data->lock); + if (en) + flash_data->chan_en_bits |= led_mask; + else + flash_data->chan_en_bits &= ~led_mask; + + enable = !!flash_data->chan_en_bits; + rc = regmap_field_write(flash_data->r_fields[REG_MODULE_EN], enable); + if (rc) + dev_err(led->flash.led_cdev.dev, "write module_en failed, rc=%d\n", rc); + mutex_unlock(&flash_data->lock); + + return rc; +} + +static int update_allowed_flash_current(struct qcom_flash_led *led, u32 *current_ma, bool strobe) +{ + struct qcom_flash_data *flash_data = led->flash_data; + u32 therm_ma, avail_ma, thrsh[3], min_thrsh, sts; + int rc = 0; + + mutex_lock(&flash_data->lock); + /* + * Put previously allocated current into allowed budget in either of these two cases: + * 1) LED is disabled; + * 2) LED is enabled repeatedly + */ + if (!strobe || led->current_in_use_ma != 0) { + if (flash_data->total_ma >= led->current_in_use_ma) + flash_data->total_ma -= led->current_in_use_ma; + else + flash_data->total_ma = 0; + + led->current_in_use_ma = 0; + if (!strobe) + goto unlock; + } + + /* + * Cache the default thermal threshold settings, and set them to the lowest levels before + * reading over-temp real time status. If over-temp has been triggered at the lowest + * threshold, it's very likely that it would be triggered at a higher (default) threshold + * when more flash current is requested. Prevent device from triggering over-temp condition + * by limiting the flash current for the new request. + */ + rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH1], &thrsh[0]); + if (rc < 0) + goto unlock; + + rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH2], &thrsh[1]); + if (rc < 0) + goto unlock; + + if (flash_data->hw_type == QCOM_MVFLASH_3CH) { + rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH3], &thrsh[2]); + if (rc < 0) + goto unlock; + } + + min_thrsh = OTST_3CH_MIN_VAL; + if (flash_data->hw_type == QCOM_MVFLASH_4CH) + min_thrsh = (flash_data->revision == FLASH_4CH_REVISION_V0P1) ? + OTST1_4CH_V0P1_MIN_VAL : OTST1_4CH_MIN_VAL; + + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH1], min_thrsh); + if (rc < 0) + goto unlock; + + if (flash_data->hw_type == QCOM_MVFLASH_4CH) + min_thrsh = OTST2_4CH_MIN_VAL; + + /* + * The default thermal threshold settings have been updated hence + * restore them if any fault happens starting from here. + */ + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH2], min_thrsh); + if (rc < 0) + goto restore; + + if (flash_data->hw_type == QCOM_MVFLASH_3CH) { + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH3], min_thrsh); + if (rc < 0) + goto restore; + } + + /* Read thermal level status to get corresponding derating flash current */ + rc = regmap_field_read(flash_data->r_fields[REG_STATUS2], &sts); + if (rc) + goto restore; + + therm_ma = FLASH_TOTAL_CURRENT_MAX_UA / 1000; + if (flash_data->hw_type == QCOM_MVFLASH_3CH) { + if (sts & FLASH_STS_3CH_OTST3) + therm_ma = OTST3_MAX_CURRENT_MA; + else if (sts & FLASH_STS_3CH_OTST2) + therm_ma = OTST2_MAX_CURRENT_MA; + else if (sts & FLASH_STS_3CH_OTST1) + therm_ma = OTST1_MAX_CURRENT_MA; + } else { + if (sts & FLASH_STS_4CH_OTST2) + therm_ma = OTST2_MAX_CURRENT_MA; + else if (sts & FLASH_STS_4CH_OTST1) + therm_ma = OTST1_MAX_CURRENT_MA; + } + + /* Calculate the allowed flash current for the request */ + if (therm_ma <= flash_data->total_ma) + avail_ma = 0; + else + avail_ma = therm_ma - flash_data->total_ma; + + *current_ma = min_t(u32, *current_ma, avail_ma); + led->current_in_use_ma = *current_ma; + flash_data->total_ma += led->current_in_use_ma; + + dev_dbg(led->flash.led_cdev.dev, "allowed flash current: %dmA, total current: %dmA\n", + led->current_in_use_ma, flash_data->total_ma); + +restore: + /* Restore to default thermal threshold settings */ + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH1], thrsh[0]); + if (rc < 0) + goto unlock; + + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH2], thrsh[1]); + if (rc < 0) + goto unlock; + + if (flash_data->hw_type == QCOM_MVFLASH_3CH) + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH3], thrsh[2]); + +unlock: + mutex_unlock(&flash_data->lock); + return rc; +} + +static int set_flash_current(struct qcom_flash_led *led, u32 current_ma, enum led_mode mode) +{ + struct qcom_flash_data *flash_data = led->flash_data; + u32 itarg_ua, ires_ua; + u8 shift, ires_mask = 0, ires_val = 0, chan_id; + int i, rc; + + /* + * Split the current across the channels and set the + * IRESOLUTION and ITARGET registers accordingly. + */ + itarg_ua = (current_ma * UA_PER_MA) / led->chan_count + 1; + ires_ua = (mode == FLASH_MODE) ? FLASH_IRES_UA : TORCH_IRES_UA; + + for (i = 0; i < led->chan_count; i++) { + u8 itarget = 0; + + if (itarg_ua > ires_ua) + itarget = itarg_ua / ires_ua - 1; + + chan_id = led->chan_id[i]; + + rc = regmap_fields_write(flash_data->r_fields[REG_ITARGET], chan_id, itarget); + if (rc) + return rc; + + if (flash_data->hw_type == QCOM_MVFLASH_3CH) { + shift = chan_id * 2; + ires_mask |= FLASH_IRES_MASK_3CH << shift; + ires_val |= ((mode == FLASH_MODE) ? + (FLASH_IRES_12P5MA_VAL << shift) : + (FLASH_IRES_5MA_VAL_3CH << shift)); + } else if (flash_data->hw_type == QCOM_MVFLASH_4CH) { + shift = chan_id; + ires_mask |= FLASH_IRES_MASK_4CH << shift; + ires_val |= ((mode == FLASH_MODE) ? + (FLASH_IRES_12P5MA_VAL << shift) : + (FLASH_IRES_5MA_VAL_4CH << shift)); + } else { + dev_err(led->flash.led_cdev.dev, + "HW type %d is not supported\n", flash_data->hw_type); + return -EOPNOTSUPP; + } + } + + return regmap_field_update_bits(flash_data->r_fields[REG_IRESOLUTION], ires_mask, ires_val); +} + +static int set_flash_timeout(struct qcom_flash_led *led, u32 timeout_ms) +{ + struct qcom_flash_data *flash_data = led->flash_data; + u8 timer, chan_id; + int rc, i; + + /* set SAFETY_TIMER for all the channels connected to the same LED */ + timeout_ms = min_t(u32, timeout_ms, led->max_timeout_ms); + + for (i = 0; i < led->chan_count; i++) { + chan_id = led->chan_id[i]; + + timer = timeout_ms / FLASH_TIMER_STEP_MS; + timer = clamp_t(u8, timer, 0, FLASH_TIMER_VAL_MASK); + + if (timeout_ms) + timer |= FLASH_TIMER_EN_BIT; + + rc = regmap_fields_write(flash_data->r_fields[REG_CHAN_TIMER], chan_id, timer); + if (rc) + return rc; + } + + return 0; +} + +static int set_flash_strobe(struct qcom_flash_led *led, enum led_strobe strobe, bool state) +{ + struct qcom_flash_data *flash_data = led->flash_data; + u8 strobe_sel, chan_en, chan_id, chan_mask = 0; + int rc, i; + + /* Set SW strobe config for all channels connected to the LED */ + for (i = 0; i < led->chan_count; i++) { + chan_id = led->chan_id[i]; + + if (strobe == SW_STROBE) + strobe_sel = FIELD_PREP(FLASH_STROBE_HW_SW_SEL_BIT, SW_STROBE_VAL); + else + strobe_sel = FIELD_PREP(FLASH_STROBE_HW_SW_SEL_BIT, HW_STROBE_VAL); + + strobe_sel |= + FIELD_PREP(FLASH_HW_STROBE_TRIGGER_SEL_BIT, STROBE_LEVEL_TRIGGER_VAL) | + FIELD_PREP(FLASH_STROBE_POLARITY_BIT, STROBE_ACTIVE_HIGH_VAL); + + rc = regmap_fields_write( + flash_data->r_fields[REG_CHAN_STROBE], chan_id, strobe_sel); + if (rc) + return rc; + + chan_mask |= BIT(chan_id); + } + + /* Enable/disable flash channels */ + chan_en = state ? chan_mask : 0; + rc = regmap_field_update_bits(flash_data->r_fields[REG_CHAN_EN], chan_mask, chan_en); + if (rc) + return rc; + + led->enabled = state; + return 0; +} + +static inline struct qcom_flash_led *flcdev_to_qcom_fled(struct led_classdev_flash *flcdev) +{ + return container_of(flcdev, struct qcom_flash_led, flash); +} + +static int qcom_flash_brightness_set(struct led_classdev_flash *fled_cdev, u32 brightness) +{ + struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev); + + led->flash_current_ma = min_t(u32, led->max_flash_current_ma, brightness / UA_PER_MA); + return 0; +} + +static int qcom_flash_timeout_set(struct led_classdev_flash *fled_cdev, u32 timeout) +{ + struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev); + + led->flash_timeout_ms = timeout / USEC_PER_MSEC; + return 0; +} + +static int qcom_flash_strobe_set(struct led_classdev_flash *fled_cdev, bool state) +{ + struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev); + int rc; + + rc = set_flash_strobe(led, SW_STROBE, false); + if (rc) + return rc; + + rc = update_allowed_flash_current(led, &led->flash_current_ma, state); + if (rc < 0) + return rc; + + rc = set_flash_current(led, led->flash_current_ma, FLASH_MODE); + if (rc) + return rc; + + rc = set_flash_timeout(led, led->flash_timeout_ms); + if (rc) + return rc; + + rc = set_flash_module_en(led, state); + if (rc) + return rc; + + return set_flash_strobe(led, SW_STROBE, state); +} + +static int qcom_flash_strobe_get(struct led_classdev_flash *fled_cdev, bool *state) +{ + struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev); + + *state = led->enabled; + return 0; +} + +static int qcom_flash_fault_get(struct led_classdev_flash *fled_cdev, u32 *fault) +{ + struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev); + struct qcom_flash_data *flash_data = led->flash_data; + u8 shift, chan_id, chan_mask = 0; + u8 ot_mask = 0, oc_mask = 0, uv_mask = 0; + u32 val, fault_sts = 0; + int i, rc; + + rc = regmap_field_read(flash_data->r_fields[REG_STATUS1], &val); + if (rc) + return rc; + + for (i = 0; i < led->chan_count; i++) { + chan_id = led->chan_id[i]; + shift = chan_id * 2; + + if (val & BIT(shift)) + fault_sts |= LED_FAULT_SHORT_CIRCUIT; + + chan_mask |= BIT(chan_id); + } + + rc = regmap_field_read(flash_data->r_fields[REG_STATUS2], &val); + if (rc) + return rc; + + if (flash_data->hw_type == QCOM_MVFLASH_3CH) { + ot_mask = FLASH_STS_3CH_OTST1 | + FLASH_STS_3CH_OTST2 | + FLASH_STS_3CH_OTST3 | + FLASH_STS_3CH_BOB_THM_OVERLOAD; + oc_mask = FLASH_STS_3CH_BOB_ILIM_S1 | + FLASH_STS_3CH_BOB_ILIM_S2 | + FLASH_STS_3CH_BCL_IBAT; + uv_mask = FLASH_STS_3CH_VPH_DROOP; + } else if (flash_data->hw_type == QCOM_MVFLASH_4CH) { + ot_mask = FLASH_STS_4CH_OTST2 | + FLASH_STS_4CH_OTST1 | + FLASH_STS_4CHG_BOB_THM_OVERLOAD; + oc_mask = FLASH_STS_4CH_BCL_IBAT | + FLASH_STS_4CH_BOB_ILIM_S1 | + FLASH_STS_4CH_BOB_ILIM_S2; + uv_mask = FLASH_STS_4CH_VPH_LOW; + } + + if (val & ot_mask) + fault_sts |= LED_FAULT_OVER_TEMPERATURE; + + if (val & oc_mask) + fault_sts |= LED_FAULT_OVER_CURRENT; + + if (val & uv_mask) + fault_sts |= LED_FAULT_INPUT_VOLTAGE; + + rc = regmap_field_read(flash_data->r_fields[REG_STATUS3], &val); + if (rc) + return rc; + + if (flash_data->hw_type == QCOM_MVFLASH_3CH) { + if (val & chan_mask) + fault_sts |= LED_FAULT_TIMEOUT; + } else if (flash_data->hw_type == QCOM_MVFLASH_4CH) { + for (i = 0; i < led->chan_count; i++) { + chan_id = led->chan_id[i]; + shift = chan_id * 2; + + if (val & BIT(shift)) + fault_sts |= LED_FAULT_TIMEOUT; + } + } + + *fault = fault_sts; + return 0; +} + +static int qcom_flash_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); + struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev); + u32 current_ma = brightness * led->max_torch_current_ma / LED_FULL; + bool enable = !!brightness; + int rc; + + rc = set_flash_strobe(led, SW_STROBE, false); + if (rc) + return rc; + + rc = set_flash_module_en(led, false); + if (rc) + return rc; + + rc = update_allowed_flash_current(led, ¤t_ma, enable); + if (rc < 0) + return rc; + + rc = set_flash_current(led, current_ma, TORCH_MODE); + if (rc) + return rc; + + /* Disable flash timeout for torch LED */ + rc = set_flash_timeout(led, 0); + if (rc) + return rc; + + rc = set_flash_module_en(led, enable); + if (rc) + return rc; + + return set_flash_strobe(led, SW_STROBE, enable); +} + +static const struct led_flash_ops qcom_flash_ops = { + .flash_brightness_set = qcom_flash_brightness_set, + .strobe_set = qcom_flash_strobe_set, + .strobe_get = qcom_flash_strobe_get, + .timeout_set = qcom_flash_timeout_set, + .fault_get = qcom_flash_fault_get, +}; + +#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS) +static int qcom_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable) +{ + struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev; + struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev); + int rc; + + rc = set_flash_module_en(led, enable); + if (rc) + return rc; + + if (enable) + return set_flash_strobe(led, HW_STROBE, true); + else + return set_flash_strobe(led, SW_STROBE, false); +} + +static enum led_brightness +qcom_flash_intensity_to_led_brightness(struct v4l2_flash *v4l2_flash, s32 intensity) +{ + struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev; + struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev); + u32 current_ma = intensity / UA_PER_MA; + + current_ma = min_t(u32, current_ma, led->max_torch_current_ma); + if (!current_ma) + return LED_OFF; + + return (current_ma * LED_FULL) / led->max_torch_current_ma; +} + +static s32 qcom_flash_brightness_to_led_intensity(struct v4l2_flash *v4l2_flash, + enum led_brightness brightness) +{ + struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev; + struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev); + + return (brightness * led->max_torch_current_ma * UA_PER_MA) / LED_FULL; +} + +static const struct v4l2_flash_ops qcom_v4l2_flash_ops = { + .external_strobe_set = qcom_flash_external_strobe_set, + .intensity_to_led_brightness = qcom_flash_intensity_to_led_brightness, + .led_brightness_to_intensity = qcom_flash_brightness_to_led_intensity, +}; + +static int +qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwnode_handle *fwnode) +{ + struct qcom_flash_data *flash_data = led->flash_data; + struct v4l2_flash_config v4l2_cfg = { 0 }; + struct led_flash_setting *intensity = &v4l2_cfg.intensity; + struct v4l2_flash *v4l2_flash; + + if (!(led->flash.led_cdev.flags & LED_DEV_CAP_FLASH)) + return 0; + + intensity->min = intensity->step = TORCH_IRES_UA * led->chan_count; + intensity->max = led->max_torch_current_ma * UA_PER_MA; + intensity->val = min_t(u32, intensity->max, TORCH_CURRENT_DEFAULT_UA); + + strscpy(v4l2_cfg.dev_name, led->flash.led_cdev.dev->kobj.name, + sizeof(v4l2_cfg.dev_name)); + + v4l2_cfg.has_external_strobe = true; + v4l2_cfg.flash_faults = LED_FAULT_INPUT_VOLTAGE | + LED_FAULT_OVER_CURRENT | + LED_FAULT_SHORT_CIRCUIT | + LED_FAULT_OVER_TEMPERATURE | + LED_FAULT_TIMEOUT; + + v4l2_flash = v4l2_flash_init(dev, fwnode, &led->flash, &qcom_v4l2_flash_ops, &v4l2_cfg); + if (IS_ERR(v4l2_flash)) + return PTR_ERR(v4l2_flash); + + flash_data->v4l2_flash[flash_data->leds_count] = v4l2_flash; + return 0; +} +# else +static int +qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwnode_handle *fwnode) +{ + return 0; +} +#endif + +static int qcom_flash_register_led_device(struct device *dev, + struct fwnode_handle *node, struct qcom_flash_led *led) +{ + struct qcom_flash_data *flash_data = led->flash_data; + struct led_init_data init_data; + struct led_classdev_flash *flash = &led->flash; + struct led_flash_setting *brightness, *timeout; + u32 current_ua, timeout_us; + u32 channels[4]; + int i, rc, count; + u8 torch_clamp; + + count = fwnode_property_count_u32(node, "led-sources"); + if (count <= 0) { + dev_err(dev, "No led-sources specified\n"); + return -ENODEV; + } + + if (count > flash_data->max_channels) { + dev_err(dev, "led-sources count %u exceeds maximum channel count %u\n", + count, flash_data->max_channels); + return -EINVAL; + } + + rc = fwnode_property_read_u32_array(node, "led-sources", channels, count); + if (rc < 0) { + dev_err(dev, "Failed to read led-sources property, rc=%d\n", rc); + return rc; + } + + led->chan_count = count; + led->chan_id = devm_kcalloc(dev, count, sizeof(u8), GFP_KERNEL); + if (!led->chan_id) + return -ENOMEM; + + for (i = 0; i < count; i++) { + if ((channels[i] == 0) || (channels[i] > flash_data->max_channels)) { + dev_err(dev, "led-source out of HW support range [1-%u]\n", + flash_data->max_channels); + return -EINVAL; + } + + /* Make chan_id indexing from 0 */ + led->chan_id[i] = channels[i] - 1; + } + + rc = fwnode_property_read_u32(node, "led-max-microamp", ¤t_ua); + if (rc < 0) { + dev_err(dev, "Failed to read led-max-microamp property, rc=%d\n", rc); + return rc; + } + + if (current_ua == 0) { + dev_err(dev, "led-max-microamp shouldn't be 0\n"); + return -EINVAL; + } + + current_ua = min_t(u32, current_ua, TORCH_CURRENT_MAX_UA * led->chan_count); + led->max_torch_current_ma = current_ua / UA_PER_MA; + + torch_clamp = (current_ua / led->chan_count) / TORCH_IRES_UA; + if (torch_clamp != 0) + torch_clamp--; + + flash_data->torch_clamp = max_t(u8, flash_data->torch_clamp, torch_clamp); + + if (fwnode_property_present(node, "flash-max-microamp")) { + flash->led_cdev.flags |= LED_DEV_CAP_FLASH; + + rc = fwnode_property_read_u32(node, "flash-max-microamp", ¤t_ua); + if (rc < 0) { + dev_err(dev, "Failed to read flash-max-microamp property, rc=%d\n", + rc); + return rc; + } + + current_ua = min_t(u32, current_ua, FLASH_CURRENT_MAX_UA * led->chan_count); + current_ua = min_t(u32, current_ua, FLASH_TOTAL_CURRENT_MAX_UA); + + /* Initialize flash class LED device brightness settings */ + brightness = &flash->brightness; + brightness->min = brightness->step = FLASH_IRES_UA * led->chan_count; + brightness->max = current_ua; + brightness->val = min_t(u32, current_ua, FLASH_CURRENT_DEFAULT_UA); + + led->max_flash_current_ma = current_ua / UA_PER_MA; + led->flash_current_ma = brightness->val / UA_PER_MA; + + rc = fwnode_property_read_u32(node, "flash-max-timeout-us", &timeout_us); + if (rc < 0) { + dev_err(dev, "Failed to read flash-max-timeout-us property, rc=%d\n", + rc); + return rc; + } + + timeout_us = min_t(u32, timeout_us, FLASH_TIMEOUT_MAX_US); + + /* Initialize flash class LED device timeout settings */ + timeout = &flash->timeout; + timeout->min = timeout->step = FLASH_TIMEOUT_STEP_US; + timeout->val = timeout->max = timeout_us; + + led->max_timeout_ms = led->flash_timeout_ms = timeout_us / USEC_PER_MSEC; + + flash->ops = &qcom_flash_ops; + } + + flash->led_cdev.brightness_set_blocking = qcom_flash_led_brightness_set; + + init_data.fwnode = node; + init_data.devicename = NULL; + init_data.default_label = NULL; + init_data.devname_mandatory = false; + + rc = devm_led_classdev_flash_register_ext(dev, flash, &init_data); + if (rc < 0) { + dev_err(dev, "Register flash LED classdev failed, rc=%d\n", rc); + return rc; + } + + return qcom_flash_v4l2_init(dev, led, node); +} + +static int qcom_flash_led_probe(struct platform_device *pdev) +{ + struct qcom_flash_data *flash_data; + struct qcom_flash_led *led; + struct device *dev = &pdev->dev; + struct regmap *regmap; + struct reg_field *regs; + int count, i, rc; + u32 val, reg_base; + + flash_data = devm_kzalloc(dev, sizeof(*flash_data), GFP_KERNEL); + if (!flash_data) + return -ENOMEM; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) { + dev_err(dev, "Failed to get parent regmap\n"); + return -EINVAL; + } + + rc = fwnode_property_read_u32(dev->fwnode, "reg", ®_base); + if (rc < 0) { + dev_err(dev, "Failed to get register base address, rc=%d\n", rc); + return rc; + } + + rc = regmap_read(regmap, reg_base + FLASH_TYPE_REG, &val); + if (rc < 0) { + dev_err(dev, "Read flash LED module type failed, rc=%d\n", rc); + return rc; + } + + if (val != FLASH_TYPE_VAL) { + dev_err(dev, "type %#x is not a flash LED module\n", val); + return -ENODEV; + } + + rc = regmap_read(regmap, reg_base + FLASH_SUBTYPE_REG, &val); + if (rc < 0) { + dev_err(dev, "Read flash LED module subtype failed, rc=%d\n", rc); + return rc; + } + + if (val == FLASH_SUBTYPE_3CH_PM8150_VAL) { + flash_data->hw_type = QCOM_MVFLASH_3CH; + flash_data->max_channels = 3; + regs = devm_kmemdup(dev, mvflash_3ch_regs, sizeof(mvflash_3ch_regs), + GFP_KERNEL); + if (!regs) + return -ENOMEM; + } else if (val == FLASH_SUBTYPE_3CH_PMI8998_VAL) { + flash_data->hw_type = QCOM_MVFLASH_3CH; + flash_data->max_channels = 3; + regs = devm_kmemdup(dev, mvflash_3ch_pmi8998_regs, + sizeof(mvflash_3ch_pmi8998_regs), GFP_KERNEL); + if (!regs) + return -ENOMEM; + } else if (val == FLASH_SUBTYPE_4CH_VAL) { + flash_data->hw_type = QCOM_MVFLASH_4CH; + flash_data->max_channels = 4; + regs = devm_kmemdup(dev, mvflash_4ch_regs, sizeof(mvflash_4ch_regs), + GFP_KERNEL); + if (!regs) + return -ENOMEM; + + rc = regmap_read(regmap, reg_base + FLASH_REVISION_REG, &val); + if (rc < 0) { + dev_err(dev, "Failed to read flash LED module revision, rc=%d\n", rc); + return rc; + } + + flash_data->revision = val; + } else { + dev_err(dev, "flash LED subtype %#x is not yet supported\n", val); + return -ENODEV; + } + + for (i = 0; i < REG_MAX_COUNT; i++) + regs[i].reg += reg_base; + + rc = devm_regmap_field_bulk_alloc(dev, regmap, flash_data->r_fields, regs, REG_MAX_COUNT); + if (rc < 0) { + dev_err(dev, "Failed to allocate regmap field, rc=%d\n", rc); + return rc; + } + devm_kfree(dev, regs); /* devm_regmap_field_bulk_alloc() makes copies */ + + platform_set_drvdata(pdev, flash_data); + mutex_init(&flash_data->lock); + + count = device_get_child_node_count(dev); + if (count == 0 || count > flash_data->max_channels) { + dev_err(dev, "No child or child count exceeds %d\n", flash_data->max_channels); + return -EINVAL; + } + + flash_data->v4l2_flash = devm_kcalloc(dev, count, + sizeof(*flash_data->v4l2_flash), GFP_KERNEL); + if (!flash_data->v4l2_flash) + return -ENOMEM; + + device_for_each_child_node_scoped(dev, child) { + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); + if (!led) { + rc = -ENOMEM; + goto release; + } + + led->flash_data = flash_data; + rc = qcom_flash_register_led_device(dev, child, led); + if (rc < 0) + goto release; + + flash_data->leds_count++; + } + + return regmap_field_write(flash_data->r_fields[REG_TORCH_CLAMP], flash_data->torch_clamp); +release: + while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count) + v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]); + return rc; +} + +static void qcom_flash_led_remove(struct platform_device *pdev) +{ + struct qcom_flash_data *flash_data = platform_get_drvdata(pdev); + + while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count) + v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]); + + mutex_destroy(&flash_data->lock); +} + +static const struct of_device_id qcom_flash_led_match_table[] = { + { .compatible = "qcom,spmi-flash-led" }, + { } +}; + +MODULE_DEVICE_TABLE(of, qcom_flash_led_match_table); +static struct platform_driver qcom_flash_led_driver = { + .driver = { + .name = "leds-qcom-flash", + .of_match_table = qcom_flash_led_match_table, + }, + .probe = qcom_flash_led_probe, + .remove = qcom_flash_led_remove, +}; + +module_platform_driver(qcom_flash_led_driver); + +MODULE_DESCRIPTION("QCOM Flash LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/flash/leds-rt4505.c b/drivers/leds/flash/leds-rt4505.c new file mode 100644 index 000000000..18fd5b7e5 --- /dev/null +++ b/drivers/leds/flash/leds-rt4505.c @@ -0,0 +1,430 @@ +// 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 = device_get_next_child_node(&client->dev, 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 = rt4505_probe, + .remove = rt4505_remove, + .shutdown = rt4505_shutdown, +}; +module_i2c_driver(rt4505_driver); + +MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>"); +MODULE_DESCRIPTION("Richtek RT4505 LED driver"); +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..00904cc90 --- /dev/null +++ b/drivers/leds/flash/leds-rt8515.c @@ -0,0 +1,396 @@ +// 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/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 { + timer_delete_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 = timer_container_of(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 = device_get_next_child_node(dev, 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 void rt8515_remove(struct platform_device *pdev) +{ + struct rt8515 *rt = platform_get_drvdata(pdev); + + rt8515_v4l2_flash_release(rt); + timer_delete_sync(&rt->powerdown_timer); + mutex_destroy(&rt->lock); +} + +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-s2m-flash.c b/drivers/leds/flash/leds-s2m-flash.c new file mode 100644 index 000000000..6ee8db094 --- /dev/null +++ b/drivers/leds/flash/leds-s2m-flash.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Flash and Torch LED Driver for Samsung S2M series PMICs. + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * Copyright (c) 2026 Kaustabh Chakraborty <kauschluss@disroot.org> + */ + +#include <linux/container_of.h> +#include <linux/led-class-flash.h> +#include <linux/mfd/samsung/core.h> +#include <linux/mfd/samsung/s2mu005.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <media/v4l2-flash-led-class.h> + +#define MAX_CHANNELS 2 + +struct s2m_led { + struct regmap *regmap; + struct led_classdev_flash fled; + struct v4l2_flash *v4l2_flash; + /* + * The mutex object prevents the concurrent access of flash control + * registers by the LED and V4L2 subsystems. + */ + struct mutex lock; + unsigned int reg_enable; + u8 channel; + u8 flash_brightness; + u8 flash_timeout; +}; + +static struct s2m_led *to_s2m_led(struct led_classdev_flash *fled) +{ + return container_of(fled, struct s2m_led, fled); +} + +static struct led_classdev_flash *to_s2m_fled(struct led_classdev *cdev) +{ + return container_of(cdev, struct led_classdev_flash, led_cdev); +} + +static int s2m_fled_flash_brightness_set(struct led_classdev_flash *fled, u32 brightness) +{ + struct s2m_led *led = to_s2m_led(fled); + struct led_flash_setting *setting = &fled->brightness; + + mutex_lock(&led->lock); + led->flash_brightness = (brightness - setting->min) / setting->step; + mutex_unlock(&led->lock); + + return 0; +} + +static int s2m_fled_flash_timeout_set(struct led_classdev_flash *fled, u32 timeout) +{ + struct s2m_led *led = to_s2m_led(fled); + struct led_flash_setting *setting = &fled->timeout; + + mutex_lock(&led->lock); + led->flash_timeout = (timeout - setting->min) / setting->step; + mutex_unlock(&led->lock); + + return 0; +} + +#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS) +static int s2m_fled_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable) +{ + struct s2m_led *led = to_s2m_led(v4l2_flash->fled_cdev); + + return led->fled.ops->strobe_set(&led->fled, enable); +} + +static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops = { + .external_strobe_set = s2m_fled_flash_external_strobe_set, +}; +#else +static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops; +#endif + +static void s2m_fled_v4l2_flash_release(void *v4l2_flash) +{ + v4l2_flash_release(v4l2_flash); +} + +static int s2mu005_fled_torch_brightness_set(struct led_classdev *cdev, enum led_brightness value) +{ + struct s2m_led *led = to_s2m_led(to_s2m_fled(cdev)); + int ret; + + mutex_lock(&led->lock); + + if (!value) { + ret = regmap_clear_bits(led->regmap, led->reg_enable, + S2MU005_FLED_TORCH_EN(led->channel)); + if (ret) + dev_err(cdev->dev, "failed to disable torch LED\n"); + goto unlock; + } + + ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL1(led->channel), + S2MU005_FLED_TORCH_IOUT, + FIELD_PREP(S2MU005_FLED_TORCH_IOUT, value - 1)); + if (ret) { + dev_err(cdev->dev, "failed to set torch current\n"); + goto unlock; + } + + ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_TORCH_EN(led->channel)); + if (ret) { + dev_err(cdev->dev, "failed to enable torch LED\n"); + goto unlock; + } + +unlock: + mutex_unlock(&led->lock); + + return ret; +} + +static int s2mu005_fled_flash_strobe_set(struct led_classdev_flash *fled, bool state) +{ + struct s2m_led *led = to_s2m_led(fled); + int ret; + + mutex_lock(&led->lock); + + ret = regmap_clear_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel)); + if (ret) { + dev_err(fled->led_cdev.dev, "failed to disable flash LED\n"); + goto unlock; + } + + if (!state) + goto unlock; + + ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL0(led->channel), + S2MU005_FLED_FLASH_IOUT, + FIELD_PREP(S2MU005_FLED_FLASH_IOUT, led->flash_brightness)); + if (ret) { + dev_err(fled->led_cdev.dev, "failed to set flash brightness\n"); + goto unlock; + } + + ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL3(led->channel), + S2MU005_FLED_FLASH_TIMEOUT, + FIELD_PREP(S2MU005_FLED_FLASH_TIMEOUT, led->flash_timeout)); + if (ret) { + dev_err(fled->led_cdev.dev, "failed to set flash timeout\n"); + goto unlock; + } + + ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel)); + if (ret) { + dev_err(fled->led_cdev.dev, "failed to enable flash LED\n"); + goto unlock; + } + +unlock: + mutex_unlock(&led->lock); + + return ret; +} + +static int s2mu005_fled_flash_strobe_get(struct led_classdev_flash *fled, bool *state) +{ + struct s2m_led *led = to_s2m_led(fled); + u32 val; + int ret; + + mutex_lock(&led->lock); + + ret = regmap_read(led->regmap, S2MU005_REG_FLED_STATUS, &val); + if (ret) { + dev_err(fled->led_cdev.dev, "failed to fetch LED status\n"); + goto unlock; + } + + *state = !!(val & S2MU005_FLED_FLASH_STATUS(led->channel)); + +unlock: + mutex_unlock(&led->lock); + + return ret; +} + +static const struct led_flash_ops s2mu005_fled_flash_ops = { + .flash_brightness_set = s2m_fled_flash_brightness_set, + .timeout_set = s2m_fled_flash_timeout_set, + .strobe_set = s2mu005_fled_flash_strobe_set, + .strobe_get = s2mu005_fled_flash_strobe_get, +}; + +static int s2mu005_fled_init(struct s2m_led *led, struct device *dev, struct regmap *regmap, + unsigned int nr_channels) +{ + unsigned int val; + int ret; + + ret = regmap_read(regmap, S2MU005_REG_ID, &val); + if (ret) + return dev_err_probe(dev, ret, "failed to read revision\n"); + + for (int i = 0; i < nr_channels; i++) { + /* + * Read the revision register. Revision EVT0 has the register + * at CTRL4, while EVT1 and higher have it at CTRL6. + */ + if (FIELD_GET(S2MU005_ID_MASK, val) == 0) + led[i].reg_enable = S2MU005_REG_FLED_CTRL4; + else + led[i].reg_enable = S2MU005_REG_FLED_CTRL6; + } + + /* Enable the LED channels. */ + ret = regmap_set_bits(regmap, S2MU005_REG_FLED_CTRL1, S2MU005_FLED_CH_EN); + if (ret) + return dev_err_probe(dev, ret, "failed to enable LED channels\n"); + + return 0; +} + +static int s2mu005_fled_init_channel(struct s2m_led *led, struct device *dev, + struct fwnode_handle *fwnp) +{ + struct led_classdev *cdev = &led->fled.led_cdev; + struct led_init_data init_data = {}; + struct v4l2_flash_config v4l2_cfg = {}; + int ret; + + cdev->max_brightness = 16; + cdev->brightness_set_blocking = s2mu005_fled_torch_brightness_set; + cdev->flags |= LED_DEV_CAP_FLASH; + + led->fled.timeout.min = 62000; + led->fled.timeout.step = 62000; + led->fled.timeout.max = 992000; + led->fled.timeout.val = 992000; + + led->fled.brightness.min = 25000; + led->fled.brightness.step = 25000; + led->fled.brightness.max = 375000; /* 400000 causes flickering */ + led->fled.brightness.val = 375000; + + s2m_fled_flash_timeout_set(&led->fled, led->fled.timeout.val); + s2m_fled_flash_brightness_set(&led->fled, led->fled.brightness.val); + + led->fled.ops = &s2mu005_fled_flash_ops; + + init_data.fwnode = fwnp; + ret = devm_led_classdev_flash_register_ext(dev, &led->fled, &init_data); + if (ret) + return dev_err_probe(dev, ret, "failed to create LED flash device\n"); + + v4l2_cfg.intensity.min = led->fled.brightness.min; + v4l2_cfg.intensity.step = led->fled.brightness.step; + v4l2_cfg.intensity.max = led->fled.brightness.max; + v4l2_cfg.intensity.val = led->fled.brightness.val; + + v4l2_cfg.has_external_strobe = true; + + led->v4l2_flash = v4l2_flash_init(dev, fwnp, &led->fled, &s2m_fled_v4l2_flash_ops, + &v4l2_cfg); + if (IS_ERR(led->v4l2_flash)) + return dev_err_probe(dev, PTR_ERR(led->v4l2_flash), + "failed to create V4L2 flash device\n"); + + ret = devm_add_action_or_reset(dev, s2m_fled_v4l2_flash_release, led->v4l2_flash); + if (ret) + return dev_err_probe(dev, ret, "failed to add cleanup action\n"); + + return 0; +} + +static int s2m_fled_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sec_pmic_dev *ddata = dev_get_drvdata(dev->parent); + struct s2m_led *led; + int ret; + + led = devm_kzalloc(dev, sizeof(*led) * MAX_CHANNELS, GFP_KERNEL); + if (!led) + return -ENOMEM; + + /* Initialize LED controller with variant-specific implementation. */ + ret = s2mu005_fled_init(led, dev, ddata->regmap_pmic, MAX_CHANNELS); + if (ret) + return ret; + + device_for_each_child_node_scoped(dev, child) { + u32 reg; + + if (fwnode_property_read_u32(child, "reg", ®)) + continue; + + if (reg >= MAX_CHANNELS) { + dev_warn(dev, "channel %d is non-existent\n", reg); + continue; + } + + if (led[reg].regmap) { + dev_warn(dev, "duplicate node for channel %d\n", reg); + continue; + } + + led[reg].regmap = ddata->regmap_pmic; + led[reg].channel = (u8)reg; + + ret = devm_mutex_init(dev, &led[reg].lock); + if (ret) + return dev_err_probe(dev, ret, "failed to create mutex\n"); + + /* Initialize LED channel with variant-specific implementation. */ + ret = s2mu005_fled_init_channel(led + reg, dev, child); + if (ret) + return ret; + } + + return 0; +} + +static const struct platform_device_id s2m_fled_id_table[] = { + { "s2mu005-flash", S2MU005 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, s2m_fled_id_table); + +static const struct of_device_id s2m_fled_of_match_table[] = { + { .compatible = "samsung,s2mu005-flash", .data = (void *)S2MU005 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, s2m_fled_of_match_table); + +static struct platform_driver s2m_fled_driver = { + .driver = { + .name = "s2m-flash", + }, + .probe = s2m_fled_probe, + .id_table = s2m_fled_id_table, +}; +module_platform_driver(s2m_fled_driver); + +MODULE_DESCRIPTION("Flash/Torch LED Driver for Samsung S2M Series PMICs"); +MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>"); +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..dc6840357 --- /dev/null +++ b/drivers/leds/flash/leds-sgm3140.c @@ -0,0 +1,313 @@ +// 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 { + timer_delete_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->flash_gpio, 0); + gpiod_set_value_cansleep(priv->enable_gpio, 1); + } else { + timer_delete_sync(&priv->powerdown_timer); + gpiod_set_value_cansleep(priv->flash_gpio, 0); + 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 = timer_container_of(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 = device_get_next_child_node(&pdev->dev, 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 void sgm3140_remove(struct platform_device *pdev) +{ + struct sgm3140 *priv = platform_get_drvdata(pdev); + + timer_delete_sync(&priv->powerdown_timer); + + v4l2_flash_release(priv->v4l2_flash); +} + +static const struct of_device_id sgm3140_dt_match[] = { + { .compatible = "ocs,ocp8110" }, + { .compatible = "richtek,rt5033-led" }, + { .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"); diff --git a/drivers/leds/flash/leds-sy7802.c b/drivers/leds/flash/leds-sy7802.c new file mode 100644 index 000000000..ddac83676 --- /dev/null +++ b/drivers/leds/flash/leds-sy7802.c @@ -0,0 +1,539 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Silergy SY7802 flash LED driver with an I2C interface + * + * Copyright 2024 AndrĂ© Apitzsch <git@apitzsch.eu> + */ + +#include <linux/gpio/consumer.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/regmap.h> +#include <linux/regulator/consumer.h> + +#define SY7802_MAX_LEDS 2 +#define SY7802_LED_JOINT 2 + +#define SY7802_REG_ENABLE 0x10 +#define SY7802_REG_TORCH_BRIGHTNESS 0xa0 +#define SY7802_REG_FLASH_BRIGHTNESS 0xb0 +#define SY7802_REG_FLASH_DURATION 0xc0 +#define SY7802_REG_FLAGS 0xd0 +#define SY7802_REG_CONFIG_1 0xe0 +#define SY7802_REG_CONFIG_2 0xf0 +#define SY7802_REG_VIN_MONITOR 0x80 +#define SY7802_REG_LAST_FLASH 0x81 +#define SY7802_REG_VLED_MONITOR 0x30 +#define SY7802_REG_ADC_DELAY 0x31 +#define SY7802_REG_DEV_ID 0xff + +#define SY7802_MODE_OFF 0 +#define SY7802_MODE_TORCH 2 +#define SY7802_MODE_FLASH 3 +#define SY7802_MODE_MASK GENMASK(1, 0) + +#define SY7802_LEDS_SHIFT 3 +#define SY7802_LEDS_MASK(_id) (BIT(_id) << SY7802_LEDS_SHIFT) +#define SY7802_LEDS_MASK_ALL (SY7802_LEDS_MASK(0) | SY7802_LEDS_MASK(1)) + +#define SY7802_TORCH_CURRENT_SHIFT 3 +#define SY7802_TORCH_CURRENT_MASK(_id) \ + (GENMASK(2, 0) << (SY7802_TORCH_CURRENT_SHIFT * (_id))) +#define SY7802_TORCH_CURRENT_MASK_ALL \ + (SY7802_TORCH_CURRENT_MASK(0) | SY7802_TORCH_CURRENT_MASK(1)) + +#define SY7802_FLASH_CURRENT_SHIFT 4 +#define SY7802_FLASH_CURRENT_MASK(_id) \ + (GENMASK(3, 0) << (SY7802_FLASH_CURRENT_SHIFT * (_id))) +#define SY7802_FLASH_CURRENT_MASK_ALL \ + (SY7802_FLASH_CURRENT_MASK(0) | SY7802_FLASH_CURRENT_MASK(1)) + +#define SY7802_TIMEOUT_DEFAULT_US 512000U +#define SY7802_TIMEOUT_MIN_US 32000U +#define SY7802_TIMEOUT_MAX_US 1024000U +#define SY7802_TIMEOUT_STEPSIZE_US 32000U + +#define SY7802_TORCH_BRIGHTNESS_MAX 8 + +#define SY7802_FLASH_BRIGHTNESS_DEFAULT 14 +#define SY7802_FLASH_BRIGHTNESS_MIN 0 +#define SY7802_FLASH_BRIGHTNESS_MAX 15 +#define SY7802_FLASH_BRIGHTNESS_STEP 1 + +#define SY7802_FLAG_TIMEOUT BIT(0) +#define SY7802_FLAG_THERMAL_SHUTDOWN BIT(1) +#define SY7802_FLAG_LED_FAULT BIT(2) +#define SY7802_FLAG_TX1_INTERRUPT BIT(3) +#define SY7802_FLAG_TX2_INTERRUPT BIT(4) +#define SY7802_FLAG_LED_THERMAL_FAULT BIT(5) +#define SY7802_FLAG_FLASH_INPUT_VOLTAGE_LOW BIT(6) +#define SY7802_FLAG_INPUT_VOLTAGE_LOW BIT(7) + +#define SY7802_CHIP_ID 0x51 + +static const struct reg_default sy7802_regmap_defs[] = { + { SY7802_REG_ENABLE, SY7802_LEDS_MASK_ALL }, + { SY7802_REG_TORCH_BRIGHTNESS, 0x92 }, + { SY7802_REG_FLASH_BRIGHTNESS, SY7802_FLASH_BRIGHTNESS_DEFAULT | + SY7802_FLASH_BRIGHTNESS_DEFAULT << SY7802_FLASH_CURRENT_SHIFT }, + { SY7802_REG_FLASH_DURATION, 0x6f }, + { SY7802_REG_FLAGS, 0x0 }, + { SY7802_REG_CONFIG_1, 0x68 }, + { SY7802_REG_CONFIG_2, 0xf0 }, +}; + +struct sy7802_led { + struct led_classdev_flash flash; + struct sy7802 *chip; + u8 led_id; +}; + +struct sy7802 { + struct device *dev; + struct regmap *regmap; + struct mutex mutex; + + struct gpio_desc *enable_gpio; + struct regulator *vin_regulator; + + unsigned int fled_strobe_used; + unsigned int fled_torch_used; + unsigned int leds_active; + int num_leds; + struct sy7802_led leds[] __counted_by(num_leds); +}; + +static int sy7802_torch_brightness_set(struct led_classdev *lcdev, enum led_brightness brightness) +{ + struct sy7802_led *led = container_of(lcdev, struct sy7802_led, flash.led_cdev); + struct sy7802 *chip = led->chip; + u32 fled_torch_used_tmp; + u32 led_enable_mask; + u32 enable_mask; + u32 torch_mask; + u32 val; + int ret; + + mutex_lock(&chip->mutex); + + if (chip->fled_strobe_used) { + dev_warn(chip->dev, "Cannot set torch brightness whilst strobe is enabled\n"); + ret = -EBUSY; + goto unlock; + } + + if (brightness) + fled_torch_used_tmp = chip->fled_torch_used | BIT(led->led_id); + else + fled_torch_used_tmp = chip->fled_torch_used & ~BIT(led->led_id); + + led_enable_mask = led->led_id == SY7802_LED_JOINT ? + SY7802_LEDS_MASK_ALL : + SY7802_LEDS_MASK(led->led_id); + + val = brightness ? led_enable_mask : SY7802_MODE_OFF; + if (fled_torch_used_tmp) + val |= SY7802_MODE_TORCH; + + /* Disable torch to apply brightness */ + ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, SY7802_MODE_MASK, + SY7802_MODE_OFF); + if (ret) + goto unlock; + + torch_mask = led->led_id == SY7802_LED_JOINT ? + SY7802_TORCH_CURRENT_MASK_ALL : + SY7802_TORCH_CURRENT_MASK(led->led_id); + + /* Register expects brightness between 0 and MAX_BRIGHTNESS - 1 */ + if (brightness) + brightness -= 1; + + brightness |= (brightness << SY7802_TORCH_CURRENT_SHIFT); + + ret = regmap_update_bits(chip->regmap, SY7802_REG_TORCH_BRIGHTNESS, torch_mask, brightness); + if (ret) + goto unlock; + + enable_mask = SY7802_MODE_MASK | led_enable_mask; + ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, enable_mask, val); + if (ret) + goto unlock; + + chip->fled_torch_used = fled_torch_used_tmp; + +unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static int sy7802_flash_brightness_set(struct led_classdev_flash *fl_cdev, u32 brightness) +{ + struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash); + struct led_flash_setting *s = &fl_cdev->brightness; + u32 val = (brightness - s->min) / s->step; + struct sy7802 *chip = led->chip; + u32 flash_mask; + int ret; + + val |= (val << SY7802_FLASH_CURRENT_SHIFT); + flash_mask = led->led_id == SY7802_LED_JOINT ? + SY7802_FLASH_CURRENT_MASK_ALL : + SY7802_FLASH_CURRENT_MASK(led->led_id); + + mutex_lock(&chip->mutex); + ret = regmap_update_bits(chip->regmap, SY7802_REG_FLASH_BRIGHTNESS, flash_mask, val); + mutex_unlock(&chip->mutex); + + return ret; +} + +static int sy7802_strobe_set(struct led_classdev_flash *fl_cdev, bool state) +{ + struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash); + struct sy7802 *chip = led->chip; + u32 fled_strobe_used_tmp; + u32 led_enable_mask; + u32 enable_mask; + u32 val; + int ret; + + mutex_lock(&chip->mutex); + + if (chip->fled_torch_used) { + dev_warn(chip->dev, "Cannot set strobe brightness whilst torch is enabled\n"); + ret = -EBUSY; + goto unlock; + } + + if (state) + fled_strobe_used_tmp = chip->fled_strobe_used | BIT(led->led_id); + else + fled_strobe_used_tmp = chip->fled_strobe_used & ~BIT(led->led_id); + + led_enable_mask = led->led_id == SY7802_LED_JOINT ? + SY7802_LEDS_MASK_ALL : + SY7802_LEDS_MASK(led->led_id); + + val = state ? led_enable_mask : SY7802_MODE_OFF; + if (fled_strobe_used_tmp) + val |= SY7802_MODE_FLASH; + + enable_mask = SY7802_MODE_MASK | led_enable_mask; + ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, enable_mask, val); + + if (ret) + goto unlock; + + chip->fled_strobe_used = fled_strobe_used_tmp; + +unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static int sy7802_strobe_get(struct led_classdev_flash *fl_cdev, bool *state) +{ + struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash); + struct sy7802 *chip = led->chip; + + mutex_lock(&chip->mutex); + *state = !!(chip->fled_strobe_used & BIT(led->led_id)); + mutex_unlock(&chip->mutex); + + return 0; +} + +static int sy7802_timeout_set(struct led_classdev_flash *fl_cdev, u32 timeout) +{ + struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash); + struct led_flash_setting *s = &fl_cdev->timeout; + u32 val = (timeout - s->min) / s->step; + struct sy7802 *chip = led->chip; + + return regmap_write(chip->regmap, SY7802_REG_FLASH_DURATION, val); +} + +static int sy7802_fault_get(struct led_classdev_flash *fl_cdev, u32 *fault) +{ + struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash); + struct sy7802 *chip = led->chip; + u32 val, led_faults = 0; + int ret; + + /* NOTE: reading register clears fault status */ + ret = regmap_read(chip->regmap, SY7802_REG_FLAGS, &val); + if (ret) + return ret; + + if (val & (SY7802_FLAG_FLASH_INPUT_VOLTAGE_LOW | SY7802_FLAG_INPUT_VOLTAGE_LOW)) + led_faults |= LED_FAULT_INPUT_VOLTAGE; + + if (val & SY7802_FLAG_THERMAL_SHUTDOWN) + led_faults |= LED_FAULT_OVER_TEMPERATURE; + + if (val & SY7802_FLAG_TIMEOUT) + led_faults |= LED_FAULT_TIMEOUT; + + *fault = led_faults; + return 0; +} + +static const struct led_flash_ops sy7802_flash_ops = { + .flash_brightness_set = sy7802_flash_brightness_set, + .strobe_set = sy7802_strobe_set, + .strobe_get = sy7802_strobe_get, + .timeout_set = sy7802_timeout_set, + .fault_get = sy7802_fault_get, +}; + +static void sy7802_init_flash_brightness(struct led_classdev_flash *fl_cdev) +{ + struct led_flash_setting *s; + + /* Init flash brightness setting */ + s = &fl_cdev->brightness; + s->min = SY7802_FLASH_BRIGHTNESS_MIN; + s->max = SY7802_FLASH_BRIGHTNESS_MAX; + s->step = SY7802_FLASH_BRIGHTNESS_STEP; + s->val = SY7802_FLASH_BRIGHTNESS_DEFAULT; +} + +static void sy7802_init_flash_timeout(struct led_classdev_flash *fl_cdev) +{ + struct led_flash_setting *s; + + /* Init flash timeout setting */ + s = &fl_cdev->timeout; + s->min = SY7802_TIMEOUT_MIN_US; + s->max = SY7802_TIMEOUT_MAX_US; + s->step = SY7802_TIMEOUT_STEPSIZE_US; + s->val = SY7802_TIMEOUT_DEFAULT_US; +} + +static int sy7802_led_register(struct device *dev, struct sy7802_led *led, + struct device_node *np) +{ + struct led_init_data init_data = {}; + int ret; + + init_data.fwnode = of_fwnode_handle(np); + + ret = devm_led_classdev_flash_register_ext(dev, &led->flash, &init_data); + if (ret) { + dev_err(dev, "Couldn't register flash %d\n", led->led_id); + return ret; + } + + return 0; +} + +static int sy7802_init_flash_properties(struct device *dev, struct sy7802_led *led, + struct device_node *np) +{ + struct led_classdev_flash *flash = &led->flash; + struct led_classdev *lcdev = &flash->led_cdev; + u32 sources[SY7802_MAX_LEDS]; + int i, num, ret; + + num = of_property_count_u32_elems(np, "led-sources"); + if (num < 1) { + dev_err(dev, "Not specified or wrong number of led-sources\n"); + return -EINVAL; + } + + ret = of_property_read_u32_array(np, "led-sources", sources, num); + if (ret) + return ret; + + for (i = 0; i < num; i++) { + if (sources[i] >= SY7802_MAX_LEDS) + return -EINVAL; + if (led->chip->leds_active & BIT(sources[i])) + return -EINVAL; + led->chip->leds_active |= BIT(sources[i]); + } + + /* If both channels are specified in 'led-sources', joint flash output mode is used */ + led->led_id = num == 2 ? SY7802_LED_JOINT : sources[0]; + + lcdev->max_brightness = SY7802_TORCH_BRIGHTNESS_MAX; + lcdev->brightness_set_blocking = sy7802_torch_brightness_set; + lcdev->flags |= LED_DEV_CAP_FLASH; + + flash->ops = &sy7802_flash_ops; + + sy7802_init_flash_brightness(flash); + sy7802_init_flash_timeout(flash); + + return 0; +} + +static int sy7802_chip_check(struct sy7802 *chip) +{ + struct device *dev = chip->dev; + u32 chipid; + int ret; + + ret = regmap_read(chip->regmap, SY7802_REG_DEV_ID, &chipid); + if (ret) + return dev_err_probe(dev, ret, "Failed to read chip ID\n"); + + if (chipid != SY7802_CHIP_ID) + return dev_err_probe(dev, -ENODEV, "Unsupported chip detected: %x\n", chipid); + + return 0; +} + +static void sy7802_enable(struct sy7802 *chip) +{ + gpiod_set_value_cansleep(chip->enable_gpio, 1); + usleep_range(200, 300); +} + +static void sy7802_disable(struct sy7802 *chip) +{ + gpiod_set_value_cansleep(chip->enable_gpio, 0); +} + +static int sy7802_probe_dt(struct sy7802 *chip) +{ + struct device_node *np = dev_of_node(chip->dev); + int child_num; + int ret; + + regmap_write(chip->regmap, SY7802_REG_ENABLE, SY7802_MODE_OFF); + regmap_write(chip->regmap, SY7802_REG_TORCH_BRIGHTNESS, LED_OFF); + + child_num = 0; + for_each_available_child_of_node_scoped(np, child) { + struct sy7802_led *led = chip->leds + child_num; + + led->chip = chip; + led->led_id = child_num; + + ret = sy7802_init_flash_properties(chip->dev, led, child); + if (ret) + return ret; + + ret = sy7802_led_register(chip->dev, led, child); + if (ret) + return ret; + + child_num++; + } + return 0; +} + +static void sy7802_chip_disable_action(void *data) +{ + struct sy7802 *chip = data; + + sy7802_disable(chip); +} + +static void sy7802_regulator_disable_action(void *data) +{ + struct sy7802 *chip = data; + + regulator_disable(chip->vin_regulator); +} + +static const struct regmap_config sy7802_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_MAPLE, + .reg_defaults = sy7802_regmap_defs, + .num_reg_defaults = ARRAY_SIZE(sy7802_regmap_defs), +}; + +static int sy7802_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct sy7802 *chip; + size_t count; + int ret; + + count = device_get_child_node_count(dev); + if (!count || count > SY7802_MAX_LEDS) + return dev_err_probe(dev, -EINVAL, "Invalid amount of LED nodes %zu\n", count); + + chip = devm_kzalloc(dev, struct_size(chip, leds, count), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->num_leds = count; + + chip->dev = dev; + i2c_set_clientdata(client, chip); + + chip->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + ret = PTR_ERR_OR_ZERO(chip->enable_gpio); + if (ret) + return dev_err_probe(dev, ret, "Failed to request enable gpio\n"); + + chip->vin_regulator = devm_regulator_get(dev, "vin"); + ret = PTR_ERR_OR_ZERO(chip->vin_regulator); + if (ret) + return dev_err_probe(dev, ret, "Failed to request regulator\n"); + + ret = regulator_enable(chip->vin_regulator); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + ret = devm_add_action_or_reset(dev, sy7802_regulator_disable_action, chip); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &chip->mutex); + if (ret) + return ret; + + mutex_lock(&chip->mutex); + + chip->regmap = devm_regmap_init_i2c(client, &sy7802_regmap_config); + if (IS_ERR(chip->regmap)) { + ret = PTR_ERR(chip->regmap); + dev_err_probe(dev, ret, "Failed to allocate register map\n"); + goto error; + } + + ret = sy7802_probe_dt(chip); + if (ret < 0) + goto error; + + sy7802_enable(chip); + + ret = devm_add_action_or_reset(dev, sy7802_chip_disable_action, chip); + if (ret) + goto error; + + ret = sy7802_chip_check(chip); + +error: + mutex_unlock(&chip->mutex); + return ret; +} + +static const struct of_device_id __maybe_unused sy7802_leds_match[] = { + { .compatible = "silergy,sy7802", }, + {} +}; +MODULE_DEVICE_TABLE(of, sy7802_leds_match); + +static struct i2c_driver sy7802_driver = { + .driver = { + .name = "sy7802", + .of_match_table = of_match_ptr(sy7802_leds_match), + }, + .probe = sy7802_probe, +}; +module_i2c_driver(sy7802_driver); + +MODULE_AUTHOR("AndrĂ© Apitzsch <git@apitzsch.eu>"); +MODULE_DESCRIPTION("Silergy SY7802 flash LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/flash/leds-tps6131x.c b/drivers/leds/flash/leds-tps6131x.c new file mode 100644 index 000000000..5c9a5af5a --- /dev/null +++ b/drivers/leds/flash/leds-tps6131x.c @@ -0,0 +1,815 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Texas Instruments TPS61310/TPS61311 flash LED driver with I2C interface + * + * Copyright 2025 Matthias Fend <matthias.fend@emfend.at> + */ + +#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/regmap.h> +#include <media/v4l2-flash-led-class.h> + +#define TPS6131X_REG_0 0x00 +#define TPS6131X_REG_0_RESET BIT(7) +#define TPS6131X_REG_0_DCLC13 GENMASK(5, 3) +#define TPS6131X_REG_0_DCLC13_SHIFT 3 +#define TPS6131X_REG_0_DCLC2 GENMASK(2, 0) +#define TPS6131X_REG_0_DCLC2_SHIFT 0 + +#define TPS6131X_REG_1 0x01 +#define TPS6131X_REG_1_MODE GENMASK(7, 6) +#define TPS6131X_REG_1_MODE_SHIFT 6 +#define TPS6131X_REG_1_FC2 GENMASK(5, 0) +#define TPS6131X_REG_1_FC2_SHIFT 0 + +#define TPS6131X_REG_2 0x02 +#define TPS6131X_REG_2_MODE GENMASK(7, 6) +#define TPS6131X_REG_2_MODE_SHIFT 6 +#define TPS6131X_REG_2_ENVM BIT(5) +#define TPS6131X_REG_2_FC13 GENMASK(4, 0) +#define TPS6131X_REG_2_FC13_SHIFT 0 + +#define TPS6131X_REG_3 0x03 +#define TPS6131X_REG_3_STIM GENMASK(7, 5) +#define TPS6131X_REG_3_STIM_SHIFT 5 +#define TPS6131X_REG_3_HPFL BIT(4) +#define TPS6131X_REG_3_SELSTIM_TO BIT(3) +#define TPS6131X_REG_3_STT BIT(2) +#define TPS6131X_REG_3_SFT BIT(1) +#define TPS6131X_REG_3_TXMASK BIT(0) + +#define TPS6131X_REG_4 0x04 +#define TPS6131X_REG_4_PG BIT(7) +#define TPS6131X_REG_4_HOTDIE_HI BIT(6) +#define TPS6131X_REG_4_HOTDIE_LO BIT(5) +#define TPS6131X_REG_4_ILIM BIT(4) +#define TPS6131X_REG_4_INDC GENMASK(3, 0) +#define TPS6131X_REG_4_INDC_SHIFT 0 + +#define TPS6131X_REG_5 0x05 +#define TPS6131X_REG_5_SELFCAL BIT(7) +#define TPS6131X_REG_5_ENPSM BIT(6) +#define TPS6131X_REG_5_STSTRB1_DIR BIT(5) +#define TPS6131X_REG_5_GPIO BIT(4) +#define TPS6131X_REG_5_GPIOTYPE BIT(3) +#define TPS6131X_REG_5_ENLED3 BIT(2) +#define TPS6131X_REG_5_ENLED2 BIT(1) +#define TPS6131X_REG_5_ENLED1 BIT(0) + +#define TPS6131X_REG_6 0x06 +#define TPS6131X_REG_6_ENTS BIT(7) +#define TPS6131X_REG_6_LEDHOT BIT(6) +#define TPS6131X_REG_6_LEDWARN BIT(5) +#define TPS6131X_REG_6_LEDHDR BIT(4) +#define TPS6131X_REG_6_OV GENMASK(3, 0) +#define TPS6131X_REG_6_OV_SHIFT 0 + +#define TPS6131X_REG_7 0x07 +#define TPS6131X_REG_7_ENBATMON BIT(7) +#define TPS6131X_REG_7_BATDROOP GENMASK(6, 4) +#define TPS6131X_REG_7_BATDROOP_SHIFT 4 +#define TPS6131X_REG_7_REVID GENMASK(2, 0) +#define TPS6131X_REG_7_REVID_SHIFT 0 + +#define TPS6131X_MAX_CHANNELS 3 + +#define TPS6131X_FLASH_MAX_I_CHAN13_MA 400 +#define TPS6131X_FLASH_MAX_I_CHAN2_MA 800 +#define TPS6131X_FLASH_STEP_I_MA 25 + +#define TPS6131X_TORCH_MAX_I_CHAN13_MA 175 +#define TPS6131X_TORCH_MAX_I_CHAN2_MA 175 +#define TPS6131X_TORCH_STEP_I_MA 25 + +/* The torch watchdog timer must be refreshed within an interval of 13 seconds. */ +#define TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES msecs_to_jiffies(10000) + +#define UA_TO_MA(UA) ((UA) / 1000) + +enum tps6131x_mode { + TPS6131X_MODE_SHUTDOWN = 0x0, + TPS6131X_MODE_TORCH = 0x1, + TPS6131X_MODE_FLASH = 0x2, +}; + +struct tps6131x { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + /* + * Registers 0, 1, 2, and 3 control parts of the controller that are not completely + * independent of each other. Since some operations require the registers to be written in + * a specific order to avoid unwanted side effects, they are synchronized with a lock. + */ + struct mutex lock; /* Hardware access lock for register 0, 1, 2 and 3 */ + struct delayed_work torch_refresh_work; + bool valley_current_limit; + bool chan1_en; + bool chan2_en; + bool chan3_en; + struct fwnode_handle *led_node; + u32 max_flash_current_ma; + u32 step_flash_current_ma; + u32 max_torch_current_ma; + u32 step_torch_current_ma; + u32 max_timeout_us; + struct led_classdev_flash fled_cdev; + struct v4l2_flash *v4l2_flash; +}; + +static struct tps6131x *fled_cdev_to_tps6131x(struct led_classdev_flash *fled_cdev) +{ + return container_of(fled_cdev, struct tps6131x, fled_cdev); +} + +/* + * Register contents after a power on/reset. These values cannot be changed. + */ + +#define TPS6131X_DCLC2_50MA 2 +#define TPS6131X_DCLC13_25MA 1 +#define TPS6131X_FC2_400MA 16 +#define TPS6131X_FC13_200MA 8 +#define TPS6131X_STIM_0_579MS_1_37MS 6 +#define TPS6131X_SELSTIM_RANGE0 0 +#define TPS6131X_INDC_OFF 0 +#define TPS6131X_OV_4950MV 9 +#define TPS6131X_BATDROOP_150MV 4 + +static const struct reg_default tps6131x_regmap_defaults[] = { + { TPS6131X_REG_0, (TPS6131X_DCLC13_25MA << TPS6131X_REG_0_DCLC13_SHIFT) | + (TPS6131X_DCLC2_50MA << TPS6131X_REG_0_DCLC2_SHIFT) }, + { TPS6131X_REG_1, (TPS6131X_MODE_SHUTDOWN << TPS6131X_REG_1_MODE_SHIFT) | + (TPS6131X_FC2_400MA << TPS6131X_REG_1_FC2_SHIFT) }, + { TPS6131X_REG_2, (TPS6131X_MODE_SHUTDOWN << TPS6131X_REG_2_MODE_SHIFT) | + (TPS6131X_FC13_200MA << TPS6131X_REG_2_FC13_SHIFT) }, + { TPS6131X_REG_3, (TPS6131X_STIM_0_579MS_1_37MS << TPS6131X_REG_3_STIM_SHIFT) | + (TPS6131X_SELSTIM_RANGE0 << TPS6131X_REG_3_SELSTIM_TO) | + TPS6131X_REG_3_TXMASK }, + { TPS6131X_REG_4, (TPS6131X_INDC_OFF << TPS6131X_REG_4_INDC_SHIFT) }, + { TPS6131X_REG_5, TPS6131X_REG_5_ENPSM | TPS6131X_REG_5_STSTRB1_DIR | + TPS6131X_REG_5_GPIOTYPE | TPS6131X_REG_5_ENLED2 }, + { TPS6131X_REG_6, (TPS6131X_OV_4950MV << TPS6131X_REG_6_OV_SHIFT) }, + { TPS6131X_REG_7, (TPS6131X_BATDROOP_150MV << TPS6131X_REG_7_BATDROOP_SHIFT) }, +}; + +/* + * These registers contain flags that are reset when read. + */ +static bool tps6131x_regmap_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TPS6131X_REG_3: + case TPS6131X_REG_4: + case TPS6131X_REG_6: + return true; + default: + return false; + } +} + +static const struct regmap_config tps6131x_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TPS6131X_REG_7, + .reg_defaults = tps6131x_regmap_defaults, + .num_reg_defaults = ARRAY_SIZE(tps6131x_regmap_defaults), + .cache_type = REGCACHE_FLAT, + .precious_reg = &tps6131x_regmap_precious, +}; + +struct tps6131x_timer_config { + u8 val; + u8 range; + u32 time_us; +}; + +static const struct tps6131x_timer_config tps6131x_timer_configs[] = { + { .val = 0, .range = 1, .time_us = 5300 }, + { .val = 1, .range = 1, .time_us = 10700 }, + { .val = 2, .range = 1, .time_us = 16000 }, + { .val = 3, .range = 1, .time_us = 21300 }, + { .val = 4, .range = 1, .time_us = 26600 }, + { .val = 5, .range = 1, .time_us = 32000 }, + { .val = 6, .range = 1, .time_us = 37300 }, + { .val = 0, .range = 0, .time_us = 68200 }, + { .val = 7, .range = 1, .time_us = 71500 }, + { .val = 1, .range = 0, .time_us = 102200 }, + { .val = 2, .range = 0, .time_us = 136300 }, + { .val = 3, .range = 0, .time_us = 170400 }, + { .val = 4, .range = 0, .time_us = 204500 }, + { .val = 5, .range = 0, .time_us = 340800 }, + { .val = 6, .range = 0, .time_us = 579300 }, + { .val = 7, .range = 0, .time_us = 852000 }, +}; + +static const struct tps6131x_timer_config *tps6131x_find_closest_timer_config(u32 timeout_us) +{ + const struct tps6131x_timer_config *timer_config = &tps6131x_timer_configs[0]; + u32 diff, min_diff = U32_MAX; + int i; + + for (i = 0; i < ARRAY_SIZE(tps6131x_timer_configs); i++) { + diff = abs(tps6131x_timer_configs[i].time_us - timeout_us); + if (diff < min_diff) { + timer_config = &tps6131x_timer_configs[i]; + min_diff = diff; + if (!min_diff) + break; + } + } + + return timer_config; +} + +static int tps6131x_reset_chip(struct tps6131x *tps6131x) +{ + int ret; + + if (tps6131x->reset_gpio) { + gpiod_set_value_cansleep(tps6131x->reset_gpio, 1); + fsleep(10); + gpiod_set_value_cansleep(tps6131x->reset_gpio, 0); + fsleep(100); + } else { + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, TPS6131X_REG_0_RESET, + TPS6131X_REG_0_RESET); + if (ret) + return ret; + + fsleep(100); + + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, TPS6131X_REG_0_RESET, 0); + if (ret) + return ret; + } + + return 0; +} + +static int tps6131x_init_chip(struct tps6131x *tps6131x) +{ + u32 val; + int ret; + + val = tps6131x->valley_current_limit ? TPS6131X_REG_4_ILIM : 0; + + ret = regmap_write(tps6131x->regmap, TPS6131X_REG_4, val); + if (ret) + return ret; + + val = TPS6131X_REG_5_ENPSM | TPS6131X_REG_5_STSTRB1_DIR | TPS6131X_REG_5_GPIOTYPE; + + if (tps6131x->chan1_en) + val |= TPS6131X_REG_5_ENLED1; + + if (tps6131x->chan2_en) + val |= TPS6131X_REG_5_ENLED2; + + if (tps6131x->chan3_en) + val |= TPS6131X_REG_5_ENLED3; + + ret = regmap_write(tps6131x->regmap, TPS6131X_REG_5, val); + if (ret) + return ret; + + val = TPS6131X_REG_6_ENTS | (TPS6131X_OV_4950MV << TPS6131X_REG_6_OV_SHIFT); + + ret = regmap_write(tps6131x->regmap, TPS6131X_REG_6, val); + if (ret) + return ret; + + return 0; +} + +static int tps6131x_set_mode(struct tps6131x *tps6131x, enum tps6131x_mode mode, bool force) +{ + u8 val = mode << TPS6131X_REG_1_MODE_SHIFT; + + return regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_1, TPS6131X_REG_1_MODE, val, + NULL, false, force); +} + +static void tps6131x_torch_refresh_handler(struct work_struct *work) +{ + struct tps6131x *tps6131x = container_of(work, struct tps6131x, torch_refresh_work.work); + int ret; + + guard(mutex)(&tps6131x->lock); + + ret = tps6131x_set_mode(tps6131x, TPS6131X_MODE_TORCH, true); + if (ret < 0) { + dev_err(tps6131x->dev, "Failed to refresh torch watchdog timer\n"); + return; + } + + schedule_delayed_work(&tps6131x->torch_refresh_work, + TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES); +} + +static int tps6131x_brightness_set(struct led_classdev *cdev, enum led_brightness brightness) +{ + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(cdev); + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + u32 num_chans, steps_chan13, steps_chan2, steps_remaining; + u8 reg0; + int ret; + + cancel_delayed_work_sync(&tps6131x->torch_refresh_work); + + /* + * The brightness parameter uses the number of current steps as the unit (not the current + * value itself). Since the reported step size can vary depending on the configuration, + * this value must be converted into actual register steps. + */ + steps_remaining = (brightness * tps6131x->step_torch_current_ma) / TPS6131X_TORCH_STEP_I_MA; + + num_chans = tps6131x->chan1_en + tps6131x->chan2_en + tps6131x->chan3_en; + + /* + * The currents are distributed as evenly as possible across the activated channels. + * Since channels 1 and 3 share the same register setting, they always use the same current + * value. Channel 2 supports higher currents and thus takes over the remaining additional + * portion that cannot be covered by the other channels. + */ + steps_chan13 = min_t(u32, steps_remaining / num_chans, + TPS6131X_TORCH_MAX_I_CHAN13_MA / TPS6131X_TORCH_STEP_I_MA); + if (tps6131x->chan1_en) + steps_remaining -= steps_chan13; + if (tps6131x->chan3_en) + steps_remaining -= steps_chan13; + + steps_chan2 = min_t(u32, steps_remaining, + TPS6131X_TORCH_MAX_I_CHAN2_MA / TPS6131X_TORCH_STEP_I_MA); + + guard(mutex)(&tps6131x->lock); + + reg0 = (steps_chan13 << TPS6131X_REG_0_DCLC13_SHIFT) | + (steps_chan2 << TPS6131X_REG_0_DCLC2_SHIFT); + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, + TPS6131X_REG_0_DCLC13 | TPS6131X_REG_0_DCLC2, reg0); + if (ret < 0) + return ret; + + ret = tps6131x_set_mode(tps6131x, brightness ? TPS6131X_MODE_TORCH : TPS6131X_MODE_SHUTDOWN, + true); + if (ret < 0) + return ret; + + /* + * In order to use both the flash and the video light functions purely via the I2C + * interface, STRB1 must be low. If STRB1 is low, then the video light watchdog timer + * is also active, which puts the device into the shutdown state after around 13 seconds. + * To prevent this, the mode must be refreshed within the watchdog timeout. + */ + if (brightness) + schedule_delayed_work(&tps6131x->torch_refresh_work, + TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES); + + return 0; +} + +static int tps6131x_strobe_set(struct led_classdev_flash *fled_cdev, bool state) +{ + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + int ret; + + guard(mutex)(&tps6131x->lock); + + ret = tps6131x_set_mode(tps6131x, state ? TPS6131X_MODE_FLASH : TPS6131X_MODE_SHUTDOWN, + true); + if (ret < 0) + return ret; + + if (state) { + ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_3, TPS6131X_REG_3_SFT, + TPS6131X_REG_3_SFT, NULL, false, true); + if (ret) + return ret; + } + + ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_3, TPS6131X_REG_3_SFT, 0, NULL, + false, true); + if (ret) + return ret; + + return 0; +} + +static int tps6131x_flash_brightness_set(struct led_classdev_flash *fled_cdev, u32 brightness) +{ + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + u32 num_chans; + u32 steps_chan13, steps_chan2; + u32 steps_remaining; + int ret; + + steps_remaining = brightness / TPS6131X_FLASH_STEP_I_MA; + num_chans = tps6131x->chan1_en + tps6131x->chan2_en + tps6131x->chan3_en; + steps_chan13 = min_t(u32, steps_remaining / num_chans, + TPS6131X_FLASH_MAX_I_CHAN13_MA / TPS6131X_FLASH_STEP_I_MA); + if (tps6131x->chan1_en) + steps_remaining -= steps_chan13; + if (tps6131x->chan3_en) + steps_remaining -= steps_chan13; + steps_chan2 = min_t(u32, steps_remaining, + TPS6131X_FLASH_MAX_I_CHAN2_MA / TPS6131X_FLASH_STEP_I_MA); + + guard(mutex)(&tps6131x->lock); + + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_2, TPS6131X_REG_2_FC13, + steps_chan13 << TPS6131X_REG_2_FC13_SHIFT); + if (ret < 0) + return ret; + + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_1, TPS6131X_REG_1_FC2, + steps_chan2 << TPS6131X_REG_1_FC2_SHIFT); + if (ret < 0) + return ret; + + fled_cdev->brightness.val = brightness; + + return 0; +} + +static int tps6131x_flash_timeout_set(struct led_classdev_flash *fled_cdev, u32 timeout_us) +{ + const struct tps6131x_timer_config *timer_config; + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + u8 reg3; + int ret; + + guard(mutex)(&tps6131x->lock); + + timer_config = tps6131x_find_closest_timer_config(timeout_us); + + reg3 = timer_config->val << TPS6131X_REG_3_STIM_SHIFT; + if (timer_config->range) + reg3 |= TPS6131X_REG_3_SELSTIM_TO; + + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_3, + TPS6131X_REG_3_STIM | TPS6131X_REG_3_SELSTIM_TO, reg3); + if (ret < 0) + return ret; + + fled_cdev->timeout.val = timer_config->time_us; + + return 0; +} + +static int tps6131x_strobe_get(struct led_classdev_flash *fled_cdev, bool *state) +{ + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + unsigned int reg3; + int ret; + + ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_3, ®3); + if (ret) + return ret; + + *state = !!(reg3 & TPS6131X_REG_3_SFT); + + return 0; +} + +static int tps6131x_flash_fault_get(struct led_classdev_flash *fled_cdev, u32 *fault) +{ + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + unsigned int reg3, reg4, reg6; + int ret; + + *fault = 0; + + ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_3, ®3); + if (ret < 0) + return ret; + + ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_4, ®4); + if (ret < 0) + return ret; + + ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_6, ®6); + if (ret < 0) + return ret; + + if (reg3 & TPS6131X_REG_3_HPFL) + *fault |= LED_FAULT_SHORT_CIRCUIT; + + if (reg3 & TPS6131X_REG_3_SELSTIM_TO) + *fault |= LED_FAULT_TIMEOUT; + + if (reg4 & TPS6131X_REG_4_HOTDIE_HI) + *fault |= LED_FAULT_OVER_TEMPERATURE; + + if (reg6 & (TPS6131X_REG_6_LEDHOT | TPS6131X_REG_6_LEDWARN)) + *fault |= LED_FAULT_LED_OVER_TEMPERATURE; + + if (!(reg6 & TPS6131X_REG_6_LEDHDR)) + *fault |= LED_FAULT_UNDER_VOLTAGE; + + if (reg6 & TPS6131X_REG_6_LEDHOT) { + ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_6, + TPS6131X_REG_6_LEDHOT, 0, NULL, false, true); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct led_flash_ops flash_ops = { + .flash_brightness_set = tps6131x_flash_brightness_set, + .strobe_set = tps6131x_strobe_set, + .strobe_get = tps6131x_strobe_get, + .timeout_set = tps6131x_flash_timeout_set, + .fault_get = tps6131x_flash_fault_get, +}; + +static int tps6131x_parse_node(struct tps6131x *tps6131x) +{ + const struct tps6131x_timer_config *timer_config; + struct device *dev = tps6131x->dev; + u32 channels[TPS6131X_MAX_CHANNELS]; + u32 current_step_multiplier; + u32 current_ua; + u32 max_current_flash_ma, max_current_torch_ma; + u32 timeout_us; + int num_channels; + int i; + int ret; + + tps6131x->valley_current_limit = device_property_read_bool(dev, "ti,valley-current-limit"); + + tps6131x->led_node = device_get_next_child_node(dev, NULL); + if (!tps6131x->led_node) { + dev_err(dev, "Missing LED node\n"); + return -EINVAL; + } + + num_channels = fwnode_property_count_u32(tps6131x->led_node, "led-sources"); + if (num_channels <= 0) { + dev_err(dev, "Failed to read led-sources property\n"); + return -EINVAL; + } + + if (num_channels > TPS6131X_MAX_CHANNELS) { + dev_err(dev, "led-sources count %u exceeds maximum channel count %u\n", + num_channels, TPS6131X_MAX_CHANNELS); + return -EINVAL; + } + + ret = fwnode_property_read_u32_array(tps6131x->led_node, "led-sources", channels, + num_channels); + if (ret < 0) { + dev_err(dev, "Failed to read led-sources property\n"); + return ret; + } + + max_current_flash_ma = 0; + max_current_torch_ma = 0; + for (i = 0; i < num_channels; i++) { + switch (channels[i]) { + case 1: + tps6131x->chan1_en = true; + max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN13_MA; + max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN13_MA; + break; + case 2: + tps6131x->chan2_en = true; + max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN2_MA; + max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN2_MA; + break; + case 3: + tps6131x->chan3_en = true; + max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN13_MA; + max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN13_MA; + break; + default: + dev_err(dev, "led-source out of range [1-3]\n"); + return -EINVAL; + } + } + + /* + * If only channels 1 and 3 are used, the step size is doubled because the two channels + * share the same current control register. + */ + current_step_multiplier = + (tps6131x->chan1_en && tps6131x->chan3_en && !tps6131x->chan2_en) ? 2 : 1; + tps6131x->step_flash_current_ma = current_step_multiplier * TPS6131X_FLASH_STEP_I_MA; + tps6131x->step_torch_current_ma = current_step_multiplier * TPS6131X_TORCH_STEP_I_MA; + + ret = fwnode_property_read_u32(tps6131x->led_node, "led-max-microamp", ¤t_ua); + if (ret < 0) { + dev_err(dev, "Failed to read led-max-microamp property\n"); + return ret; + } + + tps6131x->max_torch_current_ma = UA_TO_MA(current_ua); + + if (!tps6131x->max_torch_current_ma || + tps6131x->max_torch_current_ma > max_current_torch_ma || + (tps6131x->max_torch_current_ma % tps6131x->step_torch_current_ma)) { + dev_err(dev, "led-max-microamp out of range or not a multiple of %u\n", + tps6131x->step_torch_current_ma); + return -EINVAL; + } + + ret = fwnode_property_read_u32(tps6131x->led_node, "flash-max-microamp", ¤t_ua); + if (ret < 0) { + dev_err(dev, "Failed to read flash-max-microamp property\n"); + return ret; + } + + tps6131x->max_flash_current_ma = UA_TO_MA(current_ua); + + if (!tps6131x->max_flash_current_ma || + tps6131x->max_flash_current_ma > max_current_flash_ma || + (tps6131x->max_flash_current_ma % tps6131x->step_flash_current_ma)) { + dev_err(dev, "flash-max-microamp out of range or not a multiple of %u\n", + tps6131x->step_flash_current_ma); + return -EINVAL; + } + + ret = fwnode_property_read_u32(tps6131x->led_node, "flash-max-timeout-us", &timeout_us); + if (ret < 0) { + dev_err(dev, "Failed to read flash-max-timeout-us property\n"); + return ret; + } + + timer_config = tps6131x_find_closest_timer_config(timeout_us); + tps6131x->max_timeout_us = timer_config->time_us; + + if (tps6131x->max_timeout_us != timeout_us) + dev_warn(dev, "flash-max-timeout-us %u not supported (using %u)\n", timeout_us, + tps6131x->max_timeout_us); + + return 0; +} + +static int tps6131x_led_class_setup(struct tps6131x *tps6131x) +{ + const struct tps6131x_timer_config *timer_config; + struct led_classdev *led_cdev; + struct led_flash_setting *setting; + struct led_init_data init_data = {}; + int ret; + + tps6131x->fled_cdev.ops = &flash_ops; + + setting = &tps6131x->fled_cdev.timeout; + timer_config = tps6131x_find_closest_timer_config(0); + setting->min = timer_config->time_us; + setting->max = tps6131x->max_timeout_us; + setting->step = 1; /* Only some specific time periods are supported. No fixed step size. */ + setting->val = setting->min; + + setting = &tps6131x->fled_cdev.brightness; + setting->min = tps6131x->step_flash_current_ma; + setting->max = tps6131x->max_flash_current_ma; + setting->step = tps6131x->step_flash_current_ma; + setting->val = setting->min; + + led_cdev = &tps6131x->fled_cdev.led_cdev; + led_cdev->brightness_set_blocking = tps6131x_brightness_set; + led_cdev->max_brightness = tps6131x->max_torch_current_ma; + led_cdev->flags |= LED_DEV_CAP_FLASH; + + init_data.fwnode = tps6131x->led_node; + init_data.devicename = NULL; + init_data.default_label = NULL; + init_data.devname_mandatory = false; + + ret = devm_led_classdev_flash_register_ext(tps6131x->dev, &tps6131x->fled_cdev, + &init_data); + if (ret) + return ret; + + return 0; +} + +static int tps6131x_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable) +{ + struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev; + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + + guard(mutex)(&tps6131x->lock); + + return tps6131x_set_mode(tps6131x, enable ? TPS6131X_MODE_FLASH : TPS6131X_MODE_SHUTDOWN, + false); +} + +static const struct v4l2_flash_ops tps6131x_v4l2_flash_ops = { + .external_strobe_set = tps6131x_flash_external_strobe_set, +}; + +static int tps6131x_v4l2_setup(struct tps6131x *tps6131x) +{ + struct v4l2_flash_config v4l2_cfg = { 0 }; + struct led_flash_setting *intensity = &v4l2_cfg.intensity; + + intensity->min = tps6131x->step_torch_current_ma; + intensity->max = tps6131x->max_torch_current_ma; + intensity->step = tps6131x->step_torch_current_ma; + intensity->val = intensity->min; + + strscpy(v4l2_cfg.dev_name, tps6131x->fled_cdev.led_cdev.dev->kobj.name, + sizeof(v4l2_cfg.dev_name)); + + v4l2_cfg.has_external_strobe = true; + v4l2_cfg.flash_faults = LED_FAULT_TIMEOUT | LED_FAULT_OVER_TEMPERATURE | + LED_FAULT_SHORT_CIRCUIT | LED_FAULT_UNDER_VOLTAGE | + LED_FAULT_LED_OVER_TEMPERATURE; + + tps6131x->v4l2_flash = v4l2_flash_init(tps6131x->dev, tps6131x->led_node, + &tps6131x->fled_cdev, &tps6131x_v4l2_flash_ops, + &v4l2_cfg); + if (IS_ERR(tps6131x->v4l2_flash)) { + dev_err(tps6131x->dev, "Failed to initialize v4l2 flash LED\n"); + return PTR_ERR(tps6131x->v4l2_flash); + } + + return 0; +} + +static int tps6131x_probe(struct i2c_client *client) +{ + struct tps6131x *tps6131x; + int ret; + + tps6131x = devm_kzalloc(&client->dev, sizeof(*tps6131x), GFP_KERNEL); + if (!tps6131x) + return -ENOMEM; + + tps6131x->dev = &client->dev; + i2c_set_clientdata(client, tps6131x); + mutex_init(&tps6131x->lock); + INIT_DELAYED_WORK(&tps6131x->torch_refresh_work, tps6131x_torch_refresh_handler); + + ret = tps6131x_parse_node(tps6131x); + if (ret) + return ret; + + tps6131x->regmap = devm_regmap_init_i2c(client, &tps6131x_regmap); + if (IS_ERR(tps6131x->regmap)) { + ret = PTR_ERR(tps6131x->regmap); + return dev_err_probe(&client->dev, ret, "Failed to allocate register map\n"); + } + + tps6131x->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(tps6131x->reset_gpio)) { + ret = PTR_ERR(tps6131x->reset_gpio); + return dev_err_probe(&client->dev, ret, "Failed to get reset GPIO\n"); + } + + ret = tps6131x_reset_chip(tps6131x); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to reset LED controller\n"); + + ret = tps6131x_init_chip(tps6131x); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to initialize LED controller\n"); + + ret = tps6131x_led_class_setup(tps6131x); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to setup LED class\n"); + + ret = tps6131x_v4l2_setup(tps6131x); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to setup v4l2 flash\n"); + + return 0; +} + +static void tps6131x_remove(struct i2c_client *client) +{ + struct tps6131x *tps6131x = i2c_get_clientdata(client); + + v4l2_flash_release(tps6131x->v4l2_flash); + + cancel_delayed_work_sync(&tps6131x->torch_refresh_work); +} + +static const struct of_device_id of_tps6131x_leds_match[] = { + { .compatible = "ti,tps61310" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_tps6131x_leds_match); + +static struct i2c_driver tps6131x_i2c_driver = { + .driver = { + .name = "tps6131x", + .of_match_table = of_tps6131x_leds_match, + }, + .probe = tps6131x_probe, + .remove = tps6131x_remove, +}; +module_i2c_driver(tps6131x_i2c_driver); + +MODULE_DESCRIPTION("Texas Instruments TPS6131X flash LED driver"); +MODULE_AUTHOR("Matthias Fend <matthias.fend@emfend.at>"); +MODULE_LICENSE("GPL"); |
