root/drivers/leds/leds-lm3692x.c
// SPDX-License-Identifier: GPL-2.0
// TI LM3692x LED chip family driver
// Copyright (C) 2017-18 Texas Instruments Incorporated - https://www.ti.com/

#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/leds.h>
#include <linux/log2.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>

#define LM36922_MODEL   0
#define LM36923_MODEL   1

#define LM3692X_REV             0x0
#define LM3692X_RESET           0x1
#define LM3692X_EN              0x10
#define LM3692X_BRT_CTRL        0x11
#define LM3692X_PWM_CTRL        0x12
#define LM3692X_BOOST_CTRL      0x13
#define LM3692X_AUTO_FREQ_HI    0x15
#define LM3692X_AUTO_FREQ_LO    0x16
#define LM3692X_BL_ADJ_THRESH   0x17
#define LM3692X_BRT_LSB         0x18
#define LM3692X_BRT_MSB         0x19
#define LM3692X_FAULT_CTRL      0x1e
#define LM3692X_FAULT_FLAGS     0x1f

#define LM3692X_SW_RESET        BIT(0)
#define LM3692X_DEVICE_EN       BIT(0)
#define LM3692X_LED1_EN         BIT(1)
#define LM3692X_LED2_EN         BIT(2)
#define LM36923_LED3_EN         BIT(3)
#define LM3692X_ENABLE_MASK     (LM3692X_DEVICE_EN | LM3692X_LED1_EN | \
                                 LM3692X_LED2_EN | LM36923_LED3_EN)

/* Brightness Control Bits */
#define LM3692X_BL_ADJ_POL      BIT(0)
#define LM3692X_RAMP_RATE_125us 0x00
#define LM3692X_RAMP_RATE_250us BIT(1)
#define LM3692X_RAMP_RATE_500us BIT(2)
#define LM3692X_RAMP_RATE_1ms   (BIT(1) | BIT(2))
#define LM3692X_RAMP_RATE_2ms   BIT(3)
#define LM3692X_RAMP_RATE_4ms   (BIT(3) | BIT(1))
#define LM3692X_RAMP_RATE_8ms   (BIT(2) | BIT(3))
#define LM3692X_RAMP_RATE_16ms  (BIT(1) | BIT(2) | BIT(3))
#define LM3692X_RAMP_EN         BIT(4)
#define LM3692X_BRHT_MODE_REG   0x00
#define LM3692X_BRHT_MODE_PWM   BIT(5)
#define LM3692X_BRHT_MODE_MULTI_RAMP BIT(6)
#define LM3692X_BRHT_MODE_RAMP_MULTI (BIT(5) | BIT(6))
#define LM3692X_MAP_MODE_EXP    BIT(7)

/* PWM Register Bits */
#define LM3692X_PWM_FILTER_100  BIT(0)
#define LM3692X_PWM_FILTER_150  BIT(1)
#define LM3692X_PWM_FILTER_200  (BIT(0) | BIT(1))
#define LM3692X_PWM_HYSTER_1LSB BIT(2)
#define LM3692X_PWM_HYSTER_2LSB BIT(3)
#define LM3692X_PWM_HYSTER_3LSB (BIT(3) | BIT(2))
#define LM3692X_PWM_HYSTER_4LSB BIT(4)
#define LM3692X_PWM_HYSTER_5LSB (BIT(4) | BIT(2))
#define LM3692X_PWM_HYSTER_6LSB (BIT(4) | BIT(3))
#define LM3692X_PWM_POLARITY    BIT(5)
#define LM3692X_PWM_SAMP_4MHZ   BIT(6)
#define LM3692X_PWM_SAMP_24MHZ  BIT(7)

/* Boost Control Bits */
#define LM3692X_OCP_PROT_1A     BIT(0)
#define LM3692X_OCP_PROT_1_25A  BIT(1)
#define LM3692X_OCP_PROT_1_5A   (BIT(0) | BIT(1))
#define LM3692X_OVP_21V         BIT(2)
#define LM3692X_OVP_25V         BIT(3)
#define LM3692X_OVP_29V         (BIT(2) | BIT(3))
#define LM3692X_MIN_IND_22UH    BIT(4)
#define LM3692X_BOOST_SW_1MHZ   BIT(5)
#define LM3692X_BOOST_SW_NO_SHIFT       BIT(6)

/* Fault Control Bits */
#define LM3692X_FAULT_CTRL_OVP BIT(0)
#define LM3692X_FAULT_CTRL_OCP BIT(1)
#define LM3692X_FAULT_CTRL_TSD BIT(2)
#define LM3692X_FAULT_CTRL_OPEN BIT(3)

