mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/2] backlight: Add Awinic AW99703 driver
@ 2026-10-16 16:29 Luka Panio
  2026-10-16 16:29 ` [PATCH 1/2] dt-bindings: leds: backlight: Add Awinic AW99703 backlight Luka Panio
                   ` (2 more replies)
  0 siblings, 3 replies; 9+ messages in thread
From: Luka Panio @ 2026-10-16 16:29 UTC (permalink / raw)
  To: lee, danielt, jingoohan1, pavel, robh, krzk+dt, conor+dt, deller
  Cc: dri-devel, linux-leds, devicetree, linux-fbdev, linux-kernel, Luka Panio

The Awinic AW99703 is a high-efficiency 3-channel WLED driver with an
integrated boost converter and 11-bit brightness control over I2C,
optionally combined with a PWM input. It is used as an LCD backlight
driver in some mobile devices.

This series adds the devicetree binding and a driver for it.

Tested on the Nexphone by Nex Computers.

A board DTS using it will follow separately.

Luka Panio (2):
  dt-bindings: leds: backlight: Add Awinic AW99703 backlight
  backlight: Add Awinic AW99703 backlight driver

 .../leds/backlight/awinic,aw99703.yaml        | 131 ++++
 MAINTAINERS                                   |   6 +
 drivers/video/backlight/Kconfig               |  11 +
 drivers/video/backlight/Makefile              |   1 +
 drivers/video/backlight/aw99703.c             | 583 ++++++++++++++++++
 5 files changed, 732 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/backlight/awinic,aw99703.yaml
 create mode 100644 drivers/video/backlight/aw99703.c


base-commit: d419d682257d813f6cb7a28e42b35d0edf4ad038
-- 
2.55.0


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/2] dt-bindings: leds: backlight: Add Awinic AW99703 backlight
  2026-10-16 16:29 [PATCH 0/2] backlight: Add Awinic AW99703 driver Luka Panio
@ 2026-10-16 16:29 ` Luka Panio
  2026-10-19  7:02   ` Krzysztof Kozlowski
  2026-10-16 16:29 ` [PATCH 2/2] backlight: Add Awinic AW99703 backlight driver Luka Panio
  2026-10-19 11:26 ` [PATCH 0/2] backlight: Add Awinic AW99703 driver Luka Panio
  2 siblings, 1 reply; 9+ messages in thread
From: Luka Panio @ 2026-10-16 16:29 UTC (permalink / raw)
  To: lee, danielt, jingoohan1, pavel, robh, krzk+dt, conor+dt, deller
  Cc: dri-devel, linux-leds, devicetree, linux-fbdev, linux-kernel, Luka Panio

The Awinic AW99703 is a 3-string WLED driver converter with an
integrated boost converter and 11-bit brightness control over I2C,
optionally combined with a PWM input.

Signed-off-by: Luka Panio <lukapanio@gmail.com>
---
 .../leds/backlight/awinic,aw99703.yaml        | 131 ++++++++++++++++++
 1 file changed, 131 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/backlight/awinic,aw99703.yaml

diff --git a/Documentation/devicetree/bindings/leds/backlight/awinic,aw99703.yaml b/Documentation/devicetree/bindings/leds/backlight/awinic,aw99703.yaml
new file mode 100644
index 000000000000..62e8cc3118f2
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/backlight/awinic,aw99703.yaml
@@ -0,0 +1,131 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/backlight/awinic,aw99703.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Awinic AW99703 WLED Backlight Driver
+
+maintainers:
+  - Luka Panio <lukapanio@gmail.com>
+
+description:
+  The AW99703 is a 3-string WLED driver with an integrated boost converter
+  and 11-bit I2C (optionally PWM) brightness control.
+
+allOf:
+  - $ref: common.yaml#
+
+properties:
+  compatible:
+    const: awinic,aw99703
+
+  reg:
+    maxItems: 1
+
+  enable-gpios:
+    description: GPIO connected to the HWEN pin.
+    maxItems: 1
+
+  awinic,led-strings:
+    description: Number of enabled LED strings, starting with CH1.
+    $ref: /schemas/types.yaml#/definitions/uint32
+    minimum: 1
+    maximum: 3
+    default: 3
+
+  awinic,iled-max-microamp:
+    description:
+      Full-scale current of each LED string, reached at the maximum
+      brightness code (and 100% PWM duty cycle if PWM dimming is used).
+    minimum: 4800
+    maximum: 29600
+    multipleOf: 800
+    default: 20000
+
+  awinic,ramp-on-time-us:
+    description: Current sink ramp-up time when leaving standby.
+    enum: [512, 1000, 2000, 4000, 8000, 16000, 32000, 64000, 128000,
+           256000, 512000, 1024000, 2048000, 4096000, 8192000, 16384000]
+    default: 8000
+
+  awinic,ramp-off-time-us:
+    description: Current sink ramp-down time when entering standby.
+    enum: [512, 1000, 2000, 4000, 8000, 16000, 32000, 64000, 128000,
+           256000, 512000, 1024000, 2048000, 4096000, 8192000, 16384000]
+    default: 8000
+
+  awinic,pwm-trans-time-ms:
+    description:
+      Transition time between brightness levels set by the PWM input,
+      for a change over the full duty cycle range.
+    enum: [2, 4, 8, 16, 32, 64, 128, 256]
+    default: 2
+
+  awinic,i2c-trans-time-ms:
+    description:
+      Fixed transition time between brightness levels set over I2C,
+      independent of the size of the change. When absent the brightness
+      ramps with a fixed slope of 1 us per brightness code instead.
+    enum: [128, 192, 256, 320, 384, 448, 512, 576, 640, 768, 1024, 1280,
+           1536, 2560, 4096]
+
+  awinic,ovp-microvolt:
+    description: Boost over-voltage protection threshold.
+    enum: [17500000, 24000000, 31000000, 38000000, 41500000]
+    default: 38000000
+
+  awinic,ocp-microamp:
+    description: Boost over-current protection threshold.
+    enum: [900000, 1800000, 2700000, 3400000]
+    default: 2700000
+
+  awinic,sw-freq-hz:
+    description: Boost switching frequency.
+    enum: [500000, 1000000]
+    default: 1000000
+
+  awinic,inductor-nanohenry:
+    description: Boost inductor value.
+    $ref: /schemas/types.yaml#/definitions/uint32
+    enum: [4700, 10000]
+    default: 4700
+
+  awinic,pwm-input:
+    description: Combine the PWM input pin with the I2C brightness.
+    type: boolean
+
+  awinic,exponential-map:
+    description: Use the exponential brightness curve instead of linear.
+    type: boolean
+
+  default-brightness:
+    maximum: 2047
+
+  max-brightness:
+    maximum: 2047
+
+required:
+  - compatible
+  - reg
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        backlight@36 {
+            compatible = "awinic,aw99703";
+            reg = <0x36>;
+            enable-gpios = <&gpio 7 GPIO_ACTIVE_HIGH>;
+            awinic,led-strings = <2>;
+            awinic,iled-max-microamp = <20000>;
+            awinic,ocp-microamp = <3400000>;
+            awinic,inductor-nanohenry = <10000>;
+        };
+    };
-- 
2.55.0


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 2/2] backlight: Add Awinic AW99703 backlight driver
  2026-10-16 16:29 [PATCH 0/2] backlight: Add Awinic AW99703 driver Luka Panio
  2026-10-16 16:29 ` [PATCH 1/2] dt-bindings: leds: backlight: Add Awinic AW99703 backlight Luka Panio
@ 2026-10-16 16:29 ` Luka Panio
  2026-10-19 10:51   ` Daniel Thompson
  2026-10-19 11:26 ` [PATCH 0/2] backlight: Add Awinic AW99703 driver Luka Panio
  2 siblings, 1 reply; 9+ messages in thread
From: Luka Panio @ 2026-10-16 16:29 UTC (permalink / raw)
  To: lee, danielt, jingoohan1, pavel, robh, krzk+dt, conor+dt, deller
  Cc: dri-devel, linux-leds, devicetree, linux-fbdev, linux-kernel, Luka Panio

Add a driver for the Awinic AW99703 3-string WLED backlight controller.

Register values follow the AW99703 datasheet (V1.6), the structure
follows the aw99706 driver.

Signed-off-by: Luka Panio <lukapanio@gmail.com>
---
 MAINTAINERS                       |   6 +
 drivers/video/backlight/Kconfig   |  11 +
 drivers/video/backlight/Makefile  |   1 +
 drivers/video/backlight/aw99703.c | 583 ++++++++++++++++++++++++++++++
 4 files changed, 601 insertions(+)
 create mode 100644 drivers/video/backlight/aw99703.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 461a56850c84..e4168448e95c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4525,6 +4525,12 @@ L:	linux-sound@vger.kernel.org
 S:	Maintained
 F:	sound/hda/codecs/side-codecs/aw88399*
 
+AWINIC AW99703 WLED BACKLIGHT DRIVER
+M:	Luka Panio <lukapanio@gmail.com>
+S:	Maintained
+F:	Documentation/devicetree/bindings/leds/backlight/awinic,aw99703.yaml
+F:	drivers/video/backlight/aw99703.c
+
 AWINIC AW99706 WLED BACKLIGHT DRIVER
 M:	Junjie Cao <junjie.cao@linux.dev>
 S:	Maintained
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index dbf4ca23a9b6..c2494f7fa5d2 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -156,6 +156,17 @@ config BACKLIGHT_ATMEL_LCDC
 	  If in doubt, it's safe to enable this option; it doesn't kick
 	  in unless the board's description says it's wired that way.
 
+config BACKLIGHT_AW99703
+	tristate "Backlight Driver for Awinic AW99703"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  If you have a LCD backlight connected to the WLED outputs of an
+	  Awinic AW99703, say Y here to enable this driver.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called aw99703.
+
 config BACKLIGHT_AW99706
 	tristate "Backlight Driver for Awinic AW99706"
 	depends on I2C
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 34469711c6cd..09ec9bd71c92 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_BACKLIGHT_ADP8870)		+= adp8870_bl.o
 obj-$(CONFIG_BACKLIGHT_APPLE)		+= apple_bl.o
 obj-$(CONFIG_BACKLIGHT_APPLE_DWI)	+= apple_dwi_bl.o
 obj-$(CONFIG_BACKLIGHT_AS3711)		+= as3711_bl.o
+obj-$(CONFIG_BACKLIGHT_AW99703)		+= aw99703.o
 obj-$(CONFIG_BACKLIGHT_AW99706)		+= aw99706.o
 obj-$(CONFIG_BACKLIGHT_BD6107)		+= bd6107.o
 obj-$(CONFIG_BACKLIGHT_CGBC)		+= cgbc_bl.o
diff --git a/drivers/video/backlight/aw99703.c b/drivers/video/backlight/aw99703.c
new file mode 100644
index 000000000000..b0a46753d176
--- /dev/null
+++ b/drivers/video/backlight/aw99703.c
@@ -0,0 +1,583 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * aw99703 - Backlight driver for the AWINIC AW99703
+ *
+ * Copyright (C) 2026 Luka Panio <lukapanio@gmail.com>
+ *
+ * Register layout and init sequence based on the AW99703 datasheet (V1.6)
+ */
+#include <linux/backlight.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+
+#define AW99703_MAX_BRT_LVL		2047
+#define AW99703_ID			0x03
+
+#define AW99703_REG_ID			0x00
+#define AW99703_REG_SFTRST		0x01
+#define AW99703_REG_MODE		0x02
+#define AW99703_PDIS_MASK		BIT(4)
+#define AW99703_MAP_LINEAR_MASK		BIT(2)
+#define AW99703_WORKMODE_MASK		GENMASK(1, 0)
+#define AW99703_WORKMODE_STANDBY	0
+#define AW99703_WORKMODE_BACKLIGHT	1
+#define AW99703_REG_LEDCUR		0x03
+#define AW99703_BLFS_MASK		GENMASK(7, 3)
+#define AW99703_CHANNEL_MASK		GENMASK(2, 0)
+#define AW99703_REG_BSTCTR1		0x04
+#define AW99703_SW_FREQ_MASK		BIT(5)
+#define AW99703_OVPSEL_MASK		GENMASK(4, 2)
+#define AW99703_OCPSEL_MASK		GENMASK(1, 0)
+#define AW99703_REG_BSTCTR2		0x05
+#define AW99703_IDCTSEL_MASK		BIT(6)
+#define AW99703_REG_LEDLSB		0x06
+#define AW99703_BRT_LSB_MASK		GENMASK(2, 0)
+#define AW99703_REG_LEDMSB		0x07
+#define AW99703_REG_PWM			0x08
+#define AW99703_REG_TURNCFG		0x09
+#define AW99703_RAMP_ON_MASK		GENMASK(7, 4)
+#define AW99703_RAMP_OFF_MASK		GENMASK(3, 0)
+#define AW99703_REG_TRANCFG		0x0A
+#define AW99703_PWM_TRANS_MASK		GENMASK(6, 4)
+#define AW99703_I2C_TRANS_MASK		GENMASK(3, 0)
+#define AW99703_REG_FLASH		0x0B
+#define AW99703_REG_AFHIGH		0x0C
+#define AW99703_REG_AFLOW		0x0D
+#define AW99703_REG_FLAGS1		0x0E
+#define AW99703_REG_FLAGS2		0x0F
+#define AW99703_FLAGS2_MASK		GENMASK(7, 5)
+#define AW99703_REG_FLAGS3		0x11
+#define AW99703_REG_MAX			0x69
+
+#define AW99703_HWEN_ON_DELAY_US	3500
+#define AW99703_ID_RETRIES		5
+
+#define RESV	U32_MAX
+
+/* Boost switching frequency, in Hz */
+static const u32 aw99703_sw_freq_tbl[] = { 500000, 1000000 };
+
+/* Boost over-voltage protection threshold, in uV (1xx = 41.5 V) */
+static const u32 aw99703_ovp_tbl[] = {
+	17500000, 24000000, 31000000, 38000000, 41500000, RESV, RESV, RESV
+};
+
+/* Boost over-current protection threshold, in uA */
+static const u32 aw99703_ocp_tbl[] = { 900000, 1800000, 2700000, 3400000 };
+
+/* Boost inductor, in nH */
+static const u32 aw99703_inductor_tbl[] = { 4700, 10000 };
+
+/* Current sink turn on/off ramp time, in us */
+static const u32 aw99703_ramp_tbl[] = {
+	512, 1000, 2000, 4000, 8000, 16000, 32000, 64000, 128000, 256000,
+	512000, 1024000, 2048000, 4096000, 8192000, 16384000
+};
+
+/* PWM dimming transition time, in ms */
+static const u32 aw99703_pwm_trans_tbl[] = {
+	2, 4, 8, 16, 32, 64, 128, 256
+};
+
+/* I2C dimming transition time, in ms (code 0: 1 us per brightness step) */
+static const u32 aw99703_i2c_trans_tbl[] = {
+	RESV, 128, 192, 256, 320, 384, 448, 512, 576, 640, 768, 1024, 1280,
+	1536, 2560, 4096
+};
+
+struct aw99703_dt_prop {
+	const char * const name;
+	const u32 * const lookup_tbl;
+	u8 tbl_size;
+	u32 min;
+	u32 step;
+	u8 reg;
+	u8 mask;
+	u8 def_code;
+};
+
+static const struct aw99703_dt_prop aw99703_dt_props[] = {
+	{
+		.name = "awinic,sw-freq-hz",
+		.lookup_tbl = aw99703_sw_freq_tbl,
+		.tbl_size = ARRAY_SIZE(aw99703_sw_freq_tbl),
+		.reg = AW99703_REG_BSTCTR1,
+		.mask = AW99703_SW_FREQ_MASK,
+		.def_code = 1,
+	},
+	{
+		.name = "awinic,ovp-microvolt",
+		.lookup_tbl = aw99703_ovp_tbl,
+		.tbl_size = ARRAY_SIZE(aw99703_ovp_tbl),
+		.reg = AW99703_REG_BSTCTR1,
+		.mask = AW99703_OVPSEL_MASK,
+		.def_code = 3,
+	},
+	{
+		.name = "awinic,ocp-microamp",
+		.lookup_tbl = aw99703_ocp_tbl,
+		.tbl_size = ARRAY_SIZE(aw99703_ocp_tbl),
+		.reg = AW99703_REG_BSTCTR1,
+		.mask = AW99703_OCPSEL_MASK,
+		.def_code = 2,
+	},
+	{
+		.name = "awinic,inductor-nanohenry",
+		.lookup_tbl = aw99703_inductor_tbl,
+		.tbl_size = ARRAY_SIZE(aw99703_inductor_tbl),
+		.reg = AW99703_REG_BSTCTR2,
+		.mask = AW99703_IDCTSEL_MASK,
+		.def_code = 0,
+	},
+	{
+		.name = "awinic,iled-max-microamp",
+		.min = 4800,
+		.step = 800,
+		.reg = AW99703_REG_LEDCUR,
+		.mask = AW99703_BLFS_MASK,
+		.def_code = 19,
+	},
+	{
+		.name = "awinic,ramp-on-time-us",
+		.lookup_tbl = aw99703_ramp_tbl,
+		.tbl_size = ARRAY_SIZE(aw99703_ramp_tbl),
+		.reg = AW99703_REG_TURNCFG,
+		.mask = AW99703_RAMP_ON_MASK,
+		.def_code = 4,
+	},
+	{
+		.name = "awinic,ramp-off-time-us",
+		.lookup_tbl = aw99703_ramp_tbl,
+		.tbl_size = ARRAY_SIZE(aw99703_ramp_tbl),
+		.reg = AW99703_REG_TURNCFG,
+		.mask = AW99703_RAMP_OFF_MASK,
+		.def_code = 4,
+	},
+	{
+		.name = "awinic,pwm-trans-time-ms",
+		.lookup_tbl = aw99703_pwm_trans_tbl,
+		.tbl_size = ARRAY_SIZE(aw99703_pwm_trans_tbl),
+		.reg = AW99703_REG_TRANCFG,
+		.mask = AW99703_PWM_TRANS_MASK,
+		.def_code = 0,
+	},
+	{
+		.name = "awinic,i2c-trans-time-ms",
+		.lookup_tbl = aw99703_i2c_trans_tbl,
+		.tbl_size = ARRAY_SIZE(aw99703_i2c_trans_tbl),
+		.reg = AW99703_REG_TRANCFG,
+		.mask = AW99703_I2C_TRANS_MASK,
+		.def_code = 0,
+	},
+};
+
+struct reg_init_data {
+	u8 reg;
+	u8 mask;
+	u8 val;
+};
+
+#define AW99703_INIT_TBL_SIZE	(ARRAY_SIZE(aw99703_dt_props) + 3)
+
+struct aw99703_device {
+	struct device *dev;
+	struct regmap *regmap;
+	struct backlight_device *bl_dev;
+	struct gpio_desc *hwen_gpio;
+	struct reg_init_data init_tbl[AW99703_INIT_TBL_SIZE];
+	bool bl_enable;
+};
+
+static bool aw99703_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case AW99703_REG_ID:
+	case AW99703_REG_MODE ... AW99703_REG_FLAGS2:
+	case AW99703_REG_FLAGS3:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool aw99703_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case AW99703_REG_SFTRST ... AW99703_REG_AFLOW:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool aw99703_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case AW99703_REG_FLAGS1:
+	case AW99703_REG_FLAGS2:
+	case AW99703_REG_FLAGS3:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config aw99703_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = AW99703_REG_MAX,
+	.readable_reg = aw99703_readable_reg,
+	.writeable_reg = aw99703_writeable_reg,
+	.volatile_reg = aw99703_volatile_reg,
+};
+
+static int aw99703_dt_lookup(const struct aw99703_dt_prop *prop, u32 dt_val,
+			     u8 *val)
+{
+	u32 max_code = prop->mask >> __ffs(prop->mask);
+	int i;
+
+	if (!prop->lookup_tbl) {
+		if (dt_val < prop->min || (dt_val - prop->min) % prop->step ||
+		    (dt_val - prop->min) / prop->step > max_code)
+			return -EINVAL;
+		*val = (dt_val - prop->min) / prop->step;
+		return 0;
+	}
+
+	for (i = 0; i < prop->tbl_size; i++)
+		if (prop->lookup_tbl[i] != RESV && prop->lookup_tbl[i] == dt_val)
+			break;
+
+	*val = i;
+	return i == prop->tbl_size ? -EINVAL : 0;
+}
+
+static void aw99703_dt_parse(struct aw99703_device *aw,
+			     struct backlight_properties *props)
+{
+	const struct aw99703_dt_prop *prop;
+	struct reg_init_data *init = aw->init_tbl;
+	bool exp_map;
+	u32 dt_val, strings = 3;
+	int i;
+	u8 val;
+
+	for (i = 0; i < ARRAY_SIZE(aw99703_dt_props); i++, init++) {
+		prop = &aw99703_dt_props[i];
+
+		val = prop->def_code;
+		if (!device_property_read_u32(aw->dev, prop->name, &dt_val) &&
+		    aw99703_dt_lookup(prop, dt_val, &val)) {
+			dev_warn(aw->dev, "invalid value %u for %s, using chip default\n",
+				 dt_val, prop->name);
+			val = prop->def_code;
+		}
+
+		init->reg = prop->reg;
+		init->mask = prop->mask;
+		init->val = val << __ffs(prop->mask);
+	}
+
+	device_property_read_u32(aw->dev, "awinic,led-strings", &strings);
+	if (strings < 1 || strings > 3) {
+		dev_warn(aw->dev, "invalid awinic,led-strings %u, using 3\n",
+			 strings);
+		strings = 3;
+	}
+	init->reg = AW99703_REG_LEDCUR;
+	init->mask = AW99703_CHANNEL_MASK;
+	init->val = GENMASK(strings - 1, 0);
+	init++;
+
+	init->reg = AW99703_REG_MODE;
+	init->mask = AW99703_PDIS_MASK;
+	init->val = device_property_read_bool(aw->dev, "awinic,pwm-input") ?
+		    0 : AW99703_PDIS_MASK;
+	init++;
+
+	exp_map = device_property_read_bool(aw->dev, "awinic,exponential-map");
+	init->reg = AW99703_REG_MODE;
+	init->mask = AW99703_MAP_LINEAR_MASK;
+	init->val = exp_map ? 0 : AW99703_MAP_LINEAR_MASK;
+	props->scale = exp_map ? BACKLIGHT_SCALE_NON_LINEAR :
+				 BACKLIGHT_SCALE_LINEAR;
+
+	props->max_brightness = AW99703_MAX_BRT_LVL;
+	device_property_read_u32(aw->dev, "max-brightness",
+				 &props->max_brightness);
+	if (props->max_brightness > AW99703_MAX_BRT_LVL)
+		props->max_brightness = AW99703_MAX_BRT_LVL;
+
+	props->brightness = props->max_brightness >> 1;
+	device_property_read_u32(aw->dev, "default-brightness",
+				 &props->brightness);
+	if (props->brightness > props->max_brightness)
+		props->brightness = props->max_brightness;
+}
+
+static int aw99703_hw_init(struct aw99703_device *aw)
+{
+	int ret, i;
+
+	if (aw->hwen_gpio) {
+		gpiod_set_value_cansleep(aw->hwen_gpio, 1);
+		fsleep(AW99703_HWEN_ON_DELAY_US);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(aw->init_tbl); i++) {
+		ret = regmap_update_bits(aw->regmap, aw->init_tbl[i].reg,
+					 aw->init_tbl[i].mask,
+					 aw->init_tbl[i].val);
+		if (ret) {
+			dev_err(aw->dev, "failed to write init data: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static const char * const aw99703_flags1_names[] = {
+	"over-temperature", "boost over-current", "LED3 short", "LED2 short",
+	"LED1 short", "LED3 open", "LED2 open", "LED1 open",
+};
+
+static const char * const aw99703_flags2_names[] = {
+	[5] = "flash timeout", [6] = "UVLO", [7] = "boost over-voltage",
+};
+
+static void aw99703_check_faults(struct aw99703_device *aw)
+{
+	unsigned int f1, f2;
+	unsigned long flags;
+	int bit;
+
+	if (regmap_read(aw->regmap, AW99703_REG_FLAGS1, &f1) ||
+	    regmap_read(aw->regmap, AW99703_REG_FLAGS2, &f2))
+		return;
+
+	flags = f1;
+	for_each_set_bit(bit, &flags, 8)
+		dev_warn_ratelimited(aw->dev, "fault: %s\n",
+				     aw99703_flags1_names[bit]);
+
+	flags = f2 & AW99703_FLAGS2_MASK;
+	for_each_set_bit(bit, &flags, 8)
+		dev_warn_ratelimited(aw->dev, "fault: %s\n",
+				     aw99703_flags2_names[bit]);
+}
+
+static int aw99703_update_brightness(struct aw99703_device *aw, u32 brt)
+{
+	bool enable = !!brt;
+	int ret;
+
+	ret = regmap_write(aw->regmap, AW99703_REG_LEDLSB,
+			   brt & AW99703_BRT_LSB_MASK);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(aw->regmap, AW99703_REG_LEDMSB, brt >> 3);
+	if (ret)
+		return ret;
+
+	if (aw->bl_enable == enable)
+		return 0;
+
+	ret = regmap_update_bits(aw->regmap, AW99703_REG_MODE,
+				 AW99703_WORKMODE_MASK,
+				 enable ? AW99703_WORKMODE_BACKLIGHT :
+					  AW99703_WORKMODE_STANDBY);
+	if (ret) {
+		dev_err(aw->dev, "failed to %s backlight: %d\n",
+			enable ? "enable" : "disable", ret);
+		return ret;
+	}
+
+	aw->bl_enable = enable;
+	if (enable)
+		aw99703_check_faults(aw);
+
+	return 0;
+}
+
+static int aw99703_bl_update_status(struct backlight_device *bl)
+{
+	struct aw99703_device *aw = bl_get_data(bl);
+
+	return aw99703_update_brightness(aw, backlight_get_brightness(bl));
+}
+
+static const struct backlight_ops aw99703_bl_ops = {
+	.options = BL_CORE_SUSPENDRESUME,
+	.update_status = aw99703_bl_update_status,
+};
+
+static int aw99703_chip_id_read(struct aw99703_device *aw)
+{
+	unsigned int val = 0;
+	int ret = 0, i;
+
+	for (i = 0; i < AW99703_ID_RETRIES; i++) {
+		ret = regmap_read(aw->regmap, AW99703_REG_ID, &val);
+		if (!ret && val == AW99703_ID)
+			return 0;
+		fsleep(2000);
+	}
+
+	if (!ret)
+		dev_err(aw->dev, "unexpected chip id 0x%02x\n", val);
+
+	return ret ? ret : -ENODEV;
+}
+
+/*
+ * If the bootloader left the backlight on, start from its level so the
+ * handover doesn't flash the panel.
+ */
+static void aw99703_read_boot_brightness(struct aw99703_device *aw,
+					 struct backlight_properties *props)
+{
+	unsigned int mode, lsb, msb, brt;
+
+	if (regmap_read(aw->regmap, AW99703_REG_MODE, &mode) ||
+	    FIELD_GET(AW99703_WORKMODE_MASK, mode) != AW99703_WORKMODE_BACKLIGHT)
+		return;
+
+	if (regmap_read(aw->regmap, AW99703_REG_LEDLSB, &lsb) ||
+	    regmap_read(aw->regmap, AW99703_REG_LEDMSB, &msb))
+		return;
+
+	brt = (msb << 3) | (lsb & AW99703_BRT_LSB_MASK);
+	if (brt && brt <= props->max_brightness) {
+		props->brightness = brt;
+		aw->bl_enable = true;
+	}
+}
+
+static void aw99703_power_off(void *data)
+{
+	struct aw99703_device *aw = data;
+
+	aw99703_update_brightness(aw, 0);
+	gpiod_set_value_cansleep(aw->hwen_gpio, 0);
+}
+
+static int aw99703_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct backlight_properties props = {};
+	struct aw99703_device *aw;
+	int ret;
+
+	aw = devm_kzalloc(dev, sizeof(*aw), GFP_KERNEL);
+	if (!aw)
+		return -ENOMEM;
+
+	aw->dev = dev;
+	i2c_set_clientdata(client, aw);
+
+	aw->regmap = devm_regmap_init_i2c(client, &aw99703_regmap_config);
+	if (IS_ERR(aw->regmap))
+		return dev_err_probe(dev, PTR_ERR(aw->regmap),
+				     "failed to init regmap\n");
+
+	aw->hwen_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_ASIS);
+	if (IS_ERR(aw->hwen_gpio))
+		return dev_err_probe(dev, PTR_ERR(aw->hwen_gpio),
+				     "failed to get enable gpio\n");
+	if (aw->hwen_gpio) {
+		ret = gpiod_direction_output(aw->hwen_gpio, 1);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "failed to drive enable gpio\n");
+		fsleep(AW99703_HWEN_ON_DELAY_US);
+	}
+
+	ret = aw99703_chip_id_read(aw);
+	if (ret)
+		return dev_err_probe(dev, ret, "chip id check failed\n");
+
+	props.type = BACKLIGHT_RAW;
+	aw99703_dt_parse(aw, &props);
+	aw99703_read_boot_brightness(aw, &props);
+
+	ret = aw99703_hw_init(aw);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to initialize chip\n");
+
+	ret = devm_add_action_or_reset(dev, aw99703_power_off, aw);
+	if (ret)
+		return ret;
+
+	aw->bl_dev = devm_backlight_device_register(dev, "aw99703-backlight",
+						    dev, aw, &aw99703_bl_ops,
+						    &props);
+	if (IS_ERR(aw->bl_dev))
+		return dev_err_probe(dev, PTR_ERR(aw->bl_dev),
+				     "failed to register backlight\n");
+
+	return backlight_update_status(aw->bl_dev);
+}
+
+static void aw99703_shutdown(struct i2c_client *client)
+{
+	aw99703_power_off(i2c_get_clientdata(client));
+}
+
+static int aw99703_suspend(struct device *dev)
+{
+	struct aw99703_device *aw = dev_get_drvdata(dev);
+
+	if (aw->hwen_gpio) {
+		gpiod_set_value_cansleep(aw->hwen_gpio, 0);
+		aw->bl_enable = false;
+	}
+
+	return 0;
+}
+
+static int aw99703_resume(struct device *dev)
+{
+	struct aw99703_device *aw = dev_get_drvdata(dev);
+
+	return aw99703_hw_init(aw);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(aw99703_pm_ops, aw99703_suspend,
+				aw99703_resume);
+
+static const struct i2c_device_id aw99703_ids[] = {
+	{ "aw99703" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, aw99703_ids);
+
+static const struct of_device_id aw99703_match_table[] = {
+	{ .compatible = "awinic,aw99703" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, aw99703_match_table);
+
+static struct i2c_driver aw99703_i2c_driver = {
+	.probe = aw99703_probe,
+	.shutdown = aw99703_shutdown,
+	.id_table = aw99703_ids,
+	.driver = {
+		.name = "aw99703",
+		.of_match_table = aw99703_match_table,
+		.pm = pm_sleep_ptr(&aw99703_pm_ops),
+	},
+};
+module_i2c_driver(aw99703_i2c_driver);
+
+MODULE_AUTHOR("Luka Panio <lukapanio@gmail.com>");
+MODULE_DESCRIPTION("AWINIC AW99703 Backlight Driver");
+MODULE_LICENSE("GPL");
-- 
2.55.0


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/2] dt-bindings: leds: backlight: Add Awinic AW99703 backlight
  2026-10-16 16:29 ` [PATCH 1/2] dt-bindings: leds: backlight: Add Awinic AW99703 backlight Luka Panio
@ 2026-10-19  7:02   ` Krzysztof Kozlowski
  2026-10-19  7:51     ` Luka Panio
  0 siblings, 1 reply; 9+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-19  7:02 UTC (permalink / raw)
  To: Luka Panio
  Cc: lee, danielt, jingoohan1, pavel, robh, krzk+dt, conor+dt, deller,
	dri-devel, linux-leds, devicetree, linux-fbdev, linux-kernel

On Fri, Oct 16, 2026 at 06:29:11PM +0200, Luka Panio wrote:
> +  awinic,iled-max-microamp:
> +    description:
> +      Full-scale current of each LED string, reached at the maximum
> +      brightness code (and 100% PWM duty cycle if PWM dimming is used).
> +    minimum: 4800
> +    maximum: 29600
> +    multipleOf: 800
> +    default: 20000
> +
> +  awinic,ramp-on-time-us:
> +    description: Current sink ramp-up time when leaving standby.
> +    enum: [512, 1000, 2000, 4000, 8000, 16000, 32000, 64000, 128000,
> +           256000, 512000, 1024000, 2048000, 4096000, 8192000, 16384000]
> +    default: 8000
> +
> +  awinic,ramp-off-time-us:
> +    description: Current sink ramp-down time when entering standby.
> +    enum: [512, 1000, 2000, 4000, 8000, 16000, 32000, 64000, 128000,
> +           256000, 512000, 1024000, 2048000, 4096000, 8192000, 16384000]
> +    default: 8000
> +
> +  awinic,pwm-trans-time-ms:
> +    description:
> +      Transition time between brightness levels set by the PWM input,
> +      for a change over the full duty cycle range.
> +    enum: [2, 4, 8, 16, 32, 64, 128, 256]
> +    default: 2

Feels like you are re-implementing triggers. Several properties actually
feel duplicating standard LED ones.

> +
> +  awinic,i2c-trans-time-ms:
> +    description:
> +      Fixed transition time between brightness levels set over I2C,
> +      independent of the size of the change. When absent the brightness
> +      ramps with a fixed slope of 1 us per brightness code instead.
> +    enum: [128, 192, 256, 320, 384, 448, 512, 576, 640, 768, 1024, 1280,
> +           1536, 2560, 4096]

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/2] dt-bindings: leds: backlight: Add Awinic AW99703 backlight
  2026-10-19  7:02   ` Krzysztof Kozlowski
@ 2026-10-19  7:51     ` Luka Panio
  0 siblings, 0 replies; 9+ messages in thread
From: Luka Panio @ 2026-10-19  7:51 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: lee, danielt, jingoohan1, pavel, robh, krzk+dt, conor+dt, deller,
	dri-devel, linux-leds, devicetree, linux-fbdev, linux-kernel

Hi Krzysztof,

On Mon, Oct 19, 2026 at 9:02 AM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>
> On Fri, Oct 16, 2026 at 06:29:11PM +0200, Luka Panio wrote:
> > +  awinic,iled-max-microamp:
> > +    description:
> > +      Full-scale current of each LED string, reached at the maximum
> > +      brightness code (and 100% PWM duty cycle if PWM dimming is used).
> > +    minimum: 4800
> > +    maximum: 29600
> > +    multipleOf: 800
> > +    default: 20000
> > +
> > +  awinic,ramp-on-time-us:
> > +    description: Current sink ramp-up time when leaving standby.
> > +    enum: [512, 1000, 2000, 4000, 8000, 16000, 32000, 64000, 128000,
> > +           256000, 512000, 1024000, 2048000, 4096000, 8192000, 16384000]
> > +    default: 8000
> > +
> > +  awinic,ramp-off-time-us:
> > +    description: Current sink ramp-down time when entering standby.
> > +    enum: [512, 1000, 2000, 4000, 8000, 16000, 32000, 64000, 128000,
> > +           256000, 512000, 1024000, 2048000, 4096000, 8192000, 16384000]
> > +    default: 8000
> > +
> > +  awinic,pwm-trans-time-ms:
> > +    description:
> > +      Transition time between brightness levels set by the PWM input,
> > +      for a change over the full duty cycle range.
> > +    enum: [2, 4, 8, 16, 32, 64, 128, 256]
> > +    default: 2
>
> Feels like you are re-implementing triggers. Several properties actually
> feel duplicating standard LED ones.
>
Thanks for the review. Agreed, the ramp/transition timings and the
mapping mode are not really hardware descriptions. In v2
I'll drop them and leave the chip defaults, and switch to the standard
led-sources and led-max-microamp properties instead of
awinic,led-strings and awinic,iled-max-microamp.

> > +
> > +  awinic,i2c-trans-time-ms:
> > +    description:
> > +      Fixed transition time between brightness levels set over I2C,
> > +      independent of the size of the change. When absent the brightness
> > +      ramps with a fixed slope of 1 us per brightness code instead.
> > +    enum: [128, 192, 256, 320, 384, 448, 512, 576, 640, 768, 1024, 1280,
> > +           1536, 2560, 4096]
>
> Best regards,
> Krzysztof
>
Thanks,
Luka

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] backlight: Add Awinic AW99703 backlight driver
  2026-10-16 16:29 ` [PATCH 2/2] backlight: Add Awinic AW99703 backlight driver Luka Panio
@ 2026-10-19 10:51   ` Daniel Thompson
  2026-10-19 11:06     ` Luka Panio
  0 siblings, 1 reply; 9+ messages in thread
From: Daniel Thompson @ 2026-10-19 10:51 UTC (permalink / raw)
  To: Luka Panio
  Cc: lee, danielt, jingoohan1, pavel, robh, krzk+dt, conor+dt, deller,
	dri-devel, linux-leds, devicetree, linux-fbdev, linux-kernel

On Fri, Oct 16, 2026 at 06:29:12PM +0200, Luka Panio wrote:
> Add a driver for the Awinic AW99703 3-string WLED backlight controller.
>
> Register values follow the AW99703 datasheet (V1.6), the structure
> follows the aw99706 driver.

Given how much the structure follows the aw99706 driver I'd expect there
to be more code sharing between the drivers.

Cold AW99703 support be implemented by extending the existing aw99706
driver?


Daniel.

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/2] backlight: Add Awinic AW99703 backlight driver
  2026-10-19 10:51   ` Daniel Thompson
@ 2026-10-19 11:06     ` Luka Panio
  0 siblings, 0 replies; 9+ messages in thread
From: Luka Panio @ 2026-10-19 11:06 UTC (permalink / raw)
  To: Daniel Thompson
  Cc: lee, danielt, jingoohan1, pavel, robh, krzk+dt, conor+dt, deller,
	dri-devel, linux-leds, devicetree, linux-fbdev, linux-kernel

Hi Daniel,

On Mon, Oct 19, 2026 at 12:51 PM Daniel Thompson <daniel@riscstar.com> wrote:
>
> On Fri, Oct 16, 2026 at 06:29:12PM +0200, Luka Panio wrote:
> > Add a driver for the Awinic AW99703 3-string WLED backlight controller.
> >
> > Register values follow the AW99703 datasheet (V1.6), the structure
> > follows the aw99706 driver.
>
> Given how much the structure follows the aw99706 driver I'd expect there
> to be more code sharing between the drivers.
>
> Cold AW99703 support be implemented by extending the existing aw99706
> driver?
Thanks for the review. The register maps differ completely (chip ID,
brightness split, enable bit, current range, boost options), but the
driver flow is the same, so I'll turn aw99706 into a per-chip-data
driver and add AW99703 support to it in v2.
>
>
> Daniel.

Thanks,
Luka

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 0/2] backlight: Add Awinic AW99703 driver
  2026-10-16 16:29 [PATCH 0/2] backlight: Add Awinic AW99703 driver Luka Panio
  2026-10-16 16:29 ` [PATCH 1/2] dt-bindings: leds: backlight: Add Awinic AW99703 backlight Luka Panio
  2026-10-16 16:29 ` [PATCH 2/2] backlight: Add Awinic AW99703 backlight driver Luka Panio
