* [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
2026-10-16 16:29 ` [PATCH 2/2] backlight: Add Awinic AW99703 backlight driver Luka Panio
0 siblings, 2 replies; 3+ 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] 3+ 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-16 16:29 ` [PATCH 2/2] backlight: Add Awinic AW99703 backlight driver Luka Panio
1 sibling, 0 replies; 3+ 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 related [flat|nested] 3+ 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
1 sibling, 0 replies; 3+ 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 related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-16 16:29 UTC | newest]
Thread overview: 3+ 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-16 16:29 ` [PATCH 2/2] backlight: Add Awinic AW99703 backlight driver Luka Panio
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox