2022-11-24 20:54:19

by Martin Kurbanov

[permalink] [raw]
Subject: [PATCH v1 0/2] leds: add aw20xx driver

This patch series adds support for AWINIC AW20036/AW20054/AW20072 LED
driver programmed via an I2C interface.

This driver supports following AW200XX features:
- 3 pattern controllers for auto breathing or group dimming control
- Individual 64-level DIM currents
- Interrupt output, low active

Datasheet:
aw20036 - https://www.awinic.com/Public/Uploads/uploadfile/files/20200509/20200509151532_5eb65894d205a.pdf
aw20054 - https://www.awinic.com/Public/Uploads/uploadfile/files/20200509/20200509151602_5eb658b2b77cb.pdf
aw20072 - https://www.awinic.com/Public/Uploads/uploadfile/files/20200509/20200509151754_5eb659227a145.pdf

Add YAML dt-binding schema for AW200XX.

Martin Kurbanov (2):
dt-bindings: leds: add binding for aw200xx
leds: add aw20xx driver

.../bindings/leds/leds-aw200xx.yaml | 110 ++
Documentation/leds/leds-aw200xx.rst | 274 ++++
drivers/leds/Kconfig | 10 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-aw200xx.c | 1113 +++++++++++++++++
include/dt-bindings/leds/leds-aw200xx.h | 48 +
6 files changed, 1556 insertions(+)
create mode 100644 Documentation/devicetree/bindings/leds/leds-aw200xx.yaml
create mode 100644 Documentation/leds/leds-aw200xx.rst
create mode 100644 drivers/leds/leds-aw200xx.c
create mode 100644 include/dt-bindings/leds/leds-aw200xx.h

--
2.38.1


2022-11-24 21:01:18

by Martin Kurbanov

[permalink] [raw]
Subject: [PATCH v1 1/2] dt-bindings: leds: add binding for aw200xx

Add YAML devicetree binding for AWINIC AW20036/AW20052/AW20074
led driver.

Signed-off-by: Martin Kurbanov <[email protected]>
---
.../bindings/leds/leds-aw200xx.yaml | 110 ++++++++++++++++++
include/dt-bindings/leds/leds-aw200xx.h | 48 ++++++++
2 files changed, 158 insertions(+)
create mode 100644 Documentation/devicetree/bindings/leds/leds-aw200xx.yaml
create mode 100644 include/dt-bindings/leds/leds-aw200xx.h

diff --git a/Documentation/devicetree/bindings/leds/leds-aw200xx.yaml b/Documentation/devicetree/bindings/leds/leds-aw200xx.yaml
new file mode 100644
index 000000000000..3bdadcbc2ee2
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-aw200xx.yaml
@@ -0,0 +1,110 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/leds-aw200xx.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: AWINIC AW200XX LED Driver
+
+maintainers:
+ - Martin Kurbanov <[email protected]>
+
+description: |
+ This controller is present on AW20036/AW20054/AW20072.
+ 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.
+
+ For more product information please see the link below:
+ aw20036 - https://www.awinic.com/Public/Uploads/uploadfile/files/20200509/20200509151532_5eb65894d205a.pdf
+ aw20054 - https://www.awinic.com/Public/Uploads/uploadfile/files/20200509/20200509151602_5eb658b2b77cb.pdf
+ aw20072 - https://www.awinic.com/Public/Uploads/uploadfile/files/20200509/20200509151754_5eb659227a145.pdf
+
+properties:
+ compatible:
+ enum:
+ - awinic,aw20036
+ - awinic,aw20054
+ - awinic,aw20072
+
+ reg:
+ maxItems: 1
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ interrupts:
+ maxItems: 1
+
+ display-size:
+ maxItems: 1
+ description:
+ Leds matrix size, see dt-bindings/leds/leds-aw200xx.h
+
+ imax:
+ maxItems: 1
+ description:
+ Maximum supply current, see dt-bindings/leds/leds-aw200xx.h
+
+patternProperties:
+ "^led@[0-9a-f]$":
+ type: object
+ $ref: common.yaml#
+
+ properties:
+ reg:
+ description:
+ LED number
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - "#address-cells"
+ - "#size-cells"
+ - display-size
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/leds/common.h>
+ #include <dt-bindings/leds/leds-aw200xx.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led-controller@3a {
+ compatible = "awinic,aw20036";
+ reg = <0x3a>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-parent = <&gpio_intc>;
+ interrupts = <13 IRQ_TYPE_LEVEL_LOW>;
+
+ display-size = <AW20036_DSIZE_3X12>;
+ imax = <AW200XX_IMAX_60MA>;
+
+ led@0 {
+ reg = <0x0>;
+ color = <LED_COLOR_ID_RED>;
+ };
+
+ led@1 {
+ reg = <0x1>;
+ color = <LED_COLOR_ID_GREEN>;
+ };
+
+ led@2 {
+ reg = <0x2>;
+ color = <LED_COLOR_ID_BLUE>;
+ };
+ };
+ };
+
+...
diff --git a/include/dt-bindings/leds/leds-aw200xx.h b/include/dt-bindings/leds/leds-aw200xx.h
new file mode 100644
index 000000000000..6b2ba4c3c6b1
--- /dev/null
+++ b/include/dt-bindings/leds/leds-aw200xx.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/**
+ * This header provides constants for aw200xx LED bindings.
+ *
+ * Copyright (c) 2022, SberDevices. All Rights Reserved.
+ *
+ * Author: Martin Kurbanov <[email protected]>
+ */
+#ifndef _DT_BINDINGS_LEDS_AW200XX_H
+#define _DT_BINDINGS_LEDS_AW200XX_H
+
+/* Global max current (IMAX) */
+#define AW200XX_IMAX_3_3MA 8
+#define AW200XX_IMAX_6_7MA 9
+#define AW200XX_IMAX_10MA 0
+#define AW200XX_IMAX_13_3MA 11
+#define AW200XX_IMAX_20MA 1
+#define AW200XX_IMAX_26_7MA 13
+#define AW200XX_IMAX_30MA 2
+#define AW200XX_IMAX_40MA 3
+#define AW200XX_IMAX_53_3MA 15
+#define AW200XX_IMAX_60MA 4
+#define AW200XX_IMAX_80MA 5
+#define AW200XX_IMAX_120MA 6
+#define AW200XX_IMAX_160MA 7
+
+/* Display size for aw20036 */
+#define AW20036_DSIZE_1X12 0
+#define AW20036_DSIZE_2X12 1
+#define AW20036_DSIZE_3X12 2
+
+/* Display size for aw20054 */
+#define AW20054_DSIZE_1X9 0
+#define AW20054_DSIZE_2X9 1
+#define AW20054_DSIZE_3X9 2
+#define AW20054_DSIZE_4X9 3
+#define AW20054_DSIZE_5X9 4
+#define AW20054_DSIZE_6X9 5
+
+/* Display size for aw20072 */
+#define AW20072_DSIZE_1X12 0
+#define AW20072_DSIZE_2X12 1
+#define AW20072_DSIZE_3X12 2
+#define AW20072_DSIZE_4X12 3
+#define AW20072_DSIZE_5X12 4
+#define AW20072_DSIZE_6X12 5
+
+#endif /* !_DT_BINDINGS_LEDS_AW200XX_H */
--
2.38.1

2022-11-24 21:02:56

by Martin Kurbanov

[permalink] [raw]
Subject: [PATCH v1 2/2] leds: add aw20xx driver

This commit adds support for AWINIC AW20036/AW20054/AW20072 LED driver.
This driver supports following AW200XX features:
- 3 pattern controllers for auto breathing or group dimming control
- Individual 64-level DIM currents
- Interrupt output, low active

Signed-off-by: Martin Kurbanov <[email protected]>
---
Documentation/leds/leds-aw200xx.rst | 274 +++++++
drivers/leds/Kconfig | 10 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-aw200xx.c | 1113 +++++++++++++++++++++++++++
4 files changed, 1398 insertions(+)
create mode 100644 Documentation/leds/leds-aw200xx.rst
create mode 100644 drivers/leds/leds-aw200xx.c

diff --git a/Documentation/leds/leds-aw200xx.rst b/Documentation/leds/leds-aw200xx.rst
new file mode 100644
index 000000000000..a751b91dfda6
--- /dev/null
+++ b/Documentation/leds/leds-aw200xx.rst
@@ -0,0 +1,274 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=========================================
+Kernel driver for AW20036/AW20054/AW20072
+=========================================
+
+Description
+-----------
+
+The AW20036/AW20054/AW20072 is a 3x12/6x9/6x12 matrix LED driver programmed via
+an I2C interface. The brightness of each LED is independently controlled by
+FADE and DIM parameter.
+
+Three integrated pattern controllers provide auto breathing or group dimming
+control. Each pattern controller can work in auto breathing or manual control
+mode. All breathing parameters including rising/falling slope, on/off time,
+repeat times, min/max brightness and so on are configurable.
+
+Device attribute
+-----------------------------------
+
+**/sys/class/leds/<led>/dim** - 64-level DIM current. If write negative value
+or "auto", the dim will be calculated according to the brightness.
+
+The configuration files for each pattern are located::
+
+ /sys/bus/i2c/devices/xxxx/pattern0/
+ /sys/bus/i2c/devices/xxxx/pattern1/
+ /sys/bus/i2c/devices/xxxx/pattern2/
+
+Directory layout example for pattern
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ $ ls -l /sys/bus/i2c/devices/xxxx/pattern0/
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 clear_leds
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 fall_time
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 loop_begin
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 loop_end_on
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 max_breathing_level
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 min_breathing_level
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 mode
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 off_time
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 on_time
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 ramp
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 repeat
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 rise_time
+ -r--r--r-- 1 root root 4096 Jan 1 00:00 running
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 select_leds
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 start
+ -rw-r--r-- 1 root root 4096 Jan 1 00:00 toggle
+
+Timing parameters
+~~~~~~~~~~~~~~~~~
+
+- **on_time**
+
+- **rise_time**
+
+- **fall_time**
+
+- **off_time**
+
+See :ref:`auto_breath_mode`.
+
+Select from predefined times:
+
+.. flat-table::
+
+ * - Value
+ - Time (in seconds)
+ - Value
+ - Time (in seconds)
+
+ * - 0
+ - 0.00
+ - 8
+ - 2.1
+
+ * - 1
+ - 0.13
+ - 9
+ - 2.6
+
+ * - 2
+ - 0.26
+ - 10
+ - 3.1
+
+ * - 3
+ - 0.38
+ - 11
+ - 4.2
+
+ * - 4
+ - 0.51
+ - 12
+ - 5.2
+
+ * - 5
+ - 0.77
+ - 13
+ - 6.2
+
+ * - 6
+ - 1.04
+ - 14
+ - 7.3
+
+ * - 7
+ - 1.6
+ - 15
+ - 8.3
+
+Example set for rise-time=0.13s, on-time=0.26s,
+fall_time=6.2s, off_time=0.51s:
+
+.. code-block:: bash
+
+ echo 1 > rise_time
+ echo 2 > on_time
+ echo 13 > fall_time
+ echo 4 > off_time
+
+Maximum and minimum breathing Level
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- **max_breathing_level**
+
+- **min_breathing_level**
+
+Loop
+~~~~~
+
+- **loop_begin** - choose where to start the loop:
+
+ .. flat-table::
+
+ * - Value
+ - Description
+
+ * - 0
+ - Begin from 'rise' state
+
+ * - 1
+ - Begin from 'on' state
+
+ * - 2
+ - Begin from 'fall' state
+
+ * - 3
+ - Begin from 'off' state
+
+- **loop_end_on** - write ``1`` loop end at 'on' state.
+
+- **repeat** - loop times. When write ``0``, the loop is end-less.
+
+Others
+~~~~~~
+
+- **clear_leds** - bitmask for clear leds to pattern.
+ For example clear leds 1, 2, 3, 4, 5, 7 (``10111110``):
+
+ .. code-block:: bash
+
+ echo be > clear_leds
+
+- **select_leds** - bitmask for set leds to pattern.
+ For example select leds 0, 3, 6, 7 (``11001001``):
+
+ .. code-block:: bash
+
+ echo c9 > select_leds
+
+- **mode** - pattern mode:
+ ``0`` - manual control, ``1`` - auto breathing
+
+- **start** - start/stop pattern:
+ ``0`` - to stop, ``1`` - to start
+
+- **toggle** - manual on/off control (see :ref:`manual_breath_mode`):
+ ``0`` - LEDs off, ``1`` - LEDs on
+
+- **ramp** - the smooth ramp up/down function (see :ref:`manual_breath_mode`):
+ ``0`` - disable, ``1`` - enable
+
+- **running** - Reading this file will return the pattern state:
+ ``1`` - is running, ``0`` - is finished (or not running)
+
+This file supports poll() to detect when the pattern finished.
+
+.. _auto_breath_mode:
+
+Auto breathing mode
+~~~~~~~~~~~~~~~~~~~
+
+::
+
+ breathing level
+ ^
+ max _ ________________
+ | /. .\
+ | / . . \
+ | / . . \
+ | / . . \
+ | / . . \
+ | / . . \
+ min _ ___/ . . \_______
+ | . . . .
+ | . . . .
+ | . rise . on . fall . off
+ |
+ -|------------------------------------------------> time
+
+Example:
+
+.. code-block:: bash
+
+ echo 10 > rise_time # 3.1 seconds
+ echo 4 > on_time # 0.51 seconds
+ echo 1 > off_time # 0.13 seconds
+ echo 10 > fall_time # 3.1 seconds
+ echo 0 > min_breathing_level
+ echo 255 > max_breathing_level
+ echo 0 > loop_begin # begin from 'rise'
+ echo 0 > loop_end_on # loop end at 'off' state
+ echo 1 > mode # auto breathing mode
+ echo 5 > repeat # 5 times repeat
+ echo 1249 > select_leds # select 0, 3, 6, 9 12 leds (1001001001001)
+ echo 1 > start # run
+
+
+.. _manual_breath_mode:
+
+Manual control mode
+~~~~~~~~~~~~~~~~~~~
+
+When 'ramp' enabled (echo 1 > ramp)::
+
+ breathing level
+ ^
+ max _ ____________________
+ | / .\
+ | / . \
+ | / . \
+ | / . \
+ | / . \
+ | / . \
+ min _ ______/ . \_______
+ | . .
+ | . .
+ | . .
+ | (echo 1 > toggle) (echo 0 > toggle)
+ -|---------------------------------------------------> time
+
+
+When 'ramp' disabled (echo 0 > ramp)::
+
+ breathing level
+ ^
+ max _ __________________________
+ | | |
+ | | |
+ | | |
+ | | |
+ | | |
+ | | |
+ min _ ______| |_______
+ | . .
+ | . .
+ | . .
+ | echo 1 > toggle echo 0 > toggle
+ -|---------------------------------------------------> time
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 499d0f215a8b..66e136f43870 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -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
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 4fd2f92cd198..f611e48cd3f5 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -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
diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c
new file mode 100644
index 000000000000..273b6a44dee1
--- /dev/null
+++ b/drivers/leds/leds-aw200xx.c
@@ -0,0 +1,1113 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * leds-aw200xx.c - Awinic AW20036/AW20054/AW20072 LED driver
+ *
+ * Copyright (c) 2022, SberDevices. All Rights Reserved.
+ *
+ * Author: Martin Kurbanov <[email protected]>
+ */
+
+#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/interrupt.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_MAX 15
+#define AW200XX_IMAX_DEFAULT 4 /* 60mA */
+
+/* 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(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)
+
+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;
+ DECLARE_BITMAP(pattern_leds[AW200XX_PATTERN_MAX], AW200XX_LEDS_MAX);
+ u32 num_leds;
+ u32 imax;
+ u32 display_size;
+ struct aw200xx_led leds[];
+};
+
+struct aw200xx_chipdef {
+ u32 channels;
+ u32 display_size_max;
+ u32 display_size_columns;
+};
+
+struct aw200xx_attribute {
+ struct device_attribute dev_attr;
+ u32 reg;
+ u32 mask;
+ u32 max;
+};
+
+#define to_aw200xx_attr(attr) \
+ container_of(attr, struct aw200xx_attribute, dev_attr)
+
+#define AW200XX_ATTR(_n, _m, _sh, _st, _r, _msk, _max) { \
+ .dev_attr = __ATTR(_n, _m, _sh, _st), \
+ .reg = _r, \
+ .mask = _msk, \
+ .max = _max, \
+}
+
+#define AW200XX_DEVICE_ATTR_RW(_v, _n, _sh, _st, _r, _msk, _max) \
+struct aw200xx_attribute _v##_attr = \
+ AW200XX_ATTR(_n, 0644, _sh, _st, \
+ _r, _msk, _max)
+
+#define AW200XX_DEVICE_ATTR_RO(_v, _n, _sh, _r, _msk, _max) \
+struct aw200xx_attribute _v##_attr = \
+ AW200XX_ATTR(_n, 0444, _sh, NULL, \
+ _r, _msk, _max)
+
+static ssize_t aw200xx_store_internal(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 val;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &val);
+ if (ret < 0 || val > attr->max)
+ return -EINVAL;
+
+ val <<= __ffs(attr->mask);
+
+ mutex_lock(&chip->mutex);
+ ret = regmap_update_bits(chip->regmap, attr->reg, attr->mask, val);
+ mutex_unlock(&chip->mutex);
+
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t aw200xx_show_internal(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 val;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ ret = regmap_read(chip->regmap, attr->reg, &val);
+ mutex_unlock(&chip->mutex);
+
+ if (ret)
+ return ret;
+
+ val = (val & attr->mask) >> __ffs(attr->mask);
+
+ return sysfs_emit(buf, "%u\n", val);
+}
+
+static ssize_t aw200xx_template_time_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ static const u32 ttimes_ms[] = {
+ 0, 130, 260, 380, 510, 770, 1040, 1600,
+ 2100, 2600, 3100, 4200, 5200, 6200, 7300, 8300,
+ };
+ struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 ttime;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ ret = regmap_read(chip->regmap, attr->reg, &ttime);
+ mutex_unlock(&chip->mutex);
+
+ if (ret)
+ return ret;
+
+ ttime = (ttime & attr->mask) >> __ffs(attr->mask);
+ if (ttime >= ARRAY_SIZE(ttimes_ms))
+ return -EIO;
+
+ ttime = ttimes_ms[ttime];
+
+ /* For On & Off time minimum is 40ms */
+ if (ttime == 0 && attr->mask == AW200XX_PAT_T2_T4_MASK)
+ ttime = 40;
+
+ return sysfs_emit(buf, "%ums\n", ttime);
+}
+
+static ssize_t aw200xx_pattern_leds_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count,
+ bool clear)
+{
+ struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ unsigned long *pattern_leds = chip->pattern_leds[attr->reg];
+ u32 columns = chip->cdef->display_size_columns;
+ DECLARE_BITMAP(leds, AW200XX_LEDS_MAX);
+ u32 val = clear ? 0 : AW200XX_PATX_SELECT(attr->reg);
+ u32 i;
+ int ret;
+
+ ret = bitmap_parse(buf, count, leds, chip->num_leds);
+ if (ret)
+ return -EINVAL;
+
+ mutex_lock(&chip->mutex);
+
+ for_each_set_bit(i, leds, chip->num_leds) {
+ u32 num = chip->leds[i].num;
+
+ ret = regmap_write(chip->regmap,
+ AW200XX_REG_PAT_SELECT(num, columns), val);
+ if (ret)
+ goto exit;
+
+ if (clear)
+ __clear_bit(i, pattern_leds);
+ else
+ __set_bit(i, pattern_leds);
+ }
+
+ ret = (int)count;
+
+exit:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_select_leds_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ ret = sysfs_emit(buf, "%*pb\n",
+ chip->num_leds, chip->pattern_leds[attr->reg]);
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_select_leds_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ return aw200xx_pattern_leds_store(dev, devattr, buf, count, false);
+}
+
+static ssize_t aw200xx_pattern_clear_leds_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ DECLARE_BITMAP(leds, AW200XX_LEDS_MAX);
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ bitmap_fill(leds, chip->num_leds);
+ bitmap_xor(leds, leds, chip->pattern_leds[attr->reg], chip->num_leds);
+ ret = sysfs_emit(buf, "%*pb\n", chip->num_leds, leds);
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_clear_leds_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ return aw200xx_pattern_leds_store(dev, devattr, buf, count, true);
+}
+
+static ssize_t aw200xx_pattern_start_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 start = 0;
+ u32 val;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+
+ ret = regmap_read(chip->regmap, AW200XX_REG_PATCR, &val);
+ if (ret)
+ goto exit;
+
+ if (val & AW200XX_PATCR_PAT_ENABLE(attr->reg)) {
+ ret = regmap_read(chip->regmap, AW200XX_REG_PATGO, &val);
+ if (ret)
+ goto exit;
+
+ start = !!(val & AW200XX_PATGO(attr->reg));
+ }
+
+ ret = sysfs_emit(buf, "%u\n", start);
+
+exit:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_start_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 start;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &start);
+ if (ret < 0 || start > attr->max)
+ return -EINVAL;
+
+ start = AW200XX_PATGO_RUN(attr->reg, start);
+
+ mutex_lock(&chip->mutex);
+
+ ret = regmap_update_bits(chip->regmap, AW200XX_REG_PATCR,
+ AW200XX_PATCR_PAT_ENABLE(attr->reg), start);
+ if (ret)
+ goto exit;
+
+ ret = regmap_update_bits(chip->regmap, AW200XX_REG_PATGO,
+ AW200XX_PATGO(attr->reg), start);
+ if (!ret)
+ ret = (int)count;
+
+exit:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_running_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 running;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+
+ ret = regmap_read(chip->regmap, AW200XX_REG_PATGO, &running);
+ if (ret)
+ goto exit;
+
+ running &= AW200XX_PATGO_STATE(attr->reg);
+ ret = sysfs_emit(buf, "%u\n", !!running);
+
+exit:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_repeat_show(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 repeat_msb;
+ u32 repeat_lsb;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+
+ ret = regmap_read(chip->regmap,
+ AW200XX_REG_PATX_T2(attr->reg), &repeat_msb);
+ if (ret)
+ goto exit;
+
+ ret = regmap_read(chip->regmap,
+ AW200XX_REG_PATX_T3(attr->reg), &repeat_lsb);
+ if (ret)
+ goto exit;
+
+ repeat_msb &= AW200XX_PAT_T2_LT_MASK;
+ repeat_lsb &= AW200XX_PAT_T3_LT_MASK;
+
+ ret = sysfs_emit(buf, "%u\n",
+ AW200XX_PAT0_T_LT(repeat_msb, repeat_lsb));
+
+exit:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static ssize_t aw200xx_pattern_repeat_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct aw200xx_attribute *attr = to_aw200xx_attr(devattr);
+ struct aw200xx *chip = i2c_get_clientdata(to_i2c_client(dev));
+ u32 repeat;
+ ssize_t ret;
+
+ ret = kstrtouint(buf, 0, &repeat);
+ if (ret < 0 || repeat > attr->max)
+ return -EINVAL;
+
+ mutex_lock(&chip->mutex);
+
+ ret = regmap_update_bits(chip->regmap,
+ AW200XX_REG_PATX_T2(attr->reg),
+ AW200XX_PAT_T2_LT_MASK,
+ AW200XX_PAT0_T2_LT_MSB(repeat));
+ if (ret)
+ goto exit;
+
+ ret = regmap_update_bits(chip->regmap,
+ AW200XX_REG_PATX_T3(attr->reg),
+ AW200XX_PAT_T3_LT_MASK,
+ AW200XX_PAT0_T3_LT_LSB(repeat));
+ if (!ret)
+ ret = count;
+
+exit:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+#define AW200XX_DEVICE_ATTR_PAT_RW(_n, _sh, _st, _r, _step, _msk, _max) \
+static AW200XX_DEVICE_ATTR_RW(_n##0, _n, _sh, _st, \
+ _r, _msk, _max); \
+static AW200XX_DEVICE_ATTR_RW(_n##1, _n, _sh, _st, \
+ _r + (1 * (_step)), _msk, _max); \
+static AW200XX_DEVICE_ATTR_RW(_n##2, _n, _sh, _st, \
+ _r + (2 * (_step)), _msk, _max)
+
+#define AW200XX_DEVICE_ATTR_PAT_RO(_n, _sh, _r, _step, _msk, _max) \
+static AW200XX_DEVICE_ATTR_RO(_n##0, _n, _sh, \
+ _r, _msk, _max); \
+static AW200XX_DEVICE_ATTR_RO(_n##1, _n, _sh, \
+ _r + (1 * (_step)), _msk, _max); \
+static AW200XX_DEVICE_ATTR_RO(_n##2, _n, _sh, \
+ _r + (2 * (_step)), _msk, _max)
+
+#define AW200XX_DEFINE_ATTR_GROUP(_idx, _a0, _a1, _a2, _a3, _a4, _a5, \
+ _a6, _a7, _a8, _a9, _a10, _a11, _a12, _a13, _a14, _a15) \
+static struct attribute *aw200xx_pattern##_idx##_attributes[] = { \
+ &_a0##_idx##_attr.dev_attr.attr, \
+ &_a1##_idx##_attr.dev_attr.attr, \
+ &_a2##_idx##_attr.dev_attr.attr, \
+ &_a3##_idx##_attr.dev_attr.attr, \
+ &_a4##_idx##_attr.dev_attr.attr, \
+ &_a5##_idx##_attr.dev_attr.attr, \
+ &_a6##_idx##_attr.dev_attr.attr, \
+ &_a7##_idx##_attr.dev_attr.attr, \
+ &_a8##_idx##_attr.dev_attr.attr, \
+ &_a9##_idx##_attr.dev_attr.attr, \
+ &_a10##_idx##_attr.dev_attr.attr, \
+ &_a11##_idx##_attr.dev_attr.attr, \
+ &_a12##_idx##_attr.dev_attr.attr, \
+ &_a13##_idx##_attr.dev_attr.attr, \
+ &_a14##_idx##_attr.dev_attr.attr, \
+ &_a15##_idx##_attr.dev_attr.attr, \
+ NULL}; \
+static const struct attribute_group aw200xx_pattern##_idx##_group = { \
+ .attrs = aw200xx_pattern##_idx##_attributes, \
+ .name = "pattern"#_idx, \
+}
+
+#define AW200XX_DEFINE_ATTR_GROUPS(...) \
+AW200XX_DEFINE_ATTR_GROUP(0, __VA_ARGS__); \
+AW200XX_DEFINE_ATTR_GROUP(1, __VA_ARGS__); \
+AW200XX_DEFINE_ATTR_GROUP(2, __VA_ARGS__); \
+static const struct attribute_group *aw200xx_pattern_groups[] = { \
+ &aw200xx_pattern0_group, \
+ &aw200xx_pattern1_group, \
+ &aw200xx_pattern2_group, \
+ NULL}
+
+AW200XX_DEVICE_ATTR_PAT_RW(rise_time,
+ aw200xx_template_time_show, aw200xx_store_internal,
+ AW200XX_REG_PAT0_T0, AW200XX_PAT_T_STEP,
+ AW200XX_PAT_T1_T3_MASK, AW200XX_TEMPLATE_TIME_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(on_time,
+ aw200xx_template_time_show, aw200xx_store_internal,
+ AW200XX_REG_PAT0_T0, AW200XX_PAT_T_STEP,
+ AW200XX_PAT_T2_T4_MASK, AW200XX_TEMPLATE_TIME_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(fall_time,
+ aw200xx_template_time_show, aw200xx_store_internal,
+ AW200XX_REG_PAT0_T1, AW200XX_PAT_T_STEP,
+ AW200XX_PAT_T1_T3_MASK, AW200XX_TEMPLATE_TIME_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(off_time,
+ aw200xx_template_time_show, aw200xx_store_internal,
+ AW200XX_REG_PAT0_T1, AW200XX_PAT_T_STEP,
+ AW200XX_PAT_T2_T4_MASK, AW200XX_TEMPLATE_TIME_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(mode,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_CFG, 1,
+ AW200XX_PAT_CFG_MODE_MASK, 1);
+
+AW200XX_DEVICE_ATTR_PAT_RW(ramp,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_CFG, 1,
+ AW200XX_PAT_CFG_RAMP_MASK, 1);
+
+AW200XX_DEVICE_ATTR_PAT_RW(toggle,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_CFG, 1,
+ AW200XX_PAT_CFG_SWITCH_MASK, 1);
+
+AW200XX_DEVICE_ATTR_PAT_RW(loop_end_on,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_T2, AW200XX_PAT_T_STEP,
+ AW200XX_PAT_T2_LE_MASK, 1);
+
+AW200XX_DEVICE_ATTR_PAT_RW(loop_begin,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_T2, AW200XX_PAT_T_STEP,
+ AW200XX_PAT_T2_LB_MASK, 3);
+
+AW200XX_DEVICE_ATTR_PAT_RW(max_breathing_level,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_MAX_BREATH, 1,
+ 0xFF, AW200XX_FADE_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(min_breathing_level,
+ aw200xx_show_internal, aw200xx_store_internal,
+ AW200XX_REG_PAT0_MIN_BREATH, 1,
+ 0xFF, AW200XX_FADE_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(start,
+ aw200xx_pattern_start_show,
+ aw200xx_pattern_start_store,
+ 0, 1, 1, 1);
+
+AW200XX_DEVICE_ATTR_PAT_RO(running,
+ aw200xx_pattern_running_show, 0, 1, 0, 0);
+
+AW200XX_DEVICE_ATTR_PAT_RW(repeat,
+ aw200xx_pattern_repeat_show,
+ aw200xx_pattern_repeat_store,
+ 0, AW200XX_PAT_T_STEP,
+ 0, AW200XX_PAT0_T_LT_MAX);
+
+AW200XX_DEVICE_ATTR_PAT_RW(select_leds,
+ aw200xx_pattern_select_leds_show,
+ aw200xx_pattern_select_leds_store,
+ 0, 1, 0, 0);
+
+AW200XX_DEVICE_ATTR_PAT_RW(clear_leds,
+ aw200xx_pattern_clear_leds_show,
+ aw200xx_pattern_clear_leds_store,
+ 0, 1, 0, 0);
+
+AW200XX_DEFINE_ATTR_GROUPS(start, running, mode, ramp, toggle, repeat,
+ loop_end_on, loop_begin, select_leds, clear_leds,
+ max_breathing_level, min_breathing_level,
+ rise_time, on_time, fall_time, off_time);
+
+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 irqreturn_t aw200xx_irq_thread(int irq, void *dev_id)
+{
+ struct aw200xx *chip = dev_id;
+ unsigned long pattern_state;
+ u32 interrupt_state;
+ int i;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ ret = regmap_read(chip->regmap, AW200XX_REG_ISRFLT, &interrupt_state);
+ mutex_unlock(&chip->mutex);
+
+ if (ret) {
+ dev_err(&chip->client->dev,
+ "Failed to get interrupt status: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ pattern_state = FIELD_GET(AW200XX_ISRFLT_PATIS_MASK, interrupt_state);
+
+ for_each_set_bit(i, &pattern_state, AW200XX_PATTERN_MAX) {
+ char dir[sizeof("patternxx")];
+
+ snprintf(dir, sizeof(dir), "pattern%d", i);
+ sysfs_notify(&chip->client->dev.kobj, dir, "running");
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int aw200xx_setup_interrupts(struct aw200xx *chip)
+{
+ struct i2c_client *i2c = chip->client;
+ int ret;
+
+ if (i2c->irq <= 0)
+ return 0;
+
+ ret = devm_request_threaded_irq(&i2c->dev, i2c->irq,
+ NULL,
+ aw200xx_irq_thread,
+ IRQF_ONESHOT,
+ i2c->name,
+ chip);
+ if (ret)
+ return dev_err_probe(&i2c->dev, ret, "Failed to request irq\n");
+
+ ret = regmap_update_bits(chip->regmap, AW200XX_REG_PATCR,
+ AW200XX_PATCR_PAT_IE_MASK,
+ AW200XX_PATCR_PAT_IE_ALL);
+ if (ret)
+ dev_err_probe(&i2c->dev, ret, "Failed to enable interrupts\n");
+
+ return ret;
+}
+
+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_size);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(chip->regmap, AW200XX_REG_SLPCR,
+ AW200XX_SLPCR_ACTIVE);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(chip->regmap, AW200XX_REG_GCCR,
+ AW200XX_GCCR_IMAX(chip->imax) | 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 imax;
+ int ret;
+ int i = 0;
+
+ ret = device_property_read_u32(dev, "display-size", &chip->display_size);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to read 'display-size' property\n");
+
+ if (chip->display_size > chip->cdef->display_size_max)
+ return dev_err_probe(dev, ret,
+ "Invalid leds display size %u\n",
+ chip->display_size);
+
+ ret = device_property_read_u32(dev, "imax", &imax);
+ if (!ret && imax <= AW200XX_IMAX_MAX) {
+ chip->imax = imax;
+ } else {
+ chip->imax = AW200XX_IMAX_DEFAULT;
+ dev_info(dev, "DT property imax is missing\n");
+ }
+
+ device_for_each_child_node(dev, child) {
+ struct led_init_data init_data = {};
+ struct aw200xx_led *led;
+ u32 source;
+
+ 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;
+ }
+
+ 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)
+ ret = -EINVAL;
+
+ 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, created sysfs nodes */
+ 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);
+ if (ret)
+ goto exit;
+
+ ret = aw200xx_setup_interrupts(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_max = 2,
+ .display_size_columns = 12,
+};
+
+static const struct aw200xx_chipdef aw20054_cdef = {
+ .channels = 54,
+ .display_size_max = 5,
+ .display_size_columns = 9,
+};
+
+static const struct aw200xx_chipdef aw20072_cdef = {
+ .channels = 72,
+ .display_size_max = 5,
+ .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 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),
+ .dev_groups = aw200xx_pattern_groups,
+ },
+ .probe_new = aw200xx_probe,
+ .remove = aw200xx_remove,
+ .id_table = aw200xx_id,
+};
+
+module_i2c_driver(aw200xx_driver);
+
+MODULE_AUTHOR("Martin Kurbanov <[email protected]>");
+MODULE_DESCRIPTION("AW200XX LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:leds-aw200xx");
--
2.38.1

2022-11-25 03:30:32

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] leds: add aw20xx driver

Hi Martin,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on pavel-leds/for-next]
[also build test WARNING on robh/for-next krzk-dt/for-next linus/master v6.1-rc6 next-20221124]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Martin-Kurbanov/leds-add-aw20xx-driver/20221125-044946
base: git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git for-next
patch link: https://lore.kernel.org/r/20221124204807.1593241-3-mmkurbanov%40sberdevices.ru
patch subject: [PATCH v1 2/2] leds: add aw20xx driver
config: sparc-allyesconfig
compiler: sparc64-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/7dd31514a1ac86c2213320eef707cdaf0643ac6f
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Martin-Kurbanov/leds-add-aw20xx-driver/20221125-044946
git checkout 7dd31514a1ac86c2213320eef707cdaf0643ac6f
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=sparc SHELL=/bin/bash drivers/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>

All warnings (new ones prefixed by >>):

>> drivers/leds/leds-aw200xx.c:19: warning: expecting prototype for leds(). Prototype was for AW200XX_LEDS_MAX() instead


vim +19 drivers/leds/leds-aw200xx.c

18
> 19 #define AW200XX_LEDS_MAX 72
20 #define AW200XX_PATTERN_MAX 3
21 #define AW200XX_DIM_MAX 0x3F
22 #define AW200XX_FADE_MAX 0xFF
23 #define AW200XX_IMAX_MAX 15
24 #define AW200XX_IMAX_DEFAULT 4 /* 60mA */
25

--
0-DAY CI Kernel Test Service
https://01.org/lkp


Attachments:
(No filename) (2.19 kB)
config (328.48 kB)
Download all attachments

2022-11-25 08:53:59

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v1 1/2] dt-bindings: leds: add binding for aw200xx

On 24/11/2022 21:48, Martin Kurbanov wrote:
> Add YAML devicetree binding for AWINIC AW20036/AW20052/AW20074
> led driver.
>
> Signed-off-by: Martin Kurbanov <[email protected]>
> ---
> .../bindings/leds/leds-aw200xx.yaml | 110 ++++++++++++++++++
> include/dt-bindings/leds/leds-aw200xx.h | 48 ++++++++
> 2 files changed, 158 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/leds/leds-aw200xx.yaml
> create mode 100644 include/dt-bindings/leds/leds-aw200xx.h
>
> diff --git a/Documentation/devicetree/bindings/leds/leds-aw200xx.yaml b/Documentation/devicetree/bindings/leds/leds-aw200xx.yaml
> new file mode 100644
> index 000000000000..3bdadcbc2ee2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/leds/leds-aw200xx.yaml

Filename based on compatibles, so "awinic,aw200xx.yaml"

> @@ -0,0 +1,110 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/leds/leds-aw200xx.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: AWINIC AW200XX LED Driver

What does the "Driver" mean? Linux Driver? If yes, then drop it. Same in
other places.

> +
> +maintainers:
> + - Martin Kurbanov <[email protected]>
> +
> +description: |
> + This controller is present on AW20036/AW20054/AW20072.
> + 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.
> +
> + For more product information please see the link below:
> + aw20036 - https://www.awinic.com/Public/Uploads/uploadfile/files/20200509/20200509151532_5eb65894d205a.pdf
> + aw20054 - https://www.awinic.com/Public/Uploads/uploadfile/files/20200509/20200509151602_5eb658b2b77cb.pdf
> + aw20072 - https://www.awinic.com/Public/Uploads/uploadfile/files/20200509/20200509151754_5eb659227a145.pdf
> +
> +properties:
> + compatible:
> + enum:
> + - awinic,aw20036
> + - awinic,aw20054
> + - awinic,aw20072
> +
> + reg:
> + maxItems: 1
> +
> + "#address-cells":
> + const: 1
> +
> + "#size-cells":
> + const: 0
> +
> + interrupts:
> + maxItems: 1
> +
> + display-size:

Is it a standard property? Does not look like. Non-standard properties
need vendor prefix and type ($ref).

> + maxItems: 1
> + description:
> + Leds matrix size, see dt-bindings/leds/leds-aw200xx.h

But judging by your constants, you have the same number of columns, just
rows differ, so probably you want to describe here number of rows.

> +
> + imax:
> + maxItems: 1
> + description:
> + Maximum supply current, see dt-bindings/leds/leds-aw200xx.h

No. Use existing properties from common.yaml. This looks like
led-max-microamp and it is per LED, not per entire device.

> +
> +patternProperties:
> + "^led@[0-9a-f]$":
> + type: object
> + $ref: common.yaml#

unevaluatedProperties: false

> +
> + properties:
> + reg:
> + description:
> + LED number
> + maxItems: 1
> +
> +required:
> + - compatible
> + - reg
> + - "#address-cells"
> + - "#size-cells"
> + - display-size
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> + #include <dt-bindings/leds/common.h>
> + #include <dt-bindings/leds/leds-aw200xx.h>
> +
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + led-controller@3a {
> + compatible = "awinic,aw20036";
> + reg = <0x3a>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> + interrupt-parent = <&gpio_intc>;
> + interrupts = <13 IRQ_TYPE_LEVEL_LOW>;
> +
> + display-size = <AW20036_DSIZE_3X12>;
> + imax = <AW200XX_IMAX_60MA>;
> +
> + led@0 {
> + reg = <0x0>;
> + color = <LED_COLOR_ID_RED>;
> + };
> +
> + led@1 {
> + reg = <0x1>;
> + color = <LED_COLOR_ID_GREEN>;
> + };
> +
> + led@2 {
> + reg = <0x2>;
> + color = <LED_COLOR_ID_BLUE>;
> + };
> + };
> + };
> +
> +...
> diff --git a/include/dt-bindings/leds/leds-aw200xx.h b/include/dt-bindings/leds/leds-aw200xx.h
> new file mode 100644
> index 000000000000..6b2ba4c3c6b1
> --- /dev/null
> +++ b/include/dt-bindings/leds/leds-aw200xx.h
> @@ -0,0 +1,48 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */

Dual license, like bindings.

> +/**
> + * This header provides constants for aw200xx LED bindings.
> + *
> + * Copyright (c) 2022, SberDevices. All Rights Reserved.
> + *
> + * Author: Martin Kurbanov <[email protected]>
> + */
> +#ifndef _DT_BINDINGS_LEDS_AW200XX_H
> +#define _DT_BINDINGS_LEDS_AW200XX_H
> +
> +/* Global max current (IMAX) */
> +#define AW200XX_IMAX_3_3MA 8
> +#define AW200XX_IMAX_6_7MA 9

No. Bindings are not for storing register constants. Feel free to store
here IDs (ID start from 0 or 1 and is incremented by 1)... but how the
IMAX even matches any need for "ID"?

> +#define AW200XX_IMAX_10MA 0
> +#define AW200XX_IMAX_13_3MA 11
> +#define AW200XX_IMAX_20MA 1
> +#define AW200XX_IMAX_26_7MA 13
> +#define AW200XX_IMAX_30MA 2
> +#define AW200XX_IMAX_40MA 3
> +#define AW200XX_IMAX_53_3MA 15
> +#define AW200XX_IMAX_60MA 4
> +#define AW200XX_IMAX_80MA 5
> +#define AW200XX_IMAX_120MA 6
> +#define AW200XX_IMAX_160MA 7
> +
> +/* Display size for aw20036 */
> +#define AW20036_DSIZE_1X12 0
> +#define AW20036_DSIZE_2X12 1
> +#define AW20036_DSIZE_3X12 2
> +
> +/* Display size for aw20054 */
> +#define AW20054_DSIZE_1X9 0
> +#define AW20054_DSIZE_2X9 1
> +#define AW20054_DSIZE_3X9 2
> +#define AW20054_DSIZE_4X9 3
> +#define AW20054_DSIZE_5X9 4
> +#define AW20054_DSIZE_6X9 5
> +
> +/* Display size for aw20072 */
> +#define AW20072_DSIZE_1X12 0
> +#define AW20072_DSIZE_2X12 1
> +#define AW20072_DSIZE_3X12 2
> +#define AW20072_DSIZE_4X12 3
> +#define AW20072_DSIZE_5X12 4
> +#define AW20072_DSIZE_6X12 5

Drop all constants and instead use number of rows without specifying it
in binding.

So in total entire file can be dropped.

> +
> +#endif /* !_DT_BINDINGS_LEDS_AW200XX_H */

Best regards,
Krzysztof

2022-11-25 09:11:56

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] leds: add aw20xx driver

On 24/11/2022 21:48, Martin Kurbanov wrote:
> This commit adds support for AWINIC AW20036/AW20054/AW20072 LED driver.
> This driver supports following AW200XX features:
> - 3 pattern controllers for auto breathing or group dimming control
> - Individual 64-level DIM currents
> - Interrupt output, low active
>
> Signed-off-by: Martin Kurbanov <[email protected]>
> ---
> Documentation/leds/leds-aw200xx.rst | 274 +++++++
> drivers/leds/Kconfig | 10 +
> drivers/leds/Makefile | 1 +
> drivers/leds/leds-aw200xx.c | 1113 +++++++++++++++++++++++++++
> 4 files changed, 1398 insertions(+)
> create mode 100644 Documentation/leds/leds-aw200xx.rst
> create mode 100644 drivers/leds/leds-aw200xx.c
>
> diff --git a/Documentation/leds/leds-aw200xx.rst b/Documentation/leds/leds-aw200xx.rst
> new file mode 100644
> index 000000000000..a751b91dfda6
> --- /dev/null
> +++ b/Documentation/leds/leds-aw200xx.rst
> @@ -0,0 +1,274 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +=========================================
> +Kernel driver for AW20036/AW20054/AW20072
> +=========================================
> +
> +Description
> +-----------
> +
> +The AW20036/AW20054/AW20072 is a 3x12/6x9/6x12 matrix LED driver programmed via
> +an I2C interface. The brightness of each LED is independently controlled by
> +FADE and DIM parameter.
> +
> +Three integrated pattern controllers provide auto breathing or group dimming
> +control. Each pattern controller can work in auto breathing or manual control
> +mode. All breathing parameters including rising/falling slope, on/off time,
> +repeat times, min/max brightness and so on are configurable.
> +
> +Device attribute
> +-----------------------------------
> +
> +**/sys/class/leds/<led>/dim** - 64-level DIM current. If write negative value
> +or "auto", the dim will be calculated according to the brightness.
> +
> +The configuration files for each pattern are located::
> +
> + /sys/bus/i2c/devices/xxxx/pattern0/
> + /sys/bus/i2c/devices/xxxx/pattern1/
> + /sys/bus/i2c/devices/xxxx/pattern2/
> +
> +Directory layout example for pattern
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +::
> +
> + $ ls -l /sys/bus/i2c/devices/xxxx/pattern0/
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 clear_leds
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 fall_time
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 loop_begin
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 loop_end_on
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 max_breathing_level
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 min_breathing_level
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 mode
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 off_time
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 on_time
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 ramp
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 repeat
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 rise_time
> + -r--r--r-- 1 root root 4096 Jan 1 00:00 running
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 select_leds
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 start
> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 toggle

sysfs documentation goes to Documentation/ABI/


(...)

> +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),

sizeof(*chip)

> + 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, created sysfs nodes */
> + 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);
> + if (ret)
> + goto exit;
> +
> + ret = aw200xx_setup_interrupts(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_max = 2,
> + .display_size_columns = 12,
> +};
> +
> +static const struct aw200xx_chipdef aw20054_cdef = {
> + .channels = 54,
> + .display_size_max = 5,
> + .display_size_columns = 9,
> +};
> +
> +static const struct aw200xx_chipdef aw20072_cdef = {
> + .channels = 72,
> + .display_size_max = 5,
> + .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 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),

You will have warning now. of_match_ptr goes with __maybe_unused. Drop it.

> + .dev_groups = aw200xx_pattern_groups,
> + },
> + .probe_new = aw200xx_probe,
> + .remove = aw200xx_remove,
> + .id_table = aw200xx_id,
> +};
> +
> +module_i2c_driver(aw200xx_driver);
> +
> +MODULE_AUTHOR("Martin Kurbanov <[email protected]>");
> +MODULE_DESCRIPTION("AW200XX LED driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:leds-aw200xx");

Best regards,
Krzysztof

2022-11-25 19:48:13

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] leds: add aw20xx driver

Hi Martin,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on pavel-leds/for-next]
[also build test WARNING on robh/for-next krzk-dt/for-next linus/master v6.1-rc6 next-20221125]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Martin-Kurbanov/leds-add-aw20xx-driver/20221125-044946
base: git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git for-next
patch link: https://lore.kernel.org/r/20221124204807.1593241-3-mmkurbanov%40sberdevices.ru
patch subject: [PATCH v1 2/2] leds: add aw20xx driver
reproduce:
# https://github.com/intel-lab-lkp/linux/commit/7dd31514a1ac86c2213320eef707cdaf0643ac6f
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Martin-Kurbanov/leds-add-aw20xx-driver/20221125-044946
git checkout 7dd31514a1ac86c2213320eef707cdaf0643ac6f
make menuconfig
# enable CONFIG_COMPILE_TEST, CONFIG_WARN_MISSING_DOCUMENTS, CONFIG_WARN_ABI_ERRORS
make htmldocs

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>

All warnings (new ones prefixed by >>):

>> Documentation/leds/leds-aw200xx.rst: WARNING: document isn't included in any toctree

--
0-DAY CI Kernel Test Service
https://01.org/lkp


Attachments:
(No filename) (1.54 kB)
config (39.55 kB)
Download all attachments

2022-11-28 18:33:55

by Martin Kurbanov

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] leds: add aw20xx driver

On 25.11.2022 11:19, Krzysztof Kozlowski wrote:
>> + $ ls -l /sys/bus/i2c/devices/xxxx/pattern0/
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 clear_leds
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 fall_time
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 loop_begin
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 loop_end_on
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 max_breathing_level
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 min_breathing_level
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 mode
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 off_time
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 on_time
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 ramp
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 repeat
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 rise_time
>> + -r--r--r-- 1 root root 4096 Jan 1 00:00 running
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 select_leds
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 start
>> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 toggle
>
> sysfs documentation goes to Documentation/ABI/

Should I add it to Documentation/ABI/testing/sysfs-class-led-driver-aw200xx?
Is my understanding correct?
Is KernelVersion parameter required?


>> + chip = devm_kzalloc(&client->dev,
>> + struct_size(chip, leds, count),
>
> sizeof(*chip)

Unfortunately, it will not work. Because I want to calculate the whole
size of chip structure, it has flexible array member inside.

--
Best Regards,
Kurbanov Martin

2022-11-28 18:36:34

by Martin Kurbanov

[permalink] [raw]
Subject: Re: [PATCH v1 1/2] dt-bindings: leds: add binding for aw200xx

Hi. Thank you for quick reply.

On 25.11.2022 11:29, Krzysztof Kozlowski wrote:
>> +
>> + imax:
>> + maxItems: 1
>> + description:
>> + Maximum supply current, see dt-bindings/leds/leds-aw200xx.h
>
> No. Use existing properties from common.yaml. This looks like
> led-max-microamp and it is per LED, not per entire device.

The AW200XX LED chip does not support imax setup per led.
Imax is the global parameter over the all leds. I suppose, it's better
to add vendor prefix or take minimum from all subnodes?
How do you think?


>> +/* Global max current (IMAX) */
>> +#define AW200XX_IMAX_3_3MA 8
>> +#define AW200XX_IMAX_6_7MA 9
>
> No. Bindings are not for storing register constants. Feel free to store
> here IDs (ID start from 0 or 1 and is incremented by 1)... but how the
> IMAX even matches any need for "ID"?

IMAX can be chosen from the predefined values in the
datasheet (10mA, 20mA, etc). Do you mean the IMAX should be round down
to nearest supported value in the driver?

--
Best Regards,
Kurbanov Martin

2022-12-02 17:04:56

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v1 1/2] dt-bindings: leds: add binding for aw200xx

On 28/11/2022 18:43, Martin Kurbanov wrote:
> Hi. Thank you for quick reply.
>
> On 25.11.2022 11:29, Krzysztof Kozlowski wrote:
>>> +
>>> + imax:
>>> + maxItems: 1
>>> + description:
>>> + Maximum supply current, see dt-bindings/leds/leds-aw200xx.h
>>
>> No. Use existing properties from common.yaml. This looks like
>> led-max-microamp and it is per LED, not per entire device.
>
> The AW200XX LED chip does not support imax setup per led.
> Imax is the global parameter over the all leds. I suppose, it's better
> to add vendor prefix or take minimum from all subnodes?
> How do you think?

Have in mind that led-max-microamp is a required property in some cases,
so skipping it and using per-device properties does not solve the
problem of adjusting proper currents. What if each LED you set for
something which in total gives more than your imax?

>
>
>>> +/* Global max current (IMAX) */
>>> +#define AW200XX_IMAX_3_3MA 8
>>> +#define AW200XX_IMAX_6_7MA 9
>>
>> No. Bindings are not for storing register constants. Feel free to store
>> here IDs (ID start from 0 or 1 and is incremented by 1)... but how the
>> IMAX even matches any need for "ID"?
>
> IMAX can be chosen from the predefined values in the
> datasheet (10mA, 20mA, etc). Do you mean the IMAX should be round down
> to nearest supported value in the driver?