@ 2026-10-19 11:26 ` Luka Panio
  2026-10-19 12:24   ` Daniel Thompson
  2 siblings, 1 reply; 9+ messages in thread
From: Luka Panio @ 2026-10-19 11:26 UTC (permalink / raw)
  To: lee, danielt, jingoohan1, pavel, robh, krzk+dt, conor+dt, deller,
	daniel, krzk
  Cc: dri-devel, linux-leds, devicetree, linux-fbdev, linux-kernel,
	junjie.cao, mitltlatltl

Hi Daniel, Krzysztof,

Thanks for the reviews. Before reworking this for v2 I'd like to agree
on the approach.

Following Daniel's suggestion I'll add AW99703 support to the aw99706
driver using per-chip data instead of adding a new driver. The register
maps differ almost entirely, so most of the DT properties stay
per-chip (dim-mode/UVLO/ramp-ctl on AW99706, OVP/OCP/inductor on
AW99703).

Following Krzysztof's review, the AW99703 binding will drop the
ramp/transition timing and mapping properties and use the standard
led-sources and led-max-microamp properties.

That leaves the one common property, the maximum LED current,
spelled differently in a single driver: awinic,iled-max-microamp for
AW99706 and led-max-microamp for AW99703. To avoid that I'd like to:

  - add led-max-microamp to awinic,aw99706.yaml and mark
    awinic,iled-max-microamp as deprecated
  - have the driver read led-max-microamp for both chips, falling back
    to awinic,iled-max-microamp on AW99706 so existing DTs keep working

Does that sound acceptable?

Thanks,
Luka

On Fri, Oct 16, 2026 at 6:29 PM Luka Panio <lukapanio@gmail.com> wrote:
>
> The Awinic AW99703 is a high-efficiency 3-channel WLED driver with an
> integrated boost converter and 11-bit brightness control over I2C,
> optionally combined with a PWM input. It is used as an LCD backlight
> driver in some mobile devices.
>
> This series adds the devicetree binding and a driver for it.
>
> Tested on the Nexphone by Nex Computers.
>
> A board DTS using it will follow separately.
>
> Luka Panio (2):
>   dt-bindings: leds: backlight: Add Awinic AW99703 backlight
>   backlight: Add Awinic AW99703 backlight driver
>
>  .../leds/backlight/awinic,aw99703.yaml        | 131 ++++
>  MAINTAINERS                                   |   6 +
>  drivers/video/backlight/Kconfig               |  11 +
>  drivers/video/backlight/Makefile              |   1 +
>  drivers/video/backlight/aw99703.c             | 583 ++++++++++++++++++
>  5 files changed, 732 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/leds/backlight/awinic,aw99703.yaml
>  create mode 100644 drivers/video/backlight/aw99703.c
>
>
> base-commit: d419d682257d813f6cb7a28e42b35d0edf4ad038
> --
> 2.55.0
>

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 0/2] backlight: Add Awinic AW99703 driver
  2026-10-19 11:26 ` [PATCH 0/2] backlight: Add Awinic AW99703 driver Luka Panio
@ 2026-10-19 12:24   ` Daniel Thompson
  0 siblings, 0 replies; 9+ messages in thread
From: Daniel Thompson @ 2026-10-19 12:24 UTC (permalink / raw)
  To: Luka Panio
  Cc: lee, danielt, jingoohan1, pavel, robh, krzk+dt, conor+dt, deller,
	krzk, dri-devel, linux-leds, devicetree, linux-fbdev,
	linux-kernel, junjie.cao, mitltlatltl

On Mon, Oct 19, 2026 at 01:26:21PM +0200, Luka Panio wrote:
> Hi Daniel, Krzysztof,
>
> Thanks for the reviews. Before reworking this for v2 I'd like to agree
> on the approach.
>
> Following Daniel's suggestion I'll add AW99703 support to the aw99706
> driver using per-chip data instead of adding a new driver. The register
> maps differ almost entirely, so most of the DT properties stay
> per-chip (dim-mode/UVLO/ramp-ctl on AW99706, OVP/OCP/inductor on
> AW99703).
>
> Following Krzysztof's review, the AW99703 binding will drop the
> ramp/transition timing and mapping properties and use the standard
> led-sources and led-max-microamp properties.
>
> That leaves the one common property, the maximum LED current,
> spelled differently in a single driver: awinic,iled-max-microamp for
> AW99706 and led-max-microamp for AW99703. To avoid that I'd like to:
>
>   - add led-max-microamp to awinic,aw99706.yaml and mark
>     awinic,iled-max-microamp as deprecated
>   - have the driver read led-max-microamp for both chips, falling back
>     to awinic,iled-max-microamp on AW99706 so existing DTs keep working
>
> Does that sound acceptable?

IIUC you plan to improve the binding for AW99706 before adding AW99703
support, and you will keep the old DT property working to that
existing DTBs continue to work?

Both sound good to me.


Daniel.

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-10-19 12:24 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-16 16:29 [PATCH 0/2] backlight: Add Awinic AW99703 driver Luka Panio
2026-10-16 16:29 ` [PATCH 1/2] dt-bindings: leds: backlight: Add Awinic AW99703 backlight Luka Panio
2026-10-19  7:02   ` Krzysztof Kozlowski
2026-10-19  7:51     ` Luka Panio
2026-10-16 16:29 ` [PATCH 2/2] backlight: Add Awinic AW99703 backlight driver Luka Panio
2026-10-19 10:51   ` Daniel Thompson
2026-10-19 11:06     ` Luka Panio
2026-10-19 11:26 ` [PATCH 0/2] backlight: Add Awinic AW99703 driver Luka Panio
2026-10-19 12:24   ` Daniel Thompson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®