root/drivers/leds/leds-aw200xx.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Awinic AW20036/AW20054/AW20072/AW20108 LED driver
 *
 * Copyright (c) 2023, SberDevices. All Rights Reserved.
 *
 * Author: Martin Kurbanov <mmkurbanov@sberdevices.ru>
 */

#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/container_of.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/regmap.h>
#include <linux/time.h>
#include <linux/units.h>

#define AW200XX_DIM_MAX                  (BIT(6) - 1)
#define AW200XX_FADE_MAX                 (BIT(8) - 1)
#define AW200XX_IMAX_DEFAULT_uA          60000
#define AW200XX_IMAX_MAX_uA              160000
#define AW200XX_IMAX_MIN_uA              3300

/* Page 0 */
#define AW200XX_REG_PAGE0_BASE 0xc000

/* Select page register */
#define AW200XX_REG_PAGE       0xF0
#define AW200XX_PAGE_MASK      (GENMASK(7, 6) | GENMASK(2, 0))
#define AW200XX_PAGE_SHIFT     0
#define AW200XX_NUM_PAGES      6
#define AW200XX_PAGE_SIZE      256
#define AW200XX_REG(page, reg) \
        (AW200XX_REG_PAGE0_BASE + (page) * AW200XX_PAGE_SIZE + (reg))
#define AW200XX_REG_MAX \
        AW200XX_REG(AW200XX_NUM_PAGES - 1, AW200XX_PAGE_SIZE - 1)
#define AW200XX_PAGE0 0
#define AW200XX_PAGE1 1
#define AW200XX_PAGE2 2
#define AW200XX_PAGE3 3
#define AW200XX_PAGE4 4
#define AW200XX_PAGE5 5

/* Chip ID register */
#define AW200XX_REG_IDR       AW200XX_REG(AW200XX_PAGE0, 0x00)
#define AW200XX_IDR_CHIPID    0x18

/* Sleep mode register */
#define AW200XX_REG_SLPCR     AW200XX_REG(AW200XX_PAGE0, 0x01)
#define AW200XX_SLPCR_ACTIVE  0x00

/* Reset register */
#define AW200XX_REG_RSTR      AW200XX_REG(AW200XX_PAGE0, 0x02)
#define AW200XX_RSTR_RESET    0x01

/* Global current configuration register */
#define AW200XX_REG_GCCR        AW200XX_REG(AW200XX_PAGE0, 0x03)
#define AW200XX_GCCR_IMAX_MASK  GENMASK(7, 4)
#define AW200XX_GCCR_IMAX(x)    ((x) << 4)
#define AW200XX_GCCR_ALLON      BIT(3)

/* Fast clear display control register */
#define AW200XX_REG_FCD       AW200XX_REG(AW200XX_PAGE0, 0x04)
#define AW200XX_FCD_CLEAR     0x01

/* Display size configuration */
#define AW200XX_REG_DSIZE          AW200XX_REG(AW200XX_PAGE0, 0x80)
#define AW200XX_DSIZE_COLUMNS_MAX  12

#define AW200XX_LED2REG(x, columns) \
        ((x) + (((x) / (columns)) * (AW200XX_DSIZE_COLUMNS_MAX - (columns))))

/* DIM current configuration register on page 1 */
#define AW200XX_REG_DIM_PAGE1(x, columns) \
        AW200XX_REG(AW200XX_PAGE1, AW200XX_LED2REG(x, columns))

/*
 * DIM current configuration register (page 4).
 * The even address for current DIM configuration.
 * The odd address for current FADE configuration
 */
#define AW200XX_REG_DIM(x, columns) \
        AW200XX_REG(AW200XX_PAGE4, AW200XX_LED2REG(x, columns) * 2)
#define AW200XX_REG_DIM2FADE(x) ((x) + 1)
#define AW200XX_REG_FADE2DIM(fade) \
        DIV_ROUND_UP((fade) * AW200XX_DIM_MAX, AW200XX_FADE_MAX)

/*
 * Duty ratio of display scan (see p.15 of datasheet for formula):
 *   duty = (592us / 600.5us) * (1 / (display_rows + 1))
 *
 * Multiply to 1000 (MILLI) to improve the accuracy of calculations.
 */
#define AW200XX_DUTY_RATIO(rows) \
        (((592UL * USEC_PER_SEC) / 600500UL) * (MILLI / (rows)) / MILLI)

struct aw200xx_chipdef {
        u32 channels;
        u32 display_size_rows_max;
        u32 display_size_columns;
};

struct aw200xx_led {
        struct led_classdev cdev;
        struct aw200xx *chip;
        int dim;
        u32 num;
};

struct aw200xx {
        const struct aw200xx_chipdef *cdef;
        struct i2c_client *client;
        struct regmap *regmap;
        struct mutex mutex;
        u32 num_leds;
        u32 display_rows;
        struct gpio_desc *hwen;
        struct aw200xx_led leds[] __counted_by(num_leds);
};

static ssize_t dim_show(struct device *dev, struct device_attribute *devattr,
                        char *buf)
{
        struct led_classdev *cdev = dev_get_drvdata(dev);
        struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
        int dim = led->dim;

        if (dim < 0)
                return sysfs_emit(buf, "auto\n");

        return sysfs_emit(buf, "%d\n", dim);
}

static ssize_t dim_store(struct device *dev, struct device_attribute *devattr,
                         const char *buf, size_t count)
{
        struct led_classdev *cdev = dev_get_drvdata(dev);
        struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
        struct aw200xx *chip = led->chip;
        u32 columns = chip->cdef->display_size_columns;
        int dim;
        ssize_t ret;

        if (sysfs_streq(buf, "auto")) {
                dim = -1;
        } else {
                ret = kstrtoint(buf, 0, &dim);
                if (ret)
                        return ret;

                if (dim > AW200XX_DIM_MAX)
                        return -EINVAL;
        }

        mutex_lock(&chip->mutex);

        if (dim >= 0) {
                ret = regmap_write(chip->regmap,
                                   AW200XX_REG_DIM_PAGE1(led->num, columns),
                                   dim);
                if (ret)
                        goto out_unlock;
        }

        led->dim = dim;
        ret = count;

out_unlock:
        mutex_unlock(&chip->mutex);
        return ret;
}
static DEVICE_ATTR_RW(dim);

static struct attribute *dim_attrs[] = {
        &dev_attr_dim.attr,
        NULL
};
ATTRIBUTE_GROUPS(dim);

static int aw200xx_brightness_set(struct led_classdev *cdev,
                                  enum led_brightness brightness)
{
        struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
        struct aw200xx *chip = led->chip;
        int dim;
        u32 reg;
        int ret;

        mutex_lock(&chip->mutex);

        reg = AW200XX_REG_DIM(led->num, chip->cdef->display_size_columns);

        dim = led->dim;
        if (dim < 0)
                dim = AW200XX_REG_FADE2DIM(brightness);

        ret = regmap_write(chip->regmap, reg, dim);
        if (ret)
                goto out_unlock;

        ret = regmap_write(chip->regmap,
                           AW200XX_REG_DIM2FADE(reg), brightness);

out_unlock:
        mutex_unlock(&chip->mutex);

        return ret;
}

static u32 aw200xx_imax_from_global(const struct aw200xx *const chip,
                                    u32 global_imax_uA)
{
        u64 led_imax_uA;

        /*
         * The output current of each LED (see p.14 of datasheet for formula):
         *   Iled = Imax * (dim / 63) * ((fade + 1) / 256) * duty
         *
         * The value of duty is determined by the following formula:
         *   duty = (592us / 600.5us) * (1 / (display_rows + 1))
         *
         * Calculated for the maximum values of fade and dim.
         * We divide by 1000 because we earlier multiplied by 1000 to improve
         * accuracy when calculating the duty.
         */
        led_imax_uA = global_imax_uA * AW200XX_DUTY_RATIO(chip->display_rows);
        do_div(led_imax_uA, MILLI);

        return led_imax_uA;
}

static u32 aw200xx_imax_to_global(const struct aw200xx *const chip,
                                  u32 led_imax_uA)
{
        u32 duty = AW200XX_DUTY_RATIO(chip->display_rows);

        /* The output current of each LED (see p.14 of datasheet for formula) */
        return (led_imax_uA * 1000U) / duty;
}

#define AW200XX_IMAX_MULTIPLIER1    10000
#define AW200XX_IMAX_MULTIPLIER2    3333
#define AW200XX_IMAX_BASE_VAL1      0
#define AW200XX_IMAX_BASE_VAL2      8

/*
 * The AW200XX has a 4-bit register (GCCR) to configure the global current,
 * which ranges from 3.3mA to 160mA. The following table indicates the values
 * of the global current, divided into two parts:
 *
 * +-----------+-----------------+-----------+-----------------+
 * | reg value | global max (mA) | reg value | global max (mA) |
 * +-----------+-----------------+-----------+-----------------+
 * | 0         | 10              | 8         | 3.3             |
 * | 1         | 20              | 9         | 6.7             |
 * | 2         | 30              | 10        | 10              |
 * | 3         | 40              | 11        | 13.3            |
 * | 4         | 60              | 12        | 20              |
 * | 5         | 80              | 13        | 26.7            |
 * | 6         | 120             | 14        | 40              |
 * | 7         | 160             | 15        | 53.3            |
 * +-----------+-----------------+-----------+-----------------+
 *
 * The left part  with a multiplier of 10, and the right part  with a multiplier
 * of 3.3.
 * So we have two formulas to calculate the global current:
 *   for the left part of the table:
 *     imax = coefficient * 10
 *
 *   for the right part of the table:
 *     imax = coefficient * 3.3
 *
 * The coefficient table consists of the following values:
 *   1, 2, 3, 4, 6, 8, 12, 16.
 */
static int aw200xx_set_imax(const struct aw200xx *const chip,
                            u32 led_imax_uA)
{
        u32 g_imax_uA = aw200xx_imax_to_global(chip, led_imax_uA);
        static const u32 coeff_table[] = {1, 2, 3, 4, 6, 8, 12, 16};
        u32 gccr_imax = UINT_MAX;
        u32 cur_imax = 0;
        int i;

        for (i = 0; i < ARRAY_SIZE(coeff_table); i++) {
                u32 imax;

                /* select closest ones */
                imax = coeff_table[i] * AW200XX_IMAX_MULTIPLIER1;
                if (g_imax_uA >= imax && imax > cur_imax) {
                        cur_imax = imax;
                        gccr_imax = i + AW200XX_IMAX_BASE_VAL1;
                }

                imax = coeff_table[i] * AW200XX_IMAX_MULTIPLIER2;
                imax = DIV_ROUND_CLOSEST(imax, 100) * 100;
                if (g_imax_uA >= imax && imax > cur_imax) {
                        cur_imax = imax;
                        gccr_imax = i + AW200XX_IMAX_BASE_VAL2;
                }
        }

        if (gccr_imax == UINT_MAX)
                return -EINVAL;

        return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR,
                                  AW200XX_GCCR_IMAX_MASK,
                                  AW200XX_GCCR_IMAX(gccr_imax));
}

static int aw200xx_chip_reset(const struct aw200xx *const chip)
{
        int ret;

        ret = regmap_write(chip->regmap, AW200XX_REG_RSTR, AW200XX_RSTR_RESET);
        if (ret)
                return ret;

        /* According to the datasheet software reset takes at least 1ms */
        fsleep(1000);

        regcache_mark_dirty(chip->regmap);
        return regmap_write(chip->regmap, AW200XX_REG_FCD, AW200XX_FCD_CLEAR);
}

static int aw200xx_chip_init(const struct aw200xx *const chip)
{
        int ret;

        ret = regmap_write(chip->regmap, AW200XX_REG_DSIZE,
                           chip->display_rows - 1);
        if (ret)
                return ret;

        ret = regmap_write(chip->regmap, AW200XX_REG_SLPCR,
                           AW200XX_SLPCR_ACTIVE);
        if (ret)
                return ret;

        return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR,
                                  AW200XX_GCCR_ALLON, AW200XX_GCCR_ALLON);
}

static int aw200xx_chip_check(const struct aw200xx *const chip)
{
        struct device *dev = &chip->client->dev;
        u32 chipid;
        int ret;

        ret = regmap_read(chip->regmap, AW200XX_REG_IDR, &chipid);
        if (ret)
                return dev_err_probe(dev, ret, "Failed to read chip ID\n");

        if (chipid != AW200XX_IDR_CHIPID)
                return dev_err_probe(dev, -ENODEV,
                                     "Chip reported wrong ID: %x\n", chipid);

        return 0;
}

static void aw200xx_enable(const struct aw200xx *const chip)
{
        gpiod_set_value_cansleep(chip->hwen, 1);

        /*
         * After HWEN pin set high the chip begins to load the OTP information,
         * which takes 200us to complete. About 200us wait time is needed for
         * internal oscillator startup and display SRAM initialization. After
         * display SRAM initialization, the registers in page1 to page5 can be
         * configured via i2c interface.
         */
        fsleep(400);
}

static void aw200xx_disable(const struct aw200xx *const chip)
{
        gpiod_set_value_cansleep(chip->hwen, 0);
}

static int aw200xx_probe_get_display_rows(struct device *dev,
                                          struct aw200xx *chip)
{
        struct fwnode_handle *child;
        u32 max_source = 0;

        device_for_each_child_node(dev, child) {
                u32 source;
                int ret;

                ret = fwnode_property_read_u32(child, "reg", &source);
                if (ret || source >= chip->cdef->channels)
                        continue;

                max_source = max(max_source, source);
        }

        if (max_source == 0)
                return -EINVAL;

        chip->display_rows = max_source / chip->cdef->display_size_columns + 1;

        return 0;
}

static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
{
        u32 current_min, current_max, min_uA;
        int ret;
        int i;

        ret = aw200xx_probe_get_display_rows(dev, chip);
        if (ret)
                return dev_err_probe(dev, ret,
                                     "No valid led definitions found\n");

        current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA);
        current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA);
        min_uA = UINT_MAX;
        i = 0;

        device_for_each_child_node_scoped(dev, child) {
                struct led_init_data init_data = {};
                struct aw200xx_led *led;
                u32 source, imax;

                ret = fwnode_property_read_u32(child, "reg", &source);
                if (ret) {
                        dev_err(dev, "Missing reg property\n");
                        chip->num_leds--;
                        continue;
                }

                if (source >= chip->cdef->channels) {
                        dev_err(dev, "LED reg %u out of range (max %u)\n",
                                source, chip->cdef->channels);
                        chip->num_leds--;
                        continue;
                }

                ret = fwnode_property_read_u32(child, "led-max-microamp",
                                               &imax);
                if (ret) {
                        dev_info(&chip->client->dev,
                                 "DT property led-max-microamp is missing\n");
                } else if (imax < current_min || imax > current_max) {
                        dev_err(dev, "Invalid value %u for led-max-microamp\n",
                                imax);
                        chip->num_leds--;
                        continue;
                } else {
                        min_uA = min(min_uA, imax);
                }

                led = &chip->leds[i];
                led->dim = -1;
                led->num = source;
                led->chip = chip;
                led->cdev.brightness_set_blocking = aw200xx_brightness_set;
                led->cdev.max_brightness = AW200XX_FADE_MAX;
                led->cdev.groups = dim_groups;
                init_data.fwnode = child;

                ret = devm_led_classdev_register_ext(dev, &led->cdev,
                                                     &init_data);
                if (ret)
                        break;

                i++;
        }

        if (!chip->num_leds)
                return -EINVAL;

        if (min_uA == UINT_MAX) {
                min_uA = aw200xx_imax_from_global(chip,
                                                  AW200XX_IMAX_DEFAULT_uA);
        }

        return aw200xx_set_imax(chip, min_uA);
}

static const struct regmap_range_cfg aw200xx_ranges[] = {
        {
                .name = "aw200xx",
                .range_min = 0,
                .range_max = AW200XX_REG_MAX,
                .selector_reg = AW200XX_REG_PAGE,
                .selector_mask = AW200XX_PAGE_MASK,
                .selector_shift = AW200XX_PAGE_SHIFT,
                .window_start = 0,
                .window_len = AW200XX_PAGE_SIZE,
        },
};

static const struct regmap_range aw200xx_writeonly_ranges[] = {
        regmap_reg_range(AW200XX_REG(AW200XX_PAGE1, 0x00), AW200XX_REG_MAX),
};

static const struct regmap_access_table aw200xx_readable_table = {
        .no_ranges = aw200xx_writeonly_ranges,
        .n_no_ranges = ARRAY_SIZE(aw200xx_writeonly_ranges),
};

static const struct regmap_range aw200xx_readonly_ranges[] = {
        regmap_reg_range(AW200XX_REG_IDR, AW200XX_REG_IDR),
};

static const struct regmap_access_table aw200xx_writeable_table = {
        .no_ranges = aw200xx_readonly_ranges,
        .n_no_ranges = ARRAY_SIZE(aw200xx_readonly_ranges),
};

static const struct regmap_config aw200xx_regmap_config = {
        .reg_bits = 8,
        .val_bits = 8,
        .max_register = AW200XX_REG_MAX,
        .ranges = aw200xx_ranges,
        .num_ranges = ARRAY_SIZE(aw200xx_ranges),
        .rd_table = &aw200xx_readable_table,
        .wr_table = &aw200xx_writeable_table,
        .cache_type = REGCACHE_MAPLE,
        .disable_locking = true,
};

static void aw200xx_chip_reset_action(void *data)
{
        aw200xx_chip_reset(data);
}

static void aw200xx_disable_action(void *data)
{
        aw200xx_disable(data);
}

static int aw200xx_probe(struct i2c_client *client)
{
        const struct aw200xx_chipdef *cdef;
        struct aw200xx *chip;
        int count;
        int ret;

        cdef = device_get_match_data(&client->dev);
        if (!cdef)
                return -ENODEV;

        count = device_get_child_node_count(&client->dev);
        if (!count || count > cdef->channels)
                return dev_err_probe(&client->dev, -EINVAL,
                                     "Incorrect number of leds (%d)", count);

        chip = devm_kzalloc(&client->dev, struct_size(chip, leds, count),
                            GFP_KERNEL);
        if (!chip)
                return -ENOMEM;

        chip->cdef = cdef;
        chip->num_leds = count;
        chip->client = client;
        i2c_set_clientdata(client, chip);

        chip->regmap = devm_regmap_init_i2c(client, &aw200xx_regmap_config);
        if (IS_ERR(chip->regmap))
                return PTR_ERR(chip->regmap);

        chip->hwen = devm_gpiod_get_optional(&client->dev, "enable",
                                             GPIOD_OUT_HIGH);
        if (IS_ERR(chip->hwen))
                return dev_err_probe(&client->dev, PTR_ERR(chip->hwen),
                                     "Cannot get enable GPIO");

        aw200xx_enable(chip);

        ret = devm_add_action(&client->dev, aw200xx_disable_action, chip);
        if (ret)
                return ret;

        ret = aw200xx_chip_check(chip);
        if (ret)
                return ret;

        ret = devm_mutex_init(&client->dev, &chip->mutex);
        if (ret)
                return ret;

        /* Need a lock now since after call aw200xx_probe_fw, sysfs nodes created */
        mutex_lock(&chip->mutex);

        ret = aw200xx_chip_reset(chip);
        if (ret)
                goto out_unlock;

        ret = devm_add_action(&client->dev, aw200xx_chip_reset_action, chip);
        if (ret)
                goto out_unlock;

        ret = aw200xx_probe_fw(&client->dev, chip);
        if (ret)
                goto out_unlock;

        ret = aw200xx_chip_init(chip);

out_unlock:
        if (ret)
                aw200xx_disable(chip);

        mutex_unlock(&chip->mutex);
        return ret;
}

static const struct aw200xx_chipdef aw20036_cdef = {
        .channels = 36,
        .display_size_rows_max = 3,
        .display_size_columns = 12,
};

static const struct aw200xx_chipdef aw20054_cdef = {
        .channels = 54,
        .display_size_rows_max = 6,
        .display_size_columns = 9,
};

static const struct aw200xx_chipdef aw20072_cdef = {
        .channels = 72,
        .display_size_rows_max = 6,
        .display_size_columns = 12,
};

static const struct aw200xx_chipdef aw20108_cdef = {
        .channels = 108,
        .display_size_rows_max = 9,
        .display_size_columns = 12,
};

static const struct i2c_device_id aw200xx_id[] = {
        { "aw20036" },
        { "aw20054" },
        { "aw20072" },
        { "aw20108" },
        {}
};
MODULE_DEVICE_TABLE(i2c, aw200xx_id);

static const struct of_device_id aw200xx_match_table[] = {
        { .compatible = "awinic,aw20036", .data = &aw20036_cdef, },
        { .compatible = "awinic,aw20054", .data = &aw20054_cdef, },
        { .compatible = "awinic,aw20072", .data = &aw20072_cdef, },
        { .compatible = "awinic,aw20108", .data = &aw20108_cdef, },
        {}
};
MODULE_DEVICE_TABLE(of, aw200xx_match_table);

static struct i2c_driver aw200xx_driver = {
        .driver = {
                .name = "aw200xx",
                .of_match_table = aw200xx_match_table,
        },
        .probe = aw200xx_probe,
        .id_table = aw200xx_id,
};
module_i2c_driver(aw200xx_driver);

MODULE_AUTHOR("Martin Kurbanov <mmkurbanov@sberdevices.ru>");
MODULE_DESCRIPTION("AW200XX LED driver");
MODULE_LICENSE("GPL");