What Linux driver support does not matter here. Bindings should reflect
hardware and the same time not store register constants but logical
values (for current this is in uA).

Best regards,
Krzysztof

2022-12-02 20:05:49

by Dmitry Rokosov

[permalink] [raw]
Subject: Re: [PATCH v1 1/2] dt-bindings: leds: add binding for aw200xx

Hello Krzysztof,

On Fri, Dec 02, 2022 at 05:41:37PM +0100, Krzysztof Kozlowski wrote:
> On 28/11/2022 18:43, Martin Kurbanov wrote:
> > Hi. Thank you for quick reply.
> >
> > On 25.11.2022 11:29, Krzysztof Kozlowski wrote:
> >>> +
> >>> + imax:
> >>> + maxItems: 1
> >>> + description:
> >>> + Maximum supply current, see dt-bindings/leds/leds-aw200xx.h
> >>
> >> No. Use existing properties from common.yaml. This looks like
> >> led-max-microamp and it is per LED, not per entire device.
> >
> > The AW200XX LED chip does not support imax setup per led.
> > Imax is the global parameter over the all leds. I suppose, it's better
> > to add vendor prefix or take minimum from all subnodes?
> > How do you think?
>
> Have in mind that led-max-microamp is a required property in some cases,
> so skipping it and using per-device properties does not solve the
> problem of adjusting proper currents. What if each LED you set for
> something which in total gives more than your imax?
>

