From: André Apitzsch <g...@apitzsch.eu>

The SY7802 is a current-regulated charge pump which can regulate two
current levels for Flash and Torch modes.

It is a high-current synchronous boost converter with 2-channel high
side current sources. Each channel is able to deliver 900mA current.

Acked-by: Lee Jones <l...@kernel.org>
Signed-off-by: André Apitzsch <g...@apitzsch.eu>
---
 drivers/leds/flash/Kconfig       |  11 +
 drivers/leds/flash/Makefile      |   1 +
 drivers/leds/flash/leds-sy7802.c | 539 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 551 insertions(+)

diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
index 809b6d98bb3e..f39f0bfe6eef 100644
--- a/drivers/leds/flash/Kconfig
+++ b/drivers/leds/flash/Kconfig
@@ -121,4 +121,15 @@ config LEDS_SGM3140
          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".
+
 endif # LEDS_CLASS_FLASH
diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile
index 91d60a4b7952..48860eeced79 100644
--- a/drivers/leds/flash/Makefile
+++ b/drivers/leds/flash/Makefile
@@ -11,3 +11,4 @@ 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_SGM3140)     += leds-sgm3140.o
+obj-$(CONFIG_LEDS_SY7802)      += leds-sy7802.o
diff --git a/drivers/leds/flash/leds-sy7802.c b/drivers/leds/flash/leds-sy7802.c
new file mode 100644
index 000000000000..ddac836762af
--- /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 <g...@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 <g...@apitzsch.eu>");
+MODULE_DESCRIPTION("Silergy SY7802 flash LED driver");
+MODULE_LICENSE("GPL");

-- 
2.45.2



Reply via email to