/* Fault Flag Bits */
#define LM3692X_FAULT_FLAG_OVP BIT(0)
#define LM3692X_FAULT_FLAG_OCP BIT(1)
#define LM3692X_FAULT_FLAG_TSD BIT(2)
#define LM3692X_FAULT_FLAG_SHRT BIT(3)
#define LM3692X_FAULT_FLAG_OPEN BIT(4)

/**
 * struct lm3692x_led
 * @lock: Lock for reading/writing the device
 * @client: Pointer to the I2C client
 * @led_dev: LED class device pointer
 * @regmap: Devices register map
 * @enable_gpio: VDDIO/EN gpio to enable communication interface
 * @regulator: LED supply regulator pointer
 * @led_enable: LED sync to be enabled
 * @model_id: Current device model ID enumerated
 * @boost_ctrl: Cached configuration for the boost control register
 * @brightness_ctrl: Cached configuration for brightness/brightness control
 * @enabled: Cached enable state of the device
 */
struct lm3692x_led {
        struct mutex lock;
        struct i2c_client *client;
        struct led_classdev led_dev;
        struct regmap *regmap;
        struct gpio_desc *enable_gpio;
        struct regulator *regulator;
        int led_enable;
        int model_id;

        u8 boost_ctrl, brightness_ctrl;
        bool enabled;
};

static const struct reg_default lm3692x_reg_defs[] = {
        {LM3692X_EN, 0xf},
        {LM3692X_BRT_CTRL, 0x61},
        {LM3692X_PWM_CTRL, 0x73},
        {LM3692X_BOOST_CTRL, 0x6f},
        {LM3692X_AUTO_FREQ_HI, 0x0},
        {LM3692X_AUTO_FREQ_LO, 0x0},
        {LM3692X_BL_ADJ_THRESH, 0x0},
        {LM3692X_BRT_LSB, 0x7},
        {LM3692X_BRT_MSB, 0xff},
        {LM3692X_FAULT_CTRL, 0x7},
};

static const struct regmap_config lm3692x_regmap_config = {
        .reg_bits = 8,
        .val_bits = 8,

        .max_register = LM3692X_FAULT_FLAGS,
        .reg_defaults = lm3692x_reg_defs,
        .num_reg_defaults = ARRAY_SIZE(lm3692x_reg_defs),
        .cache_type = REGCACHE_MAPLE,
};

static int lm3692x_fault_check(struct lm3692x_led *led)
{
        int ret;
        unsigned int read_buf;

        ret = regmap_read(led->regmap, LM3692X_FAULT_FLAGS, &read_buf);
        if (ret)
                return ret;

        if (read_buf)
                dev_err(&led->client->dev, "Detected a fault 0x%X\n", read_buf);

        /* The first read may clear the fault.  Check again to see if the fault
         * still exits and return that value.
         */
        regmap_read(led->regmap, LM3692X_FAULT_FLAGS, &read_buf);
        if (read_buf)
                dev_err(&led->client->dev, "Second read of fault flags 0x%X\n",
                        read_buf);

        return read_buf;
}

static int lm3692x_leds_enable(struct lm3692x_led *led)
{
        int enable_state;
        int ret, reg_ret;

        if (led->enabled)
                return 0;

        if (led->regulator) {
                ret = regulator_enable(led->regulator);
                if (ret) {
                        dev_err(&led->client->dev,
                                "Failed to enable regulator: %d\n", ret);
                        return ret;
                }
        }

        if (led->enable_gpio)
                gpiod_direction_output(led->enable_gpio, 1);

        ret = lm3692x_fault_check(led);
        if (ret) {
                dev_err(&led->client->dev, "Cannot read/clear faults: %d\n",
                        ret);
                goto out;
        }

        ret = regmap_write(led->regmap, LM3692X_BRT_CTRL, 0x00);
        if (ret)
                goto out;

        /*
         * For glitch free operation, the following data should
         * only be written while LEDx enable bits are 0 and the device enable
         * bit is set to 1.
         * per Section 7.5.14 of the data sheet
         */
        ret = regmap_write(led->regmap, LM3692X_EN, LM3692X_DEVICE_EN);
        if (ret)
                goto out;

        /* Set the brightness to 0 so when enabled the LEDs do not come
         * on with full brightness.
         */
        ret = regmap_write(led->regmap, LM3692X_BRT_MSB, 0);
        if (ret)
                goto out;

        ret = regmap_write(led->regmap, LM3692X_BRT_LSB, 0);
        if (ret)
                goto out;

        ret = regmap_write(led->regmap, LM3692X_PWM_CTRL,
                LM3692X_PWM_FILTER_100 | LM3692X_PWM_SAMP_24MHZ);
        if (ret)
                goto out;

        ret = regmap_write(led->regmap, LM3692X_BOOST_CTRL, led->boost_ctrl);
        if (ret)
                goto out;

        ret = regmap_write(led->regmap, LM3692X_AUTO_FREQ_HI, 0x00);
        if (ret)
                goto out;

        ret = regmap_write(led->regmap, LM3692X_AUTO_FREQ_LO, 0x00);
        if (ret)
                goto out;

        ret = regmap_write(led->regmap, LM3692X_BL_ADJ_THRESH, 0x00);
        if (ret)
                goto out;

        ret = regmap_write(led->regmap, LM3692X_BRT_CTRL,
                        LM3692X_BL_ADJ_POL | LM3692X_RAMP_EN);
        if (ret)
                goto out;

        switch (led->led_enable) {
        case 0:
        default:
                if (led->model_id == LM36923_MODEL)
                        enable_state = LM3692X_LED1_EN | LM3692X_LED2_EN |
                               LM36923_LED3_EN;
                else
                        enable_state = LM3692X_LED1_EN | LM3692X_LED2_EN;

                break;
        case 1:
                enable_state = LM3692X_LED1_EN;
                break;
        case 2:
                enable_state = LM3692X_LED2_EN;
                break;

        case 3:
                if (led->model_id == LM36923_MODEL) {
                        enable_state = LM36923_LED3_EN;
                        break;
                }

                ret = -EINVAL;
                dev_err(&led->client->dev,
                        "LED3 sync not available on this device\n");
                goto out;
        }

        ret = regmap_update_bits(led->regmap, LM3692X_EN, LM3692X_ENABLE_MASK,
                                 enable_state | LM3692X_DEVICE_EN);

        led->enabled = true;
        return ret;
out:
        dev_err(&led->client->dev, "Fail writing initialization values\n");

        if (led->enable_gpio)
                gpiod_direction_output(led->enable_gpio, 0);

        if (led->regulator) {
                reg_ret = regulator_disable(led->regulator);
                if (reg_ret)
                        dev_err(&led->client->dev,
                                "Failed to disable regulator: %d\n", reg_ret);
        }

        return ret;
}

static int lm3692x_leds_disable(struct lm3692x_led *led)
{
        int ret;

        if (!led->enabled)
                return 0;

        ret = regmap_update_bits(led->regmap, LM3692X_EN, LM3692X_DEVICE_EN, 0);
        if (ret) {
                dev_err(&led->client->dev, "Failed to disable regulator: %d\n",
                        ret);
                return ret;
        }

        if (led->enable_gpio)
                gpiod_direction_output(led->enable_gpio, 0);

        if (led->regulator) {
                ret = regulator_disable(led->regulator);
                if (ret)
                        dev_err(&led->client->dev,
                                "Failed to disable regulator: %d\n", ret);
        }

        led->enabled = false;
        return ret;
}

static int lm3692x_brightness_set(struct led_classdev *led_cdev,
                                enum led_brightness brt_val)
{
        struct lm3692x_led *led =
                        container_of(led_cdev, struct lm3692x_led, led_dev);
        int ret;
        int led_brightness_lsb = (brt_val >> 5);

        mutex_lock(&led->lock);

        if (brt_val == 0) {
                ret = lm3692x_leds_disable(led);
                goto out;
        } else {
                lm3692x_leds_enable(led);
        }

        ret = lm3692x_fault_check(led);
        if (ret) {
                dev_err(&led->client->dev, "Cannot read/clear faults: %d\n",
                        ret);
                goto out;
        }

        ret = regmap_write(led->regmap, LM3692X_BRT_MSB, brt_val);
        if (ret) {
                dev_err(&led->client->dev, "Cannot write MSB: %d\n", ret);
                goto out;
        }

        ret = regmap_write(led->regmap, LM3692X_BRT_LSB, led_brightness_lsb);
        if (ret) {
                dev_err(&led->client->dev, "Cannot write LSB: %d\n", ret);
                goto out;
        }
out:
        mutex_unlock(&led->lock);
        return ret;
}

static enum led_brightness lm3692x_max_brightness(struct lm3692x_led *led,
                                                  u32 max_cur)
{
        u32 max_code;

        /* see p.12 of LM36922 data sheet for brightness formula */
        max_code = ((max_cur * 1000) - 37806) / 12195;
        if (max_code > 0x7FF)
                max_code = 0x7FF;

        return max_code >> 3;
}

static int lm3692x_probe_dt(struct lm3692x_led *led)
{
        struct fwnode_handle *child = NULL;
        struct led_init_data init_data = {};
        u32 ovp, max_cur;
        int ret;

        led->enable_gpio = devm_gpiod_get_optional(&led->client->dev,
                                                   "enable", GPIOD_OUT_LOW);
        if (IS_ERR(led->enable_gpio)) {
                ret = PTR_ERR(led->enable_gpio);
                dev_err(&led->client->dev, "Failed to get enable gpio: %d\n",
                        ret);
                return ret;
        }

        led->regulator = devm_regulator_get_optional(&led->client->dev, "vled");
        if (IS_ERR(led->regulator)) {
                ret = PTR_ERR(led->regulator);
                if (ret != -ENODEV)
                        return dev_err_probe(&led->client->dev, ret,
                                             "Failed to get vled regulator\n");

                led->regulator = NULL;
        }

        led->boost_ctrl = LM3692X_BOOST_SW_1MHZ |
                LM3692X_BOOST_SW_NO_SHIFT |
                LM3692X_OCP_PROT_1_5A;
        ret = device_property_read_u32(&led->client->dev,
                                       "ti,ovp-microvolt", &ovp);
        if (ret) {
                led->boost_ctrl |= LM3692X_OVP_29V;
        } else {
                switch (ovp) {
                case 17000000:
                        break;
                case 21000000:
                        led->boost_ctrl |= LM3692X_OVP_21V;
                        break;
                case 25000000:
                        led->boost_ctrl |= LM3692X_OVP_25V;
                        break;
                case 29000000:
                        led->boost_ctrl |= LM3692X_OVP_29V;
                        break;
                default:
                        dev_err(&led->client->dev, "Invalid OVP %d\n", ovp);
                        return -EINVAL;
                }
        }

        child = device_get_next_child_node(&led->client->dev, child);
        if (!child) {
                dev_err(&led->client->dev, "No LED Child node\n");
                return -ENODEV;
        }

        ret = fwnode_property_read_u32(child, "reg", &led->led_enable);
        if (ret) {
                fwnode_handle_put(child);
                dev_err(&led->client->dev, "reg DT property missing\n");
                return ret;
        }

        ret = fwnode_property_read_u32(child, "led-max-microamp", &max_cur);
        led->led_dev.max_brightness = ret ? LED_FULL :
                lm3692x_max_brightness(led, max_cur);

        init_data.fwnode = child;
        init_data.devicename = led->client->name;
        init_data.default_label = ":";

        ret = devm_led_classdev_register_ext(&led->client->dev, &led->led_dev,
                                             &init_data);
        if (ret)
                dev_err(&led->client->dev, "led register err: %d\n", ret);

        fwnode_handle_put(init_data.fwnode);
        return ret;
}

static int lm3692x_probe(struct i2c_client *client)
{
        const struct i2c_device_id *id = i2c_client_get_device_id(client);
        struct lm3692x_led *led;
        int ret;

        led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL);
        if (!led)
                return -ENOMEM;

        mutex_init(&led->lock);
        led->client = client;
        led->led_dev.brightness_set_blocking = lm3692x_brightness_set;
        led->model_id = id->driver_data;
        i2c_set_clientdata(client, led);

        led->regmap = devm_regmap_init_i2c(client, &lm3692x_regmap_config);
        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 = lm3692x_probe_dt(led);
        if (ret)
                return ret;

        ret = lm3692x_leds_enable(led);
        if (ret)
                return ret;

        return 0;
}

static void lm3692x_remove(struct i2c_client *client)
{
        struct lm3692x_led *led = i2c_get_clientdata(client);

        lm3692x_leds_disable(led);
        mutex_destroy(&led->lock);
}

static const struct i2c_device_id lm3692x_id[] = {
        { "lm36922", LM36922_MODEL },
        { "lm36923", LM36923_MODEL },
        { }
};
MODULE_DEVICE_TABLE(i2c, lm3692x_id);

static const struct of_device_id of_lm3692x_leds_match[] = {
        { .compatible = "ti,lm36922", },
        { .compatible = "ti,lm36923", },
        {},
};
MODULE_DEVICE_TABLE(of, of_lm3692x_leds_match);

static struct i2c_driver lm3692x_driver = {
        .driver = {
                .name   = "lm3692x",
                .of_match_table = of_lm3692x_leds_match,
        },
        .probe          = lm3692x_probe,
        .remove         = lm3692x_remove,
        .id_table       = lm3692x_id,
};
module_i2c_driver(lm3692x_driver);

MODULE_DESCRIPTION("Texas Instruments LM3692X LED driver");
MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
MODULE_LICENSE("GPL v2");