You are right. From my point of view too, we must build our solutions from
HW capabilities. In the current situation, AW200XX chips support global
Imax value, so it's acceptable decision to use vendor prefix for global
imax parameter, why not?

...

--
Thank you,
Dmitry

2022-12-03 11:19:04

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v1 1/2] dt-bindings: leds: add binding for aw200xx

On 02/12/2022 19:53, Dmitry Rokosov wrote:
> Hello Krzysztof,
>
> On Fri, Dec 02, 2022 at 05:41:37PM +0100, Krzysztof Kozlowski wrote:
>> On 28/11/2022 18:43, Martin Kurbanov wrote:
>>> Hi. Thank you for quick reply.
>>>
>>> On 25.11.2022 11:29, Krzysztof Kozlowski wrote:
>>>>> +
>>>>> + imax:
>>>>> + maxItems: 1
>>>>> + description:
>>>>> + Maximum supply current, see dt-bindings/leds/leds-aw200xx.h
>>>>
>>>> No. Use existing properties from common.yaml. This looks like
>>>> led-max-microamp and it is per LED, not per entire device.
>>>
>>> The AW200XX LED chip does not support imax setup per led.
>>> Imax is the global parameter over the all leds. I suppose, it's better
>>> to add vendor prefix or take minimum from all subnodes?
>>> How do you think?
>>
>> Have in mind that led-max-microamp is a required property in some cases,
>> so skipping it and using per-device properties does not solve the
>> problem of adjusting proper currents. What if each LED you set for
>> something which in total gives more than your imax?
>>
>
> You are right. From my point of view too, we must build our solutions from
> HW capabilities.

