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