On Tue, Feb 28, 2023 at 11:11 PM Martin Kurbanov
<mmkurbanov@sberdevices.ru> wrote:
>
> This commit adds support for AWINIC AW20036/AW20054/AW20072 LED driver.
> This driver supports following AW200XX features:
> - Individual 64-level DIM currents
I'm wondering if I already commented on the v1 of this. A lot of
issues with the code and your email rings a bell...
Okay, I have dug into archives and it was something else.
...
> +Date: February 2023
Blast from the past? The best you can get is March 2023.
...
> +Description: 64-level DIM current. If write negative value or "auto",
If you write a
> + the dim will be calculated according to the brightness.
...
> +config LEDS_AW200XX
> + tristate "LED support for Awinic AW20036/AW20054/AW20072"
> + depends on LEDS_CLASS
> + depends on I2C
> + help
> + This option enables support for the AW20036/AW20054/AW20072 LED driver.
> + It is a 3x12/6x9/6x12 matrix LED driver programmed via
> + an I2C interface, up to 36/54/72 LEDs or 12/18/24 RGBs,
> + 3 pattern controllers for auto breathing or group dimming control.
What would be the name of the module if M?
...
> +/**
Is it a kernel doc?
> + * leds-aw200xx.c - Awinic AW20036/AW20054/AW20072 LED driver
No name of the file in the file. It's impractical (in case it will be
moved/renamed).
> + *
> + * Copyright (c) 2023, SberDevices. All Rights Reserved.
> + *
> + * Author: Martin Kurbanov <mmkurbanov@sberdevices.ru>
> + */
...
> +#include <linux/i2c.h>
> +#include <linux/leds.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/mutex.h>
> +#include <linux/regmap.h>
> +#include <linux/bitfield.h>
Can you keep this sorted?
...
> +#define AW200XX_DIM_MAX 0x3F
> +#define AW200XX_FADE_MAX 0xFF
If it is limited by the amount of bits in the bitfields, better to use
the form of (BIT(x) - 1), where x is the amount of bits.
...
> +#define AW200XX_IMAX_DEFAULT_MICROAMP 60000
> +#define AW200XX_IMAX_MAX_MICROAMP 160000
> +#define AW200XX_IMAX_MIN_MICROAMP 3300
A is the unit, and for microamperes we already use (in another
driver(s)) the _uA suffix.
...
> +/* Page 0 */
> +#define AW200XX_REG_PAGE0_BASE 0xc000
Indeed, like Pavel mentioned, why not consider the DRM approach for
this? If it's not really a display similar to LCD, then there is
drivers/auxdisplay folder for the non-standard / alphanumeric / etc
cases.
...
> + (AW200XX_REG_PAGE0_BASE + ((page) * AW200XX_PAGE_SIZE) + (reg))
Multiplication doesn't require parentheses.
...
> +#define AW200XX_PAT_T3_LT_MASK 0xFF
> +#define AW200XX_PAT0_T3_LT_LSB(x) ((x) & 0xFF)
GENMASK()
...
> +#define AW200XX_PAT0_T_LT_MAX 0xFFF
(BIT(12) - 1) ?
...
> +#define AW200XX_PAT_T1_T3_MASK 0xF0
> +#define AW200XX_PAT_T2_T4_MASK 0x0F
GENMASK()
...
> +#define AW200XX_TEMPLATE_TIME_MAX 0x0F
(BIT(4) - 1)
...
> +/* Duty ratio of display scan (see p.15 of datasheet for formula) */
> +#define AW200XX_DUTY_RATIO(rows) \
> + (((592UL * 1000000UL) / 600500UL) * (1000UL / (rows)) / 1000UL)
Something to use from units.h?
...
> +struct aw200xx_led {
> + struct aw200xx *chip;
> + struct led_classdev cdev;
Moving embedded structure to be the first member might make some code
to be no-op at compile time.
> + int dim;
> + u32 num;
> +};
...
> + ssize_t ret;
Useless, just use return directly.
> + if (dim < 0)
> + ret = sysfs_emit(buf, "auto\n");
> + else
> + ret = sysfs_emit(buf, "%d\n", dim);
> +
> + return ret;
if (dim >= 0)
return sysfs_emit(...);
return sysfs_emit(...);
...
> + ret = kstrtoint(buf, 0, &dim);
> + if (ret < 0 || dim > AW200XX_DIM_MAX)
> + return -EINVAL;
Why shadowing ret?
Hint: it may not be EINVAL in some cases.
...
> + if (dim >= 0) {
Hmm... Why not simply use an unsigned type?
> + }
...
> + /* The output current of each LED (see p.14 of datasheet for formula) */
> + return (duty * global_imax_microamp) / 1000U;
units.h ?
...
> + /* The output current of each LED (see p.14 of datasheet for formula) */
> + return (led_imax_microamp * 1000U) / duty;
Ditto.
...
> +static int aw200xx_set_imax(const struct aw200xx *const chip,
> + u32 led_imax_microamp)
> +{
> + struct imax_global {
> + u32 regval;
> + u32 microamp;
> + } imaxs[] = {
> + { 8, 3300 },
> + { 9, 6700 },
> + { 0, 10000 },
> + { 11, 13300 },
> + { 1, 20000 },
> + { 13, 26700 },
> + { 2, 30000 },
> + { 3, 40000 },
> + { 15, 53300 },
> + { 4, 60000 },
> + { 5, 80000 },
> + { 6, 120000 },
> + { 7, 160000 },
This looks a bit random. Is there any pattern on how value is
connected to the register value?
> + };
> + u32 g_imax_microamp = aw200xx_imax_to_global(chip, led_imax_microamp);
> + int i;
int i = ARRAY_SIZE(...);
> + for (i = ARRAY_SIZE(imaxs) - 1; i >= 0; i--) {
while (i--) {
> + if (g_imax_microamp >= imaxs[i].microamp)
> + break;
> + }
> +
Redundant blank line.
> + if (i < 0)
> + return -EINVAL;
> +
> + return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR,
> + AW200XX_GCCR_IMAX_MASK,
> + AW200XX_GCCR_IMAX(imaxs[i].regval));
> +}
...
> + ret = regmap_write(chip->regmap, AW200XX_REG_FCD, AW200XX_FCD_CLEAR);
> +
> + return ret;
return regmap_write(...);
...
> + ret = regmap_update_bits(chip->regmap, AW200XX_REG_GCCR,
> + AW200XX_GCCR_ALLON, AW200XX_GCCR_ALLON);
> +
> + return ret;
Ditto.
...
> + ret = aw200xx_set_imax(chip, min_microamp);
> +
> + return ret;
Ditto.
...
> + chip = devm_kzalloc(&client->dev,
> + struct_size(chip, leds, count),
> + GFP_KERNEL);
There is a lot of room on the previous lines.
> + if (!chip)
> + return -ENOMEM;
...
> +static const struct of_device_id __maybe_unused aw200xx_match_table[] = {
> + { .compatible = "awinic,aw20036", .data = &aw20036_cdef, },
> + { .compatible = "awinic,aw20054", .data = &aw20054_cdef, },
> + { .compatible = "awinic,aw20072", .data = &aw20072_cdef, },
> + {},
Comma is not needed for the terminator entry.
> +};
...
> +static struct i2c_driver aw200xx_driver = {
> + .driver = {
> + .name = "aw200xx",
> + .of_match_table = of_match_ptr(aw200xx_match_table),
Why of_match_ptr()? It's a very rare case when you really need this.
> + },
> + .probe_new = aw200xx_probe,
> + .remove = aw200xx_remove,
> + .id_table = aw200xx_id,
> +};
> +
Redundant blank line.
> +module_i2c_driver(aw200xx_driver);
...
> +MODULE_ALIAS("platform:leds-aw200xx");
What is this?! Or i.o.w. why is this violation of the subsystems?
--
With Best Regards,
Andy Shevchenko
new file mode 100644
@@ -0,0 +1,4 @@
+What: /sys/class/leds/<led>/dim
+Date: February 2023
+Description: 64-level DIM current. If write negative value or "auto",
+ the dim will be calculated according to the brightness.
@@ -94,6 +94,16 @@ config LEDS_ARIEL
Say Y to if your machine is a Dell Wyse 3020 thin client.
+config LEDS_AW200XX
+ tristate "LED support for Awinic AW20036/AW20054/AW20072"
+ depends on LEDS_CLASS
+ depends on I2C
+ help
+ This option enables support for the AW20036/AW20054/AW20072 LED driver.
+ It is a 3x12/6x9/6x12 matrix LED driver programmed via
+ an I2C interface, up to 36/54/72 LEDs or 12/18/24 RGBs,
+ 3 pattern controllers for auto breathing or group dimming control.
+
config LEDS_AW2013
tristate "LED support for Awinic AW2013"
depends on LEDS_CLASS && I2C && OF
@@ -15,6 +15,7 @@ obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o
obj-$(CONFIG_LEDS_APU) += leds-apu.o
obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o
obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
+obj-$(CONFIG_LEDS_AW200XX) += leds-aw200xx.o
obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o
obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
new file mode 100644
@@ -0,0 +1,649 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * leds-aw200xx.c - Awinic AW20036/AW20054/AW20072 LED driver
+ *
+ * Copyright (c) 2023, SberDevices. All Rights Reserved.
+ *
+ * Author: Martin Kurbanov <mmkurbanov@sberdevices.ru>
+ */
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/bitfield.h>
+
+#define AW200XX_LEDS_MAX 72
+#define AW200XX_PATTERN_MAX 3
+#define AW200XX_DIM_MAX 0x3F
+#define AW200XX_FADE_MAX 0xFF
+#define AW200XX_IMAX_DEFAULT_MICROAMP 60000
+#define AW200XX_IMAX_MAX_MICROAMP 160000
+#define AW200XX_IMAX_MIN_MICROAMP 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
+
+/* Interrupt status register */
+#define AW200XX_REG_ISRFLT AW200XX_REG(AW200XX_PAGE0, 0x0B)
+#define AW200XX_ISRFLT_PATIS_MASK GENMASK(6, 4)
+
+/* Pattern enable control register */
+#define AW200XX_REG_PATCR AW200XX_REG(AW200XX_PAGE0, 0x43)
+#define AW200XX_PATCR_PAT_IE_MASK GENMASK(6, 4)
+#define AW200XX_PATCR_PAT_IE_ALL AW200XX_PATCR_PAT_IE_MASK
+#define AW200XX_PATCR_PAT_ENABLE(x) BIT(x)
+
+/*
+ * Maximum breathing level registers
+ * For patterns 0 - 0x44, 1 - 0x45, 2 - 0x46 (step 1)
+ */
+#define AW200XX_REG_PAT0_MAX_BREATH AW200XX_REG(AW200XX_PAGE0, 0x44)
+
+/*
+ * Minimum breathing level registers
+ * For patterns 0 - 0x47, 1 - 0x48, 2 - 0x49 (step 1)
+ */
+#define AW200XX_REG_PAT0_MIN_BREATH AW200XX_REG(AW200XX_PAGE0, 0x47)
+
+/*
+ * Template 1 (rise-time) & template 2 (on-time) configuration register
+ * For patterns 0 - 0x4A, 1 - 0x4E, 2 - 0x52 (step 4)
+ */
+#define AW200XX_REG_PAT0_T0 AW200XX_REG(AW200XX_PAGE0, 0x4A)
+
+/*
+ * Template 3 (fall-time) & template 4 (off-time) configuration register
+ * For patterns 0 - 0x4B, 1 - 0x4F, 2 - 0x53 (step 4)
+ */
+#define AW200XX_REG_PAT0_T1 AW200XX_REG(AW200XX_PAGE0, 0x4B)
+
+/*
+ * Loop configuration registers:
+ * loop end point setting (LE)
+ * loop beginning point setting (LB)
+ * MSB of loop times (LT)
+ * For patterns 0 - 0x4C, 1 - 0x50, 2 - 0x54 (step 4)
+ */
+#define AW200XX_REG_PAT0_T2 AW200XX_REG(AW200XX_PAGE0, 0x4C)
+#define AW200XX_REG_PATX_T2(x) (AW200XX_REG_PAT0_T2 + (x))
+
+/*
+ * Loop configuration registers:
+ * LSB of loop times (LT)
+ * For patterns 0 - 0x4D, 1 - 0x51, 2 - 0x55 (step 4)
+ */
+#define AW200XX_REG_PAT0_T3 AW200XX_REG(AW200XX_PAGE0, 0x4D)
+#define AW200XX_REG_PATX_T3(x) (AW200XX_REG_PAT0_T3 + (x))
+
+#define AW200XX_PAT_T2_LE_MASK GENMASK(7, 6)
+#define AW200XX_PAT_T2_LB_MASK GENMASK(5, 4)
+#define AW200XX_PAT_T2_LT_MASK GENMASK(3, 0)
+#define AW200XX_PAT_T3_LT_MASK 0xFF
+#define AW200XX_PAT0_T2_LT_MSB(x) ((x) >> 8)
+#define AW200XX_PAT0_T3_LT_LSB(x) ((x) & 0xFF)
+#define AW200XX_PAT0_T_LT(msb, lsb) ((msb) << 8 | (lsb))
+#define AW200XX_PAT0_T_LT_MAX 0xFFF
+
+#define AW200XX_PAT_T_STEP 4
+
+#define AW200XX_PAT_T1_T3_MASK 0xF0
+#define AW200XX_PAT_T2_T4_MASK 0x0F
+#define AW200XX_TEMPLATE_TIME_MAX 0x0F
+
+/*
+ * Pattern mode configuration register
+ * For patterns 0 - 0x56, 1 - 0x57, 2 - 0x58 (step 1)
+ */
+#define AW200XX_REG_PAT0_CFG AW200XX_REG(AW200XX_PAGE0, 0x56)
+#define AW200XX_PAT_CFG_MODE_MASK BIT(0)
+#define AW200XX_PAT_CFG_RAMP_MASK BIT(1)
+#define AW200XX_PAT_CFG_SWITCH_MASK BIT(2)
+
+/* Start pattern register */
+#define AW200XX_REG_PATGO AW200XX_REG(AW200XX_PAGE0, 0x59)
+#define AW200XX_PATGO(x) BIT(x)
+#define AW200XX_PATGO_RUN(x, run) ((run) << (x))
+#define AW200XX_PATGO_STATE(x) BIT((x) + 4)
+
+/* 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))))
+
+/* Patern selection register*/
+#define AW200XX_REG_PAT_SELECT(x, columns) \
+ AW200XX_REG(AW200XX_PAGE3, AW200XX_LED2REG(x, columns))
+#define AW200XX_PATX_SELECT(x) ((x) + 1)
+
+/*
+ * 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_FADE(x, columns) (AW200XX_REG_DIM(x, columns) + 1)
+
+/* Duty ratio of display scan (see p.15 of datasheet for formula) */
+#define AW200XX_DUTY_RATIO(rows) \
+ (((592UL * 1000000UL) / 600500UL) * (1000UL / (rows)) / 1000UL)
+
+struct aw200xx_led {
+ struct aw200xx *chip;
+ struct led_classdev cdev;
+ 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 aw200xx_led leds[];
+};
+
+struct aw200xx_chipdef {
+ u32 channels;
+ u32 display_size_rows_max;
+ u32 display_size_columns;
+};
+
+static ssize_t aw200xx_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;
+ ssize_t ret;
+
+ if (dim < 0)
+ ret = sysfs_emit(buf, "auto\n");
+ else
+ ret = sysfs_emit(buf, "%d\n", dim);
+
+ return ret;
+}
+
+static ssize_t aw200xx_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 < 0 || dim > AW200XX_DIM_MAX)
+ return -EINVAL;
+ }
+
+ mutex_lock(&chip->mutex);
+
+ if (dim >= 0) {
+ ret = regmap_write(chip->regmap,
+ AW200XX_REG_DIM(led->num, columns), dim);
+ if (ret)
+ goto exit;
+ }
+
+ led->dim = dim;
+ ret = count;
+
+exit:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+static DEVICE_ATTR(dim, 0644, aw200xx_dim_show, aw200xx_dim_store);
+
+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 = brightness / (AW200XX_FADE_MAX / AW200XX_DIM_MAX);
+ dim = max(dim, 1);
+ }
+
+ ret = regmap_write(chip->regmap, reg, dim);
+ if (ret)
+ goto error;
+
+ ret = regmap_write(chip->regmap,
+ AW200XX_REG_DIM2FADE(reg), brightness);
+
+error:
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+static u32 aw200xx_imax_from_global(const struct aw200xx *const chip,
+ u32 global_imax_microamp)
+{
+ unsigned long long duty = AW200XX_DUTY_RATIO(chip->display_rows);
+
+ /* The output current of each LED (see p.14 of datasheet for formula) */
+ return (duty * global_imax_microamp) / 1000U;
+}
+
+static u32 aw200xx_imax_to_global(const struct aw200xx *const chip,
+ u32 led_imax_microamp)
+{
+ u32 duty = AW200XX_DUTY_RATIO(chip->display_rows);
+
+ /* The output current of each LED (see p.14 of datasheet for formula) */
+ return (led_imax_microamp * 1000U) / duty;
+}
+
+static int aw200xx_set_imax(const struct aw200xx *const chip,
+ u32 led_imax_microamp)
+{
+ struct imax_global {
+ u32 regval;
+ u32 microamp;
+ } imaxs[] = {
+ { 8, 3300 },
+ { 9, 6700 },
+ { 0, 10000 },
+ { 11, 13300 },
+ { 1, 20000 },
+ { 13, 26700 },
+ { 2, 30000 },
+ { 3, 40000 },
+ { 15, 53300 },
+ { 4, 60000 },
+ { 5, 80000 },
+ { 6, 120000 },
+ { 7, 160000 },
+ };
+ u32 g_imax_microamp = aw200xx_imax_to_global(chip, led_imax_microamp);
+ int i;
+
+ for (i = ARRAY_SIZE(imaxs) - 1; i >= 0; i--) {
+ if (g_imax_microamp >= imaxs[i].microamp)
+ break;
+ }
+
+ if (i < 0)
+ return -EINVAL;
+
+ return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR,
+ AW200XX_GCCR_IMAX_MASK,
+ AW200XX_GCCR_IMAX(imaxs[i].regval));
+}
+
+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;
+
+ regcache_mark_dirty(chip->regmap);
+ ret = regmap_write(chip->regmap, AW200XX_REG_FCD, AW200XX_FCD_CLEAR);
+
+ return ret;
+}
+
+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;
+
+ ret = regmap_update_bits(chip->regmap, AW200XX_REG_GCCR,
+ AW200XX_GCCR_ALLON, AW200XX_GCCR_ALLON);
+
+ return ret;
+}
+
+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 int aw200xx_probe_dt(struct device *dev, struct aw200xx *chip)
+{
+ struct fwnode_handle *child;
+ u32 current_min, current_max, min_microamp;
+ int ret;
+ int i = 0;
+
+ ret = device_property_read_u32(dev, "awinic,display-rows",
+ &chip->display_rows);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to read 'display-rows' property\n");
+
+ if (!chip->display_rows ||
+ chip->display_rows > chip->cdef->display_size_rows_max) {
+ return dev_err_probe(dev, ret,
+ "Invalid leds display size %u\n",
+ chip->display_rows);
+ }
+
+ current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_MICROAMP);
+ current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_MICROAMP);
+ min_microamp = UINT_MAX;
+
+ device_for_each_child_node(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_microamp = min(min_microamp, imax);
+ }
+
+ led = &chip->leds[i];
+ led->dim = -1;
+ led->num = source;
+ led->chip = chip;
+ led->cdev.brightness_set_blocking = aw200xx_brightness_set;
+ led->cdev.groups = dim_groups;
+ init_data.fwnode = child;
+
+ ret = devm_led_classdev_register_ext(dev, &led->cdev,
+ &init_data);
+ if (ret) {
+ fwnode_handle_put(child);
+ break;
+ }
+
+ i++;
+ }
+
+ if (!chip->num_leds)
+ return -EINVAL;
+
+ if (min_microamp == UINT_MAX) {
+ min_microamp =
+ aw200xx_imax_from_global(chip,
+ AW200XX_IMAX_DEFAULT_MICROAMP);
+ }
+
+ ret = aw200xx_set_imax(chip, min_microamp);
+
+ return ret;
+}
+
+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),
+ regmap_reg_range(AW200XX_REG_ISRFLT, AW200XX_REG_ISRFLT),
+};
+
+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_range aw200xx_volatile_registers[] = {
+ regmap_reg_range(AW200XX_REG_ISRFLT, AW200XX_REG_ISRFLT),
+};
+
+static const struct regmap_access_table aw200xx_volatile_table = {
+ .yes_ranges = aw200xx_volatile_registers,
+ .n_yes_ranges = ARRAY_SIZE(aw200xx_volatile_registers),
+};
+
+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,
+ .volatile_table = &aw200xx_volatile_table,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+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);
+
+ 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);
+
+ ret = aw200xx_chip_check(chip);
+ if (ret)
+ return ret;
+
+ mutex_init(&chip->mutex);
+
+ /* Need a lock now since after call aw200xx_probe_dt, sysfs nodes created */
+ mutex_lock(&chip->mutex);
+
+ ret = aw200xx_probe_dt(&client->dev, chip);
+ if (ret < 0)
+ goto exit;
+
+ ret = aw200xx_chip_reset(chip);
+ if (ret)
+ goto exit;
+
+ ret = aw200xx_chip_init(chip);
+
+exit:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static void aw200xx_remove(struct i2c_client *client)
+{
+ struct aw200xx *chip = i2c_get_clientdata(client);
+
+ aw200xx_chip_reset(chip);
+ mutex_destroy(&chip->mutex);
+}
+
+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 i2c_device_id aw200xx_id[] = {
+ { "aw20036" },
+ { "aw20054" },
+ { "aw20072" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, aw200xx_id);
+
+static const struct of_device_id __maybe_unused aw200xx_match_table[] = {
+ { .compatible = "awinic,aw20036", .data = &aw20036_cdef, },
+ { .compatible = "awinic,aw20054", .data = &aw20054_cdef, },
+ { .compatible = "awinic,aw20072", .data = &aw20072_cdef, },
+ {},
+};
+MODULE_DEVICE_TABLE(of, aw200xx_match_table);
+
+static struct i2c_driver aw200xx_driver = {
+ .driver = {
+ .name = "aw200xx",
+ .of_match_table = of_match_ptr(aw200xx_match_table),
+ },
+ .probe_new = aw200xx_probe,
+ .remove = aw200xx_remove,
+ .id_table = aw200xx_id,
+};
+
+module_i2c_driver(aw200xx_driver);
+
+MODULE_AUTHOR("Martin Kurbanov <mmkurbanov@sberdevices.ru>");
+MODULE_DESCRIPTION("AW200XX LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:leds-aw200xx");