And there was no proposal to go around HW capabilities. We talk only
about representation.

> In the current situation, AW200XX chips support global
> Imax value, so it's acceptable decision to use vendor prefix for global
> imax parameter, why not?

Jacek made his statement some time ago quite clear:

https://lore.kernel.org/all/[email protected]/

"If you question the idea of having different maximum brightness per
sub-LEDs controlled by the same device, then it means that you have
objections to the entire idea of LED subsystem max_brightness property,
whereas it has been broadly accepted and successfully used for years."

Best regards,
Krzysztof

2022-12-07 20:16:46

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] leds: add aw20xx driver

On Mon 2022-11-28 17:45:22, Martin Kurbanov wrote:
> On 25.11.2022 11:19, Krzysztof Kozlowski wrote:
> >> + $ ls -l /sys/bus/i2c/devices/xxxx/pattern0/
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 clear_leds
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 fall_time
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 loop_begin
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 loop_end_on
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 max_breathing_level
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 min_breathing_level
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 mode
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 off_time
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 on_time
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 ramp
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 repeat
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 rise_time
> >> + -r--r--r-- 1 root root 4096 Jan 1 00:00 running
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 select_leds
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 start
> >> + -rw-r--r-- 1 root root 4096 Jan 1 00:00 toggle
> >
> > sysfs documentation goes to Documentation/ABI/
>
> Should I add it to Documentation/ABI/testing/sysfs-class-led-driver-aw200xx?
> Is my understanding correct?
> Is KernelVersion parameter required?

You really should take a look at pattern trigger. You should probably
use same API as it does, perhaps documenting which patterns this
particular driver can do in hardware.

Best regards,
Pavel
--
People of Russia, stop Putin before his war on Ukraine escalates.


Attachments:
(No filename) (1.86 kB)
signature.asc (201.00 B)
Download all attachments

2022-12-07 20:20:46

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] leds: add aw20xx driver

On Thu 2022-11-24 23:48:07, Martin Kurbanov wrote:
> This commit adds support for AWINIC AW20036/AW20054/AW20072 LED driver.
> This driver supports following AW200XX features:
> - 3 pattern controllers for auto breathing or group dimming control
> - Individual 64-level DIM currents
> - Interrupt output, low active

You may want to submit driver without the hardware pattern support,
first.

Best regards,
Pavel
--
People of Russia, stop Putin before his war on Ukraine escalates.


Attachments:
(No filename) (510.00 B)
signature.asc (201.00 B)
Download all attachments

2022-12-31 16:03:55

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v1 2/2] leds: add aw20xx driver

Hi Martin,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on pavel-leds/for-next]
[also build test WARNING on robh/for-next krzk-dt/for-next linus/master v6.2-rc1 next-20221226]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/Martin-Kurbanov/leds-add-aw20xx-driver/20221125-044946
base: git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git for-next
patch link: https://lore.kernel.org/r/20221124204807.1593241-3-mmkurbanov%40sberdevices.ru
patch subject: [PATCH v1 2/2] leds: add aw20xx driver
config: x86_64-randconfig-a013-20211213-CONFIG_RCU_TORTURE_TEST
compiler: gcc-11 (Debian 11.3.0-8) 11.3.0
reproduce (this is a W=1 build):
# https://github.com/intel-lab-lkp/linux/commit/7dd31514a1ac86c2213320eef707cdaf0643ac6f
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Martin-Kurbanov/leds-add-aw20xx-driver/20221125-044946
git checkout 7dd31514a1ac86c2213320eef707cdaf0643ac6f
# save the config file
mkdir build_dir && cp config build_dir/.config
make W=1 O=build_dir ARCH=x86_64 olddefconfig
make W=1 O=build_dir ARCH=x86_64 SHELL=/bin/bash drivers/leds/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>

All warnings (new ones prefixed by >>):

>> drivers/leds/leds-aw200xx.c:1089:34: warning: 'aw200xx_match_table' defined but not used [-Wunused-const-variable=]
1089 | static const struct of_device_id aw200xx_match_table[] = {
| ^~~~~~~~~~~~~~~~~~~


vim +/aw200xx_match_table +1089 drivers/leds/leds-aw200xx.c

1088
> 1089 static const struct of_device_id aw200xx_match_table[] = {
1090 { .compatible = "awinic,aw20036", .data = &aw20036_cdef, },
1091 { .compatible = "awinic,aw20054", .data = &aw20054_cdef, },
1092 { .compatible = "awinic,aw20072", .data = &aw20072_cdef, },
1093 {},
1094 };
1095 MODULE_DEVICE_TABLE(of, aw200xx_match_table);
1096

--
0-DAY CI Kernel Test Service
https://01.org/lkp


Attachments:
(No filename) (2.33 kB)
config (150.09 kB)
Download all attachments