* [PATCH v2 0/3] backlight: qcom-wled: Fix OVP IRQ imbalance and start from the hardware state
@ 2026-09-14 17:42 ` David Heidelberg
0 siblings, 0 replies; 11+ messages in thread
From: David Heidelberg via B4 Relay @ 2026-09-14 17:42 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Helge Deller, Kiran Gunda,
Marco Mattiolo, Barnabás Czémán
Cc: linux-arm-msm, dri-devel, linux-fbdev, linux-kernel, phone-devel,
Konrad Dybcio, Joel Selvaraj, David Heidelberg, stable
The qcom-wled OVP interrupt is enabled at probe whenever the module is
already on, but the driver still believes the backlight is off, so the
first brightness update re-enables the interrupt and trips "Unbalanced
enable for IRQ". sdm845-mainline has carried a workaround for years and
Joel posted a flag-based version last October [1], which Konrad and
Daniel asked to be replaced by something that does not track the IRQ
state by hand.
The first patch is unrelated and fell out of writing the readback: the
WLED3 brightness register stride has been one byte instead of two since
the 2019 restructuring, so multi-string pm8941 boards only program one
string correctly. Untested, I have no WLED3 board; it restores what
pm8941-wled.c did and matches the downstream leds-qpnp.c layout.
[1] https://lore.kernel.org/all/20251021-qcom-wled-fix-unbalanced-ovp-irq-enable-v2-1-7ff115b4ffe7@joelselvaraj.com/
Signed-off-by: David Heidelberg <david@ixit.cz>
---
Changes in v2:
- Add a fix for the WLED3 brightness register stride.
- Add patch reading the programmed brightness back via
backlight_ops.get_brightness. (Konrad)
- Note the brightness readback is done in the follow-up to the first
patch.
- Link to v1: https://patch.msgid.link/20260908-qcom-wled-backlight-v1-1-c4dd4eabda07@ixit.cz
---
David Heidelberg (3):
backlight: qcom-wled: Fix WLED3 brightness register stride
backlight: qcom-wled: Fix unbalanced OVP IRQ enable at probe
backlight: qcom-wled: Read back the programmed brightness at probe
drivers/video/backlight/qcom-wled.c | 88 +++++++++++++++++++++++++++++++------
1 file changed, 75 insertions(+), 13 deletions(-)
---
base-commit: 944a035ecca915ae947905dcfb03f2b9dc6d032c
change-id: 20260908-qcom-wled-backlight-fd9574027353
Best regards,
--
David Heidelberg <david@ixit.cz>
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 0/3] backlight: qcom-wled: Fix OVP IRQ imbalance and start from the hardware state
@ 2026-09-14 17:42 ` David Heidelberg
0 siblings, 0 replies; 11+ messages in thread
From: David Heidelberg @ 2026-09-14 17:42 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Helge Deller, Kiran Gunda,
Marco Mattiolo, Barnabás Czémán
Cc: linux-arm-msm, dri-devel, linux-fbdev, linux-kernel, phone-devel,
Konrad Dybcio, Joel Selvaraj, David Heidelberg, stable
The qcom-wled OVP interrupt is enabled at probe whenever the module is
already on, but the driver still believes the backlight is off, so the
first brightness update re-enables the interrupt and trips "Unbalanced
enable for IRQ". sdm845-mainline has carried a workaround for years and
Joel posted a flag-based version last October [1], which Konrad and
Daniel asked to be replaced by something that does not track the IRQ
state by hand.
The first patch is unrelated and fell out of writing the readback: the
WLED3 brightness register stride has been one byte instead of two since
the 2019 restructuring, so multi-string pm8941 boards only program one
string correctly. Untested, I have no WLED3 board; it restores what
pm8941-wled.c did and matches the downstream leds-qpnp.c layout.
[1] https://lore.kernel.org/all/20251021-qcom-wled-fix-unbalanced-ovp-irq-enable-v2-1-7ff115b4ffe7@joelselvaraj.com/
Signed-off-by: David Heidelberg <david@ixit.cz>
---
Changes in v2:
- Add a fix for the WLED3 brightness register stride.
- Add patch reading the programmed brightness back via
backlight_ops.get_brightness. (Konrad)
- Note the brightness readback is done in the follow-up to the first
patch.
- Link to v1: https://patch.msgid.link/20260908-qcom-wled-backlight-v1-1-c4dd4eabda07@ixit.cz
---
David Heidelberg (3):
backlight: qcom-wled: Fix WLED3 brightness register stride
backlight: qcom-wled: Fix unbalanced OVP IRQ enable at probe
backlight: qcom-wled: Read back the programmed brightness at probe
drivers/video/backlight/qcom-wled.c | 88 +++++++++++++++++++++++++++++++------
1 file changed, 75 insertions(+), 13 deletions(-)
---
base-commit: 944a035ecca915ae947905dcfb03f2b9dc6d032c
change-id: 20260908-qcom-wled-backlight-fd9574027353
Best regards,
--
David Heidelberg <david@ixit.cz>
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2 1/3] backlight: qcom-wled: Fix WLED3 brightness register stride
2026-09-14 17:42 ` David Heidelberg
@ 2026-09-14 17:42 ` David Heidelberg
-1 siblings, 0 replies; 11+ messages in thread
From: David Heidelberg via B4 Relay @ 2026-09-14 17:42 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Helge Deller, Kiran Gunda,
Marco Mattiolo, Barnabás Czémán
Cc: linux-arm-msm, dri-devel, linux-fbdev, linux-kernel, phone-devel,
Konrad Dybcio, Joel Selvaraj, David Heidelberg, stable
From: David Heidelberg <david@ixit.cz>
WLED3 has a 16-bit brightness register pair per string, at 0x40 + 2*n,
which is what the pm8941-wled driver wrote to:
rc = regmap_bulk_write(wled->regmap,
wled->addr + WLED3_CTRL_REG_VAL_BASE + 2 * i,
v, 2);
The restructuring for WLED3 turned that into WLED3_SINK_REG_BRIGHT(n),
defined as 0x40 + n, so the two byte writes for consecutive strings
overlap: string 1 overwrites the MSB of string 0 with its own LSB, and
with the default three strings only string 1 ends up with the requested
value.
Use the 2 byte stride.
Fixes: 775d2ffb4af6 ("backlight: qcom-wled: Restructure the driver for WLED3")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/video/backlight/qcom-wled.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c
index 650dd95f06ef5..22c35da72dce3 100644
--- a/drivers/video/backlight/qcom-wled.c
+++ b/drivers/video/backlight/qcom-wled.c
@@ -54,17 +54,17 @@
#define WLED3_SINK_REG_SYNC 0x47
#define WLED3_SINK_REG_SYNC_CLEAR 0x00
#define WLED3_SINK_REG_CURR_SINK 0x4f
#define WLED3_SINK_REG_CURR_SINK_MASK GENMASK(7, 5)
#define WLED3_SINK_REG_CURR_SINK_SHFT 5
/* WLED3 specific per-'string' registers below */
-#define WLED3_SINK_REG_BRIGHT(n) (0x40 + n)
+#define WLED3_SINK_REG_BRIGHT(n) (0x40 + (n * 0x10))
#define WLED3_SINK_REG_STR_MOD_EN(n) (0x60 + (n * 0x10))
#define WLED3_SINK_REG_STR_MOD_MASK BIT(7)
#define WLED3_SINK_REG_STR_FULL_SCALE_CURR(n) (0x62 + (n * 0x10))
#define WLED3_SINK_REG_STR_FULL_SCALE_CURR_MASK GENMASK(4, 0)
#define WLED3_SINK_REG_STR_MOD_SRC(n) (0x63 + (n * 0x10))
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 1/3] backlight: qcom-wled: Fix WLED3 brightness register stride
@ 2026-09-14 17:42 ` David Heidelberg
0 siblings, 0 replies; 11+ messages in thread
From: David Heidelberg @ 2026-09-14 17:42 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Helge Deller, Kiran Gunda,
Marco Mattiolo, Barnabás Czémán
Cc: linux-arm-msm, dri-devel, linux-fbdev, linux-kernel, phone-devel,
Konrad Dybcio, Joel Selvaraj, David Heidelberg, stable
WLED3 has a 16-bit brightness register pair per string, at 0x40 + 2*n,
which is what the pm8941-wled driver wrote to:
rc = regmap_bulk_write(wled->regmap,
wled->addr + WLED3_CTRL_REG_VAL_BASE + 2 * i,
v, 2);
The restructuring for WLED3 turned that into WLED3_SINK_REG_BRIGHT(n),
defined as 0x40 + n, so the two byte writes for consecutive strings
overlap: string 1 overwrites the MSB of string 0 with its own LSB, and
with the default three strings only string 1 ends up with the requested
value.
Use the 2 byte stride.
Fixes: 775d2ffb4af6 ("backlight: qcom-wled: Restructure the driver for WLED3")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/video/backlight/qcom-wled.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c
index 650dd95f06ef5..22c35da72dce3 100644
--- a/drivers/video/backlight/qcom-wled.c
+++ b/drivers/video/backlight/qcom-wled.c
@@ -54,17 +54,17 @@
#define WLED3_SINK_REG_SYNC 0x47
#define WLED3_SINK_REG_SYNC_CLEAR 0x00
#define WLED3_SINK_REG_CURR_SINK 0x4f
#define WLED3_SINK_REG_CURR_SINK_MASK GENMASK(7, 5)
#define WLED3_SINK_REG_CURR_SINK_SHFT 5
/* WLED3 specific per-'string' registers below */
-#define WLED3_SINK_REG_BRIGHT(n) (0x40 + n)
+#define WLED3_SINK_REG_BRIGHT(n) (0x40 + (n * 0x10))
#define WLED3_SINK_REG_STR_MOD_EN(n) (0x60 + (n * 0x10))
#define WLED3_SINK_REG_STR_MOD_MASK BIT(7)
#define WLED3_SINK_REG_STR_FULL_SCALE_CURR(n) (0x62 + (n * 0x10))
#define WLED3_SINK_REG_STR_FULL_SCALE_CURR_MASK GENMASK(4, 0)
#define WLED3_SINK_REG_STR_MOD_SRC(n) (0x63 + (n * 0x10))
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 2/3] backlight: qcom-wled: Fix unbalanced OVP IRQ enable at probe
2026-09-14 17:42 ` David Heidelberg
@ 2026-09-14 17:42 ` David Heidelberg
-1 siblings, 0 replies; 11+ messages in thread
From: David Heidelberg via B4 Relay @ 2026-09-14 17:42 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Helge Deller, Kiran Gunda,
Marco Mattiolo, Barnabás Czémán
Cc: linux-arm-msm, dri-devel, linux-fbdev, linux-kernel, phone-devel,
Konrad Dybcio, Joel Selvaraj, David Heidelberg, stable
From: David Heidelberg <david@ixit.cz>
wled_configure_ovp_irq() derives the initial state of the OVP interrupt
from the hardware:
/* Keep OVP irq disabled until module is enabled */
if (!(val & WLED3_CTRL_REG_MOD_EN_MASK))
disable_irq(wled->ovp_irq);
but wled->brightness, which is what the rest of the driver uses to tell
whether the module is on, is left at zero.
On boards where the bootloader hands the kernel a lit backlight the two
disagree. MOD_EN is already set, so the interrupt is left enabled, while
wled_update_status() still believes the backlight is off and takes
if (!!brightness != !!wled->brightness)
rc = wled_module_enable(wled, !!brightness);
on the first backlight update. wled_module_enable() then schedules
wled_ovp_work(), which calls enable_irq() on the already enabled
interrupt:
Unbalanced enable for IRQ 176
WARNING: CPU: 0 PID: 160 at kernel/irq/manage.c:774 __enable_irq+0x50/0x80
Hardware name: Xiaomi Pocophone F1 (DT)
Workqueue: events wled_ovp_work
Call trace:
__enable_irq+0x50/0x80
enable_irq+0x48/0xa0
wled_ovp_work+0x18/0x24
process_one_work+0x1d0/0x350
worker_thread+0x13c/0x460
kthread+0x110/0x114
ret_from_fork+0x10/0x20
The bootloader is not the only way to get there. The readback runs after
wledN_setup(), and wled4_setup() sets MOD_EN itself on the path where the
sink configuration does not already match, as does the tail of
wled_auto_string_detection(), which all three setup paths can reach
through wled_auto_detection_at_init(). A cold-booted board with a dark
panel can therefore reach the same disagreement.
Move the MOD_EN readback into wled_probe() and use it to seed
wled->brightness, so the driver starts out agreeing with the hardware,
and key the OVP interrupt off wled->brightness instead. The first
backlight update then only reprograms the brightness registers and
leaves both the module and the interrupt alone. The OVP interrupt also
stays armed from probe whenever the module is already enabled, rather
than being disabled at probe and only enabled once something writes
brightness.
Note that a backlight update requesting brightness 0 before any non-zero
one now really does turn the module off wherever MOD_EN was already set,
where before it was silently ignored.
wled->brightness is seeded with default-brightness rather than the level
the bootloader actually programmed, so the first update can still step
the brightness. Reading that level back is version specific and is done
in a follow-up, to keep this fix small enough to backport.
Assisted-by: LLM
Fixes: 8663c188beea ("backlight: qcom-wled: Add auto string detection logic")
Cc: stable@vger.kernel.org
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/video/backlight/qcom-wled.c | 32 +++++++++++++++++++++-----------
1 file changed, 21 insertions(+), 11 deletions(-)
diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c
index 22c35da72dce3..43e9f6f755582 100644
--- a/drivers/video/backlight/qcom-wled.c
+++ b/drivers/video/backlight/qcom-wled.c
@@ -1622,54 +1622,49 @@ static int wled_configure_short_irq(struct wled *wled,
return rc;
}
static int wled_configure_ovp_irq(struct wled *wled,
struct platform_device *pdev)
{
int rc;
- u32 val;
wled->ovp_irq = platform_get_irq_byname(pdev, "ovp");
if (wled->ovp_irq < 0) {
dev_dbg(&pdev->dev, "OVP IRQ not found - disabling automatic string detection\n");
return 0;
}
rc = devm_request_threaded_irq(wled->dev, wled->ovp_irq, NULL,
wled_ovp_irq_handler, IRQF_ONESHOT,
"wled_ovp_irq", wled);
if (rc < 0) {
wled->ovp_irq = 0;
return 0;
}
- rc = regmap_read(wled->regmap, wled->ctrl_addr +
- WLED3_CTRL_REG_MOD_EN, &val);
- if (rc < 0)
- return rc;
-
- /* Keep OVP irq disabled until module is enabled */
- if (!(val & WLED3_CTRL_REG_MOD_EN_MASK))
+ /* Keep the OVP irq disabled until the module is enabled */
+ if (!wled->brightness)
disable_irq(wled->ovp_irq);
return 0;
}
static const struct backlight_ops wled_ops = {
.update_status = wled_update_status,
};
static int wled_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct backlight_device *bl;
struct wled *wled;
struct regmap *regmap;
+ u32 mod_en;
u32 val;
int rc;
regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!regmap) {
dev_err(&pdev->dev, "Unable to get regmap\n");
return -EINVAL;
}
@@ -1729,27 +1724,42 @@ static int wled_probe(struct platform_device *pdev)
default:
dev_err(wled->dev, "Invalid WLED version\n");
break;
}
INIT_DELAYED_WORK(&wled->ovp_work, wled_ovp_work);
+ val = WLED_DEFAULT_BRIGHTNESS;
+ of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
+
+ /*
+ * The module may already be enabled, either by a bootloader that left
+ * the backlight lit or by the setup above. Record that, so that the
+ * first brightness update does not enable an already enabled module,
+ * and so that the OVP irq is armed from probe rather than from that
+ * first update.
+ */
+ rc = regmap_read(wled->regmap, wled->ctrl_addr + WLED3_CTRL_REG_MOD_EN,
+ &mod_en);
+ if (rc < 0)
+ return rc;
+
+ if (mod_en & WLED3_CTRL_REG_MOD_EN_MASK)
+ wled->brightness = val;
+
rc = wled_configure_short_irq(wled, pdev);
if (rc < 0)
return rc;
rc = wled_configure_ovp_irq(wled, pdev);
if (rc < 0)
return rc;
- val = WLED_DEFAULT_BRIGHTNESS;
- of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
-
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.brightness = val;
props.max_brightness = wled->max_brightness;
bl = devm_backlight_device_register(&pdev->dev, wled->name,
&pdev->dev, wled,
&wled_ops, &props);
return PTR_ERR_OR_ZERO(bl);
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 2/3] backlight: qcom-wled: Fix unbalanced OVP IRQ enable at probe
@ 2026-09-14 17:42 ` David Heidelberg
0 siblings, 0 replies; 11+ messages in thread
From: David Heidelberg @ 2026-09-14 17:42 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Helge Deller, Kiran Gunda,
Marco Mattiolo, Barnabás Czémán
Cc: linux-arm-msm, dri-devel, linux-fbdev, linux-kernel, phone-devel,
Konrad Dybcio, Joel Selvaraj, David Heidelberg, stable
wled_configure_ovp_irq() derives the initial state of the OVP interrupt
from the hardware:
/* Keep OVP irq disabled until module is enabled */
if (!(val & WLED3_CTRL_REG_MOD_EN_MASK))
disable_irq(wled->ovp_irq);
but wled->brightness, which is what the rest of the driver uses to tell
whether the module is on, is left at zero.
On boards where the bootloader hands the kernel a lit backlight the two
disagree. MOD_EN is already set, so the interrupt is left enabled, while
wled_update_status() still believes the backlight is off and takes
if (!!brightness != !!wled->brightness)
rc = wled_module_enable(wled, !!brightness);
on the first backlight update. wled_module_enable() then schedules
wled_ovp_work(), which calls enable_irq() on the already enabled
interrupt:
Unbalanced enable for IRQ 176
WARNING: CPU: 0 PID: 160 at kernel/irq/manage.c:774 __enable_irq+0x50/0x80
Hardware name: Xiaomi Pocophone F1 (DT)
Workqueue: events wled_ovp_work
Call trace:
__enable_irq+0x50/0x80
enable_irq+0x48/0xa0
wled_ovp_work+0x18/0x24
process_one_work+0x1d0/0x350
worker_thread+0x13c/0x460
kthread+0x110/0x114
ret_from_fork+0x10/0x20
The bootloader is not the only way to get there. The readback runs after
wledN_setup(), and wled4_setup() sets MOD_EN itself on the path where the
sink configuration does not already match, as does the tail of
wled_auto_string_detection(), which all three setup paths can reach
through wled_auto_detection_at_init(). A cold-booted board with a dark
panel can therefore reach the same disagreement.
Move the MOD_EN readback into wled_probe() and use it to seed
wled->brightness, so the driver starts out agreeing with the hardware,
and key the OVP interrupt off wled->brightness instead. The first
backlight update then only reprograms the brightness registers and
leaves both the module and the interrupt alone. The OVP interrupt also
stays armed from probe whenever the module is already enabled, rather
than being disabled at probe and only enabled once something writes
brightness.
Note that a backlight update requesting brightness 0 before any non-zero
one now really does turn the module off wherever MOD_EN was already set,
where before it was silently ignored.
wled->brightness is seeded with default-brightness rather than the level
the bootloader actually programmed, so the first update can still step
the brightness. Reading that level back is version specific and is done
in a follow-up, to keep this fix small enough to backport.
Assisted-by: LLM
Fixes: 8663c188beea ("backlight: qcom-wled: Add auto string detection logic")
Cc: stable@vger.kernel.org
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/video/backlight/qcom-wled.c | 32 +++++++++++++++++++++-----------
1 file changed, 21 insertions(+), 11 deletions(-)
diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c
index 22c35da72dce3..43e9f6f755582 100644
--- a/drivers/video/backlight/qcom-wled.c
+++ b/drivers/video/backlight/qcom-wled.c
@@ -1622,54 +1622,49 @@ static int wled_configure_short_irq(struct wled *wled,
return rc;
}
static int wled_configure_ovp_irq(struct wled *wled,
struct platform_device *pdev)
{
int rc;
- u32 val;
wled->ovp_irq = platform_get_irq_byname(pdev, "ovp");
if (wled->ovp_irq < 0) {
dev_dbg(&pdev->dev, "OVP IRQ not found - disabling automatic string detection\n");
return 0;
}
rc = devm_request_threaded_irq(wled->dev, wled->ovp_irq, NULL,
wled_ovp_irq_handler, IRQF_ONESHOT,
"wled_ovp_irq", wled);
if (rc < 0) {
wled->ovp_irq = 0;
return 0;
}
- rc = regmap_read(wled->regmap, wled->ctrl_addr +
- WLED3_CTRL_REG_MOD_EN, &val);
- if (rc < 0)
- return rc;
-
- /* Keep OVP irq disabled until module is enabled */
- if (!(val & WLED3_CTRL_REG_MOD_EN_MASK))
+ /* Keep the OVP irq disabled until the module is enabled */
+ if (!wled->brightness)
disable_irq(wled->ovp_irq);
return 0;
}
static const struct backlight_ops wled_ops = {
.update_status = wled_update_status,
};
static int wled_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct backlight_device *bl;
struct wled *wled;
struct regmap *regmap;
+ u32 mod_en;
u32 val;
int rc;
regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!regmap) {
dev_err(&pdev->dev, "Unable to get regmap\n");
return -EINVAL;
}
@@ -1729,27 +1724,42 @@ static int wled_probe(struct platform_device *pdev)
default:
dev_err(wled->dev, "Invalid WLED version\n");
break;
}
INIT_DELAYED_WORK(&wled->ovp_work, wled_ovp_work);
+ val = WLED_DEFAULT_BRIGHTNESS;
+ of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
+
+ /*
+ * The module may already be enabled, either by a bootloader that left
+ * the backlight lit or by the setup above. Record that, so that the
+ * first brightness update does not enable an already enabled module,
+ * and so that the OVP irq is armed from probe rather than from that
+ * first update.
+ */
+ rc = regmap_read(wled->regmap, wled->ctrl_addr + WLED3_CTRL_REG_MOD_EN,
+ &mod_en);
+ if (rc < 0)
+ return rc;
+
+ if (mod_en & WLED3_CTRL_REG_MOD_EN_MASK)
+ wled->brightness = val;
+
rc = wled_configure_short_irq(wled, pdev);
if (rc < 0)
return rc;
rc = wled_configure_ovp_irq(wled, pdev);
if (rc < 0)
return rc;
- val = WLED_DEFAULT_BRIGHTNESS;
- of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
-
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.brightness = val;
props.max_brightness = wled->max_brightness;
bl = devm_backlight_device_register(&pdev->dev, wled->name,
&pdev->dev, wled,
&wled_ops, &props);
return PTR_ERR_OR_ZERO(bl);
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 3/3] backlight: qcom-wled: Read back the programmed brightness at probe
2026-09-14 17:42 ` David Heidelberg
@ 2026-09-14 17:42 ` David Heidelberg
-1 siblings, 0 replies; 11+ messages in thread
From: David Heidelberg via B4 Relay @ 2026-09-14 17:42 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Helge Deller, Kiran Gunda,
Marco Mattiolo, Barnabás Czémán
Cc: linux-arm-msm, dri-devel, linux-fbdev, linux-kernel, phone-devel,
Konrad Dybcio, Joel Selvaraj, David Heidelberg
From: David Heidelberg <david@ixit.cz>
Since the previous change wled_probe() seeds wled->brightness from
MOD_EN, but with default-brightness rather than the level the hardware
is actually driving, so a backlight left lit by the bootloader still
visibly steps on the first update, and actual_brightness reports the
last value written rather than what the panel shows.
Add wled_read_brightness(), which returns 0 when MOD_EN is clear and
otherwise reads the brightness register the matching
wledN_set_brightness() writes: the first enabled string on WLED3 and
WLED4, the selected modulator on WLED5. Use it to seed both
wled->brightness and the initial backlight property, and expose it as
backlight_ops.get_brightness so that actual_brightness reflects the
hardware too.
A module that is enabled with the brightness registers at zero, which
wled4_setup() and wled_auto_string_detection() can leave behind on a
cold boot, now reads back as off. The OVP interrupt is then armed by the
first brightness update rather than from probe, which is what happens
on every other board where the module starts out disabled.
Suggested-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Assisted-by: LLM
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/video/backlight/qcom-wled.c | 74 +++++++++++++++++++++++++++++++------
1 file changed, 63 insertions(+), 11 deletions(-)
diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c
index 43e9f6f755582..1acb3ee86878f 100644
--- a/drivers/video/backlight/qcom-wled.c
+++ b/drivers/video/backlight/qcom-wled.c
@@ -285,16 +285,60 @@ static int wled5_set_brightness(struct wled *wled, u16 brightness)
WLED5_SINK_REG_MOD_A_BRIGHTNESS_LSB :
WLED5_SINK_REG_MOD_B_BRIGHTNESS_LSB;
rc = regmap_bulk_write(wled->regmap, wled->sink_addr + offset,
&v, sizeof(v));
return rc;
}
+static int wled_read_brightness(struct wled *wled)
+{
+ u16 addr, mask;
+ __le16 v;
+ u32 val;
+ int rc;
+
+ rc = regmap_read(wled->regmap, wled->ctrl_addr + WLED3_CTRL_REG_MOD_EN,
+ &val);
+ if (rc < 0)
+ return rc;
+
+ if (!(val & WLED3_CTRL_REG_MOD_EN_MASK))
+ return 0;
+
+ switch (wled->version) {
+ case 3:
+ addr = wled->sink_addr +
+ WLED3_SINK_REG_BRIGHT(wled->cfg.enabled_strings[0]);
+ mask = WLED3_SINK_REG_BRIGHT_MAX;
+ break;
+ case 4:
+ addr = wled->sink_addr +
+ WLED4_SINK_REG_BRIGHT(wled->cfg.enabled_strings[0]);
+ mask = WLED3_SINK_REG_BRIGHT_MAX;
+ break;
+ case 5:
+ addr = wled->sink_addr + (wled->cfg.mod_sel == MOD_A ?
+ WLED5_SINK_REG_MOD_A_BRIGHTNESS_LSB :
+ WLED5_SINK_REG_MOD_B_BRIGHTNESS_LSB);
+ mask = WLED5_SINK_REG_BRIGHT_MAX_15B;
+ break;
+ default:
+ dev_err(wled->dev, "Invalid WLED version\n");
+ return -EINVAL;
+ }
+
+ rc = regmap_bulk_read(wled->regmap, addr, &v, sizeof(v));
+ if (rc < 0)
+ return rc;
+
+ return min_t(u32, le16_to_cpu(v) & mask, wled->max_brightness);
+}
+
static void wled_ovp_work(struct work_struct *work)
{
struct wled *wled = container_of(work,
struct wled, ovp_work.work);
enable_irq(wled->ovp_irq);
}
static int wled_module_enable(struct wled *wled, int val)
@@ -475,16 +519,28 @@ static int wled_update_status(struct backlight_device *bl)
wled->brightness = brightness;
unlock_mutex:
mutex_unlock(&wled->lock);
return rc;
}
+static int wled_get_brightness(struct backlight_device *bl)
+{
+ struct wled *wled = bl_get_data(bl);
+ int rc;
+
+ mutex_lock(&wled->lock);
+ rc = wled_read_brightness(wled);
+ mutex_unlock(&wled->lock);
+
+ return rc;
+}
+
static int wled4_cabc_config(struct wled *wled, bool enable)
{
int i, j, rc;
u8 val;
for (i = 0; i < wled->cfg.num_strings; i++) {
j = wled->cfg.enabled_strings[i];
@@ -1646,25 +1702,25 @@ static int wled_configure_ovp_irq(struct wled *wled,
if (!wled->brightness)
disable_irq(wled->ovp_irq);
return 0;
}
static const struct backlight_ops wled_ops = {
.update_status = wled_update_status,
+ .get_brightness = wled_get_brightness,
};
static int wled_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct backlight_device *bl;
struct wled *wled;
struct regmap *regmap;
- u32 mod_en;
u32 val;
int rc;
regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!regmap) {
dev_err(&pdev->dev, "Unable to get regmap\n");
return -EINVAL;
}
@@ -1728,41 +1784,37 @@ static int wled_probe(struct platform_device *pdev)
}
INIT_DELAYED_WORK(&wled->ovp_work, wled_ovp_work);
val = WLED_DEFAULT_BRIGHTNESS;
of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
/*
- * The module may already be enabled, either by a bootloader that left
- * the backlight lit or by the setup above. Record that, so that the
- * first brightness update does not enable an already enabled module,
- * and so that the OVP irq is armed from probe rather than from that
- * first update.
+ * The module may already be lit, either by the bootloader or by the
+ * setup above. Start from what the hardware is driving, so that the
+ * first brightness update does not step the brightness.
*/
- rc = regmap_read(wled->regmap, wled->ctrl_addr + WLED3_CTRL_REG_MOD_EN,
- &mod_en);
+ rc = wled_read_brightness(wled);
if (rc < 0)
return rc;
- if (mod_en & WLED3_CTRL_REG_MOD_EN_MASK)
- wled->brightness = val;
+ wled->brightness = rc;
rc = wled_configure_short_irq(wled, pdev);
if (rc < 0)
return rc;
rc = wled_configure_ovp_irq(wled, pdev);
if (rc < 0)
return rc;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
- props.brightness = val;
+ props.brightness = wled->brightness ?: val;
props.max_brightness = wled->max_brightness;
bl = devm_backlight_device_register(&pdev->dev, wled->name,
&pdev->dev, wled,
&wled_ops, &props);
return PTR_ERR_OR_ZERO(bl);
};
static void wled_remove(struct platform_device *pdev)
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v2 3/3] backlight: qcom-wled: Read back the programmed brightness at probe
@ 2026-09-14 17:42 ` David Heidelberg
0 siblings, 0 replies; 11+ messages in thread
From: David Heidelberg @ 2026-09-14 17:42 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Helge Deller, Kiran Gunda,
Marco Mattiolo, Barnabás Czémán
Cc: linux-arm-msm, dri-devel, linux-fbdev, linux-kernel, phone-devel,
Konrad Dybcio, Joel Selvaraj, David Heidelberg
Since the previous change wled_probe() seeds wled->brightness from
MOD_EN, but with default-brightness rather than the level the hardware
is actually driving, so a backlight left lit by the bootloader still
visibly steps on the first update, and actual_brightness reports the
last value written rather than what the panel shows.
Add wled_read_brightness(), which returns 0 when MOD_EN is clear and
otherwise reads the brightness register the matching
wledN_set_brightness() writes: the first enabled string on WLED3 and
WLED4, the selected modulator on WLED5. Use it to seed both
wled->brightness and the initial backlight property, and expose it as
backlight_ops.get_brightness so that actual_brightness reflects the
hardware too.
A module that is enabled with the brightness registers at zero, which
wled4_setup() and wled_auto_string_detection() can leave behind on a
cold boot, now reads back as off. The OVP interrupt is then armed by the
first brightness update rather than from probe, which is what happens
on every other board where the module starts out disabled.
Suggested-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Assisted-by: LLM
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/video/backlight/qcom-wled.c | 74 +++++++++++++++++++++++++++++++------
1 file changed, 63 insertions(+), 11 deletions(-)
diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c
index 43e9f6f755582..1acb3ee86878f 100644
--- a/drivers/video/backlight/qcom-wled.c
+++ b/drivers/video/backlight/qcom-wled.c
@@ -285,16 +285,60 @@ static int wled5_set_brightness(struct wled *wled, u16 brightness)
WLED5_SINK_REG_MOD_A_BRIGHTNESS_LSB :
WLED5_SINK_REG_MOD_B_BRIGHTNESS_LSB;
rc = regmap_bulk_write(wled->regmap, wled->sink_addr + offset,
&v, sizeof(v));
return rc;
}
+static int wled_read_brightness(struct wled *wled)
+{
+ u16 addr, mask;
+ __le16 v;
+ u32 val;
+ int rc;
+
+ rc = regmap_read(wled->regmap, wled->ctrl_addr + WLED3_CTRL_REG_MOD_EN,
+ &val);
+ if (rc < 0)
+ return rc;
+
+ if (!(val & WLED3_CTRL_REG_MOD_EN_MASK))
+ return 0;
+
+ switch (wled->version) {
+ case 3:
+ addr = wled->sink_addr +
+ WLED3_SINK_REG_BRIGHT(wled->cfg.enabled_strings[0]);
+ mask = WLED3_SINK_REG_BRIGHT_MAX;
+ break;
+ case 4:
+ addr = wled->sink_addr +
+ WLED4_SINK_REG_BRIGHT(wled->cfg.enabled_strings[0]);
+ mask = WLED3_SINK_REG_BRIGHT_MAX;
+ break;
+ case 5:
+ addr = wled->sink_addr + (wled->cfg.mod_sel == MOD_A ?
+ WLED5_SINK_REG_MOD_A_BRIGHTNESS_LSB :
+ WLED5_SINK_REG_MOD_B_BRIGHTNESS_LSB);
+ mask = WLED5_SINK_REG_BRIGHT_MAX_15B;
+ break;
+ default:
+ dev_err(wled->dev, "Invalid WLED version\n");
+ return -EINVAL;
+ }
+
+ rc = regmap_bulk_read(wled->regmap, addr, &v, sizeof(v));
+ if (rc < 0)
+ return rc;
+
+ return min_t(u32, le16_to_cpu(v) & mask, wled->max_brightness);
+}
+
static void wled_ovp_work(struct work_struct *work)
{
struct wled *wled = container_of(work,
struct wled, ovp_work.work);
enable_irq(wled->ovp_irq);
}
static int wled_module_enable(struct wled *wled, int val)
@@ -475,16 +519,28 @@ static int wled_update_status(struct backlight_device *bl)
wled->brightness = brightness;
unlock_mutex:
mutex_unlock(&wled->lock);
return rc;
}
+static int wled_get_brightness(struct backlight_device *bl)
+{
+ struct wled *wled = bl_get_data(bl);
+ int rc;
+
+ mutex_lock(&wled->lock);
+ rc = wled_read_brightness(wled);
+ mutex_unlock(&wled->lock);
+
+ return rc;
+}
+
static int wled4_cabc_config(struct wled *wled, bool enable)
{
int i, j, rc;
u8 val;
for (i = 0; i < wled->cfg.num_strings; i++) {
j = wled->cfg.enabled_strings[i];
@@ -1646,25 +1702,25 @@ static int wled_configure_ovp_irq(struct wled *wled,
if (!wled->brightness)
disable_irq(wled->ovp_irq);
return 0;
}
static const struct backlight_ops wled_ops = {
.update_status = wled_update_status,
+ .get_brightness = wled_get_brightness,
};
static int wled_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct backlight_device *bl;
struct wled *wled;
struct regmap *regmap;
- u32 mod_en;
u32 val;
int rc;
regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!regmap) {
dev_err(&pdev->dev, "Unable to get regmap\n");
return -EINVAL;
}
@@ -1728,41 +1784,37 @@ static int wled_probe(struct platform_device *pdev)
}
INIT_DELAYED_WORK(&wled->ovp_work, wled_ovp_work);
val = WLED_DEFAULT_BRIGHTNESS;
of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
/*
- * The module may already be enabled, either by a bootloader that left
- * the backlight lit or by the setup above. Record that, so that the
- * first brightness update does not enable an already enabled module,
- * and so that the OVP irq is armed from probe rather than from that
- * first update.
+ * The module may already be lit, either by the bootloader or by the
+ * setup above. Start from what the hardware is driving, so that the
+ * first brightness update does not step the brightness.
*/
- rc = regmap_read(wled->regmap, wled->ctrl_addr + WLED3_CTRL_REG_MOD_EN,
- &mod_en);
+ rc = wled_read_brightness(wled);
if (rc < 0)
return rc;
- if (mod_en & WLED3_CTRL_REG_MOD_EN_MASK)
- wled->brightness = val;
+ wled->brightness = rc;
rc = wled_configure_short_irq(wled, pdev);
if (rc < 0)
return rc;
rc = wled_configure_ovp_irq(wled, pdev);
if (rc < 0)
return rc;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
- props.brightness = val;
+ props.brightness = wled->brightness ?: val;
props.max_brightness = wled->max_brightness;
bl = devm_backlight_device_register(&pdev->dev, wled->name,
&pdev->dev, wled,
&wled_ops, &props);
return PTR_ERR_OR_ZERO(bl);
};
static void wled_remove(struct platform_device *pdev)
--
2.55.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v2 1/3] backlight: qcom-wled: Fix WLED3 brightness register stride
2026-09-14 17:42 ` David Heidelberg
(?)
@ 2026-09-14 17:50 ` sashiko-bot
-1 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-09-14 17:50 UTC (permalink / raw)
To: David Heidelberg; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The patch incorrectly implements a 16-byte stride for WLED3 brightness registers instead of the intended 2-byte stride, causing register collision and corruption.
--
commit f7a2ef410d595586ca0ef2c87637e54f23dec1e2
Author: David Heidelberg <david@ixit.cz>
Subject: backlight: qcom-wled: Fix WLED3 brightness register stride
This commit aims to fix an overlapping brightness register write issue in the
WLED3 driver. It modifies the WLED3_SINK_REG_BRIGHT macro to update the
register stride for consecutive strings. The commit message states the intent
is to use a 2-byte stride.
> diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c
> index 650dd95f06ef5..22c35da72dce3 100644
> --- a/drivers/video/backlight/qcom-wled.c
> +++ b/drivers/video/backlight/qcom-wled.c
> @@ -59,7 +59,7 @@
> #define WLED3_SINK_REG_CURR_SINK_SHFT 5
>
> /* WLED3 specific per-'string' registers below */
> -#define WLED3_SINK_REG_BRIGHT(n) (0x40 + n)
> +#define WLED3_SINK_REG_BRIGHT(n) (0x40 + (n * 0x10))
[Severity: High]
Does this code incorrectly implement a 16-byte stride instead of the intended
2-byte stride?
The commit message states "Use the 2 byte stride.", but the macro evaluates to
a 16-byte stride (n * 0x10) instead of a mathematically sound 2-byte stride
(n * 2).
When wled3_set_brightness() evaluates this macro and passes it to
regmap_bulk_write():
drivers/video/backlight/qcom-wled.c:wled3_set_brightness() {
...
rc = regmap_bulk_write(wled->regmap, wled->ctrl_addr +
WLED3_SINK_REG_BRIGHT(wled->cfg.enabled_strings[i]),
&v, sizeof(v));
...
}
Writing 2 bytes to WLED3_SINK_REG_BRIGHT(2) calculates the address as
0x40 + 0x20 = 0x60. This will overwrite the adjacent control register
WLED3_SINK_REG_STR_MOD_EN(0) (which is also defined as 0x60 + 0x00), causing
hardware control register corruption.
>
> #define WLED3_SINK_REG_STR_MOD_EN(n) (0x60 + (n * 0x10))
> #define WLED3_SINK_REG_STR_MOD_MASK BIT(7)
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260914-qcom-wled-backlight-v2-0-a908d9233e93@ixit.cz?part=1
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 3/3] backlight: qcom-wled: Read back the programmed brightness at probe
2026-09-14 17:42 ` David Heidelberg
(?)
@ 2026-09-14 17:56 ` sashiko-bot
-1 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-09-14 17:56 UTC (permalink / raw)
To: David Heidelberg; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [Critical] wled_remove() dereferences a NULL pointer because platform_set_drvdata() is never called in wled_probe().
- [High] wled_auto_string_detection() hardcodes wled4_set_brightness() instead of using the wled_set_brightness() function pointer, corrupting WLED5 hardware registers.
- [High] The asynchronous delayed work wled->ovp_work can execute after the wled struct is freed on the probe error path.
- [High] Incorrect teardown sequence in wled_remove() causes use-after-destroy on mutex and Use-After-Free on workqueue.
- [High] WLED3_SINK_REG_BRIGHT macro is incorrectly defined with a 16-byte stride instead of a 2-byte stride, causing brightness updates to corrupt unrelated registers.
--
commit 410b83154149a165bcd4c13790f405a3c5f2ac9d
Author: David Heidelberg <david@ixit.cz>
backlight: qcom-wled: Read back the programmed brightness at probe
This commit adds support to read the hardware brightness at probe time to avoid
visible stepping if the bootloader left the backlight on. It introduces
wled_read_brightness() and wires it up to the backlight_ops get_brightness.
> A module that is enabled with the brightness registers at zero, which
> wled4_setup() and wled_auto_string_detection() can leave behind on a
> cold boot, now reads back as off.
[Severity: High]
This is a pre-existing issue, but since the commit message mentions
wled_auto_string_detection(), does this function incorrectly hardcode the WLED4
brightness setter for all versions?
Looking at wled_auto_string_detection(), it bypasses the
wled->wled_set_brightness() pointer and directly calls wled4_set_brightness():
drivers/video/backlight/qcom-wled.c:wled_auto_string_detection() {
...
rc = wled4_set_brightness(wled, AUTO_DETECT_BRIGHTNESS);
...
rc = wled4_set_brightness(wled, wled->brightness);
}
On WLED5 hardware, this writes to the WLED4 register instead of the proper
WLED5 registers, potentially corrupting hardware state when an OVP fault
occurs. Could this be updated to use the generic function pointer?
> diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c
> index 43e9f6f755582..1acb3ee86878f 100644
> --- a/drivers/video/backlight/qcom-wled.c
> +++ b/drivers/video/backlight/qcom-wled.c
[ ... ]
> @@ -290,6 +290,50 @@ static int wled5_set_brightness(struct wled *wled, u16 brightness)
> return rc;
> }
>
> +static int wled_read_brightness(struct wled *wled)
> +{
> + u16 addr, mask;
> + __le16 v;
> + u32 val;
> + int rc;
> +
> + rc = regmap_read(wled->regmap, wled->ctrl_addr + WLED3_CTRL_REG_MOD_EN,
> + &val);
> + if (rc < 0)
> + return rc;
> +
> + if (!(val & WLED3_CTRL_REG_MOD_EN_MASK))
> + return 0;
> +
> + switch (wled->version) {
> + case 3:
> + addr = wled->sink_addr +
> + WLED3_SINK_REG_BRIGHT(wled->cfg.enabled_strings[0]);
[Severity: High]
This is a pre-existing issue, but does WLED3_SINK_REG_BRIGHT use an incorrect
stride that corrupts other registers here and in wled3_set_brightness()?
Looking at the macro definition:
#define WLED3_SINK_REG_BRIGHT(n) (0x40 + (n * 0x10))
The macro applies a 16-byte stride instead of a 2-byte stride. Because
WLED3_SINK_REG_STR_MOD_EN(n) also uses a 16-byte stride starting at 0x60,
evaluating WLED3_SINK_REG_BRIGHT(2) results in 0x60.
This aliases exactly with WLED3_SINK_REG_STR_MOD_EN(0), meaning brightness
updates for string 2 can accidentally overwrite the modulator enable for
string 0.
> + mask = WLED3_SINK_REG_BRIGHT_MAX;
> + break;
[ ... ]
> @@ -480,6 +524,18 @@ static int wled_update_status(struct backlight_device *bl)
> return rc;
> }
>
> +static int wled_get_brightness(struct backlight_device *bl)
> +{
> + struct wled *wled = bl_get_data(bl);
> + int rc;
> +
> + mutex_lock(&wled->lock);
[Severity: High]
This isn't a bug introduced by this patch, but does the teardown sequence in
wled_remove() allow a use-after-destroy here if this new sysfs method is
called concurrently?
wled_remove() prematurely destroys the mutex:
drivers/video/backlight/qcom-wled.c:wled_remove() {
...
mutex_destroy(&wled->lock);
cancel_delayed_work_sync(&wled->ovp_work);
...
}
Because the backlight device is registered with devm_backlight_device_register,
it remains accessible in sysfs until devres cleanup runs after wled_remove()
finishes.
If a user reads the brightness via sysfs during this window,
wled_get_brightness() will lock a destroyed mutex. Could we move the resource
cleanup to a devm action or unregister the backlight explicitly?
> + rc = wled_read_brightness(wled);
> + mutex_unlock(&wled->lock);
> +
> + return rc;
> +}
[ ... ]
> @@ -1757,7 +1809,7 @@ static int wled_probe(struct platform_device *pdev)
>
> memset(&props, 0, sizeof(struct backlight_properties));
> props.type = BACKLIGHT_RAW;
> - props.brightness = val;
> + props.brightness = wled->brightness ?: val;
> props.max_brightness = wled->max_brightness;
> bl = devm_backlight_device_register(&pdev->dev, wled->name,
> &pdev->dev, wled,
> &wled_ops, &props);
> return PTR_ERR_OR_ZERO(bl);
> }
[Severity: Critical]
This is a pre-existing issue, but does wled_remove() dereference a NULL pointer
because we missed calling platform_set_drvdata() in probe?
If the driver is unbound, wled_remove() does:
drivers/video/backlight/qcom-wled.c:wled_remove() {
struct wled *wled = platform_get_drvdata(pdev);
mutex_destroy(&wled->lock);
...
}
Since platform_set_drvdata(pdev, wled) is never called in wled_probe(),
platform_get_drvdata() will return NULL, causing a crash on module removal.
[Severity: High]
This is also a pre-existing issue, but can the wled->ovp_work delayed work
execute after the wled structure is freed if this backlight registration fails?
If the short circuit interrupt fires during probe after
wled_configure_short_irq() enables it, the handler can call
wled_module_enable() which does:
drivers/video/backlight/qcom-wled.c:wled_module_enable() {
...
schedule_delayed_work(&wled->ovp_work, HZ / 100);
...
}
If devm_backlight_device_register() subsequently fails, wled_probe() returns
an error. Devres will free the wled structure, but the delayed work is never
cancelled on the probe error path, resulting in a use-after-free when the
work executes.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260914-qcom-wled-backlight-v2-0-a908d9233e93@ixit.cz?part=3
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2 2/3] backlight: qcom-wled: Fix unbalanced OVP IRQ enable at probe
2026-09-14 17:42 ` David Heidelberg
(?)
@ 2026-09-14 17:58 ` sashiko-bot
-1 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-09-14 17:58 UTC (permalink / raw)
To: David Heidelberg; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Missing platform_set_drvdata() in wled_probe() leads to a guaranteed NULL pointer dereference in wled_remove().
- [High] Incorrect teardown order in wled_remove() causes mutex use-after-destroy and use-after-free via re-armed delayed work.
- [High] A race condition between wled_short_irq_handler() and wled_update_status() can leave the hardware module incorrectly enabled when requested off.
- [High] Missing cancel_delayed_work_sync() on the probe error path leads to Use-After-Free if the short IRQ fires during probe.
- [High] wled_remove() unconditionally calls disable_irq() on short_irq and ovp_irq, which may be unallocated or negative.
--
commit 43030df8f21835cdbc934e6ed48a2467d72a8a08
Author: David Heidelberg <david@ixit.cz>
backlight: qcom-wled: Fix unbalanced OVP IRQ enable at probe
wled_configure_ovp_irq() derives the initial state of the OVP interrupt
from the hardware but wled->brightness is left at zero. This patch moves
the MOD_EN readback into wled_probe() and seeds wled->brightness to fix
the unbalanced IRQ enable.
> diff --git a/drivers/video/backlight/qcom-wled.c b/drivers/video/backlight/qcom-wled.c
> index 22c35da72dce3..43e9f6f755582 100644
> --- a/drivers/video/backlight/qcom-wled.c
> +++ b/drivers/video/backlight/qcom-wled.c
> @@ -1627,7 +1627,6 @@ static int wled_configure_ovp_irq(struct wled *wled,
> struct platform_device *pdev)
> {
> int rc;
> - u32 val;
>
> wled->ovp_irq = platform_get_irq_byname(pdev, "ovp");
> if (wled->ovp_irq < 0) {
[Severity: High]
This isn't a regression introduced by this patch, but if
platform_get_irq_byname() fails, wled->ovp_irq retains the negative error
code, and wled_configure_ovp_irq() returns 0.
Later, during unbind:
drivers/video/backlight/qcom-wled.c:wled_remove() {
...
disable_irq(wled->short_irq);
disable_irq(wled->ovp_irq);
...
}
Does this unconditional disable_irq() risk passing an unallocated negative
value (or 0, if the threaded IRQ request fails and sets it to 0),
potentially crippling system IRQ 0?
[ ... ]
> @@ -1742,9 +1755,6 @@ static int wled_probe(struct platform_device *pdev)
> if (rc < 0)
> return rc;
>
> - val = WLED_DEFAULT_BRIGHTNESS;
> - of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
> -
> memset(&props, 0, sizeof(struct backlight_properties));
> props.type = BACKLIGHT_RAW;
> props.brightness = val;
[Severity: Critical]
This is a pre-existing regression, but I noticed wled_probe() successfully
registers the backlight device and returns:
drivers/video/backlight/qcom-wled.c:wled_probe() {
...
bl = devm_backlight_device_register(&pdev->dev, wled->name,
&pdev->dev, wled,
&wled_ops, &props);
return PTR_ERR_OR_ZERO(bl);
}
Since platform_set_drvdata() is never called in the probe path, won't
platform_get_drvdata(pdev) return NULL in wled_remove()?
drivers/video/backlight/qcom-wled.c:wled_remove() {
struct wled *wled = platform_get_drvdata(pdev);
mutex_destroy(&wled->lock);
...
}
Could this lead to a guaranteed NULL pointer dereference when the device
is unbound?
[Severity: High]
This is another pre-existing regression, but looking at the error paths at
the end of wled_probe():
If wled_configure_short_irq() succeeds, it registers and enables the
short IRQ immediately. If a subsequent step like wled_configure_ovp_irq() or
devm_backlight_device_register() fails, wled_probe() returns an error code:
drivers/video/backlight/qcom-wled.c:wled_probe() {
...
rc = wled_configure_ovp_irq(wled, pdev);
if (rc < 0)
return rc;
...
}
If the hardware asserts the short IRQ concurrently during this failure,
wled_short_irq_handler() will run and schedule the delayed work:
drivers/video/backlight/qcom-wled.c:wled_short_irq_handler() {
...
rc = wled_module_enable(wled, true);
...
}
Since there is no cancel_delayed_work_sync() on the probe error path,
will this result in a use-after-free when devres cleans up and frees the
wled struct while the delayed work is still queued?
[Severity: High]
This isn't a regression introduced by this patch, but looking at the
teardown sequence in wled_remove():
drivers/video/backlight/qcom-wled.c:wled_remove() {
struct wled *wled = platform_get_drvdata(pdev);
mutex_destroy(&wled->lock);
cancel_delayed_work_sync(&wled->ovp_work);
disable_irq(wled->short_irq);
...
}
If the short IRQ fires concurrently before disable_irq() is called, could
the IRQ handler attempt to acquire the destroyed mutex?
Additionally, since wled_short_irq_handler() unconditionally re-enables the
module, could it asynchronously re-arm wled->ovp_work after the cancellation
has already finished, leading to a use-after-free when the struct is freed?
[Severity: High]
This is also a pre-existing regression, but I noticed
wled_short_irq_handler() doesn't seem to check if the backlight was
intentionally disabled:
drivers/video/backlight/qcom-wled.c:wled_short_irq_handler() {
...
msleep(WLED_SHORT_DLY_MS);
rc = wled_module_enable(wled, true);
...
}
If userspace intentionally disables the backlight via wled_update_status()
by setting brightness to 0, and a short circuit IRQ fires concurrently or
shortly after, wouldn't the handler force the hardware back on?
Does this leave the hardware enabled indefinitely while the software state
considers it off, ignoring further requests to set brightness to 0?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260914-qcom-wled-backlight-v2-0-a908d9233e93@ixit.cz?part=2
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-09-14 17:58 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-14 17:42 [PATCH v2 0/3] backlight: qcom-wled: Fix OVP IRQ imbalance and start from the hardware state David Heidelberg via B4 Relay
2026-09-14 17:42 ` David Heidelberg
2026-09-14 17:42 ` [PATCH v2 1/3] backlight: qcom-wled: Fix WLED3 brightness register stride David Heidelberg via B4 Relay
2026-09-14 17:42 ` David Heidelberg
2026-09-14 17:50 ` sashiko-bot
2026-09-14 17:42 ` [PATCH v2 2/3] backlight: qcom-wled: Fix unbalanced OVP IRQ enable at probe David Heidelberg via B4 Relay
2026-09-14 17:42 ` David Heidelberg
2026-09-14 17:58 ` sashiko-bot
2026-09-14 17:42 ` [PATCH v2 3/3] backlight: qcom-wled: Read back the programmed brightness " David Heidelberg via B4 Relay
2026-09-14 17:42 ` David Heidelberg
2026-09-14 17:56 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.