root/drivers/leds/flash/leds-sy7802.c
// 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");