mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout
@ 2026-10-08 14:16 chitranshmandhaniya
  2026-10-08 14:30 ` Ilpo Järvinen
  2026-10-09  7:24 ` [PATCH v2] " chitranshmandhaniya
  0 siblings, 2 replies; 13+ messages in thread
From: chitranshmandhaniya @ 2026-10-08 14:16 UTC (permalink / raw)
  To: hansg
  Cc: ilpo.jarvinen, platform-driver-x86, linux-kernel, Chitransh Mandhaniya

From: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>

Some Dell OptiPlex AIO 7410 (35W, board 01KP8D) ship with DELL0501 in
the ACPI DSDT (status = 0x0F) even though no UART scalar board is
physically present.  This causes acpi_video_get_backlight_type() to
return acpi_backlight_dell_uart, which makes i915 skip registering
intel_backlight.  The dell-uart-backlight driver then times out talking
to the absent board and the probe fails, leaving the machine with zero
backlight control.

A BIOS update fixes this on some models (e.g. OptiPlex 7420, board
0914RT) by setting DELL0501 _STA to 0, but Dell has not shipped a
corrected BIOS for the 7410 even in the latest release (1.37.0,
2026-05-28).

When the initial get_version command returns -ETIMEDOUT, fall back to
driving the backlight through the Intel PCH PWM registers (BLC_PWM_CTL /
BLC_PWM_FREQ / BLC_PWM_DUTY at MMIO BAR0 + 0xC8250).  The fallback
reads the BIOS-programmed PWM frequency as max_brightness and registers
a BACKLIGHT_PLATFORM device so that compositors (Weston, GNOME, KDE)
pick it up normally.

Tested on:
  - OptiPlex AIO 7410 35W (01KP8D) -- timeout, fallback activates, works
  - OptiPlex AIO 7420 35W (0914RT, old BIOS) -- timeout, fallback works
  - OptiPlex AIO 7420 35W (0914RT, new BIOS) -- DELL0501 absent, driver
    not loaded, intel_backlight works natively (no regression)

Signed-off-by: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>
---
 .../platform/x86/dell/dell-uart-backlight.c   | 116 +++++++++++++++++-
 1 file changed, 113 insertions(+), 3 deletions(-)

diff --git a/drivers/platform/x86/dell/dell-uart-backlight.c b/drivers/platform/x86/dell/dell-uart-backlight.c
index f323a667dc2d..a2045e6c49bd 100644
--- a/drivers/platform/x86/dell/dell-uart-backlight.c
+++ b/drivers/platform/x86/dell/dell-uart-backlight.c
@@ -13,8 +13,10 @@
 #include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/err.h>
+#include <linux/io.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/pci.h>
 #include <linux/platform_device.h>
 #include <linux/serdev.h>
 #include <linux/string.h>
@@ -50,6 +52,11 @@
 #define MIN_RESP_LEN			3
 #define MAX_RESP_LEN			80
 
+/* PCH PWM backlight registers relative to Intel GPU MMIO BAR0 */
+#define PCH_BLC_PWM_CTL		0xC8250
+#define PCH_BLC_PWM_FREQ	0xC8254
+#define PCH_BLC_PWM_DUTY	0xC8258
+
 struct dell_uart_backlight {
 	struct mutex mutex;
 	wait_queue_head_t wait_queue;
@@ -64,6 +71,11 @@ struct dell_uart_backlight {
 	int power;
 };
 
+struct dell_pwm_backlight {
+	void __iomem *mmio;
+	u32 max_brightness;
+};
+
 /* Checksum: SUM(Length and Cmd and Data) xor 0xFF */
 static u8 dell_uart_checksum(u8 *buf, int len)
 {
@@ -267,6 +279,97 @@ static const struct serdev_device_ops dell_uart_bl_serdev_ops = {
 	.write_wakeup = serdev_device_write_wakeup,
 };
 
+/*
+ * Some Dell AIO models (e.g. OptiPlex 7410) declare DELL0501 in ACPI but
+ * have no physical UART scalar board.  When the UART times out fall back
+ * to driving the Intel PCH PWM backlight registers directly.
+ */
+static int dell_pwm_update_status(struct backlight_device *bd)
+{
+	struct dell_pwm_backlight *pwm = bl_get_data(bd);
+	u32 duty;
+
+	if (bd->props.power != BACKLIGHT_POWER_ON ||
+	    bd->props.state & BL_CORE_SUSPENDED)
+		duty = 0;
+	else
+		duty = bd->props.brightness;
+
+	writel(duty, pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
+	return 0;
+}
+
+static int dell_pwm_get_brightness(struct backlight_device *bd)
+{
+	struct dell_pwm_backlight *pwm = bl_get_data(bd);
+
+	return readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
+}
+
+static const struct backlight_ops dell_pwm_backlight_ops = {
+	.update_status  = dell_pwm_update_status,
+	.get_brightness = dell_pwm_get_brightness,
+};
+
+static int dell_pwm_fallback_register(struct device *dev)
+{
+	struct dell_pwm_backlight *pwm;
+	struct backlight_properties props = {};
+	struct backlight_device *bl;
+	struct pci_dev *gpu;
+	resource_size_t bar0;
+	u32 freq, duty, ctl;
+
+	gpu = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL);
+	if (!gpu || gpu->vendor != PCI_VENDOR_ID_INTEL) {
+		pci_dev_put(gpu);
+		dev_warn(dev, "No Intel GPU found, cannot fall back to PCH PWM\n");
+		return -ENODEV;
+	}
+
+	bar0 = pci_resource_start(gpu, 0);
+	pci_dev_put(gpu);
+	if (!bar0) {
+		dev_warn(dev, "Intel GPU BAR0 not assigned\n");
+		return -ENODEV;
+	}
+
+	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
+	if (!pwm)
+		return -ENOMEM;
+
+	pwm->mmio = devm_ioremap(dev, bar0 + PCH_BLC_PWM_CTL, 0x10);
+	if (!pwm->mmio) {
+		dev_warn(dev, "Failed to ioremap PCH PWM registers\n");
+		return -ENOMEM;
+	}
+
+	ctl  = readl(pwm->mmio);
+	freq = readl(pwm->mmio + (PCH_BLC_PWM_FREQ - PCH_BLC_PWM_CTL));
+	duty = readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
+
+	if (!(ctl & BIT(31)) || freq == 0) {
+		dev_warn(dev, "PCH PWM not enabled by BIOS (ctl=0x%08x freq=%u)\n",
+			 ctl, freq);
+		return -ENODEV;
+	}
+
+	pwm->max_brightness = freq;
+
+	dev_info(dev, "Falling back to PCH PWM backlight (max=%u current=%u)\n",
+		 freq, duty);
+
+	props.type = BACKLIGHT_PLATFORM;
+	props.brightness = duty;
+	props.max_brightness = freq;
+
+	bl = devm_backlight_device_register(dev, "dell_uart_backlight",
+					    dev, pwm,
+					    &dell_pwm_backlight_ops,
+					    &props);
+	return PTR_ERR_OR_ZERO(bl);
+}
+
 static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
 {
 	u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN];
@@ -300,18 +403,18 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
 
 	ret = dell_uart_bl_command(dell_bl, get_version, GET_CMD_LEN, resp, MAX_RESP_LEN);
 	if (ret)
-		return dev_err_probe(dev, ret, "getting firmware version\n");
+		goto uart_fail;
 
 	dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA);
 
 	/* Initialize bl_power to a known value */
 	ret = dell_uart_set_bl_power(dell_bl, BACKLIGHT_POWER_ON);
 	if (ret)
-		return ret;
+		goto uart_fail;
 
 	ret = dell_uart_get_brightness(dell_bl);
 	if (ret < 0)
-		return ret;
+		goto uart_fail;
 
 	props.type = BACKLIGHT_PLATFORM;
 	props.brightness = ret;
@@ -323,6 +426,13 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
 						     &dell_uart_backlight_ops,
 						     &props);
 	return PTR_ERR_OR_ZERO(dell_bl->bl);
+
+uart_fail:
+	if (ret == -ETIMEDOUT) {
+		dev_info(dev, "UART backlight controller not responding, trying PCH PWM fallback\n");
+		return dell_pwm_fallback_register(dev);
+	}
+	return dev_err_probe(dev, ret, "UART backlight communication failed\n");
 }
 
 static struct serdev_device_driver dell_uart_bl_serdev_driver = {
-- 
2.43.0


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

* Re: [PATCH] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout
  2026-10-08 14:16 [PATCH] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout chitranshmandhaniya
@ 2026-10-08 14:30 ` Ilpo Järvinen
  2026-10-09  7:24 ` [PATCH v2] " chitranshmandhaniya
  1 sibling, 0 replies; 13+ messages in thread
From: Ilpo Järvinen @ 2026-10-08 14:30 UTC (permalink / raw)
  To: chitranshmandhaniya
  Cc: Hans de Goede, platform-driver-x86, LKML, Chitransh Mandhaniya

On Thu, 8 Oct 2026, chitranshmandhaniya@gmail.com wrote:

> From: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>
> 
> Some Dell OptiPlex AIO 7410 (35W, board 01KP8D) ship with DELL0501 in
> the ACPI DSDT (status = 0x0F) even though no UART scalar board is
> physically present.  This causes acpi_video_get_backlight_type() to
> return acpi_backlight_dell_uart, which makes i915 skip registering
> intel_backlight.  The dell-uart-backlight driver then times out talking
> to the absent board and the probe fails, leaving the machine with zero
> backlight control.
> 
> A BIOS update fixes this on some models (e.g. OptiPlex 7420, board
> 0914RT) by setting DELL0501 _STA to 0, but Dell has not shipped a
> corrected BIOS for the 7410 even in the latest release (1.37.0,
> 2026-05-28).
> 
> When the initial get_version command returns -ETIMEDOUT, fall back to
> driving the backlight through the Intel PCH PWM registers (BLC_PWM_CTL /
> BLC_PWM_FREQ / BLC_PWM_DUTY at MMIO BAR0 + 0xC8250).  The fallback
> reads the BIOS-programmed PWM frequency as max_brightness and registers
> a BACKLIGHT_PLATFORM device so that compositors (Weston, GNOME, KDE)
> pick it up normally.
> 
> Tested on:
>   - OptiPlex AIO 7410 35W (01KP8D) -- timeout, fallback activates, works
>   - OptiPlex AIO 7420 35W (0914RT, old BIOS) -- timeout, fallback works
>   - OptiPlex AIO 7420 35W (0914RT, new BIOS) -- DELL0501 absent, driver
>     not loaded, intel_backlight works natively (no regression)
> 
> Signed-off-by: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>
> ---
>  .../platform/x86/dell/dell-uart-backlight.c   | 116 +++++++++++++++++-
>  1 file changed, 113 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/platform/x86/dell/dell-uart-backlight.c b/drivers/platform/x86/dell/dell-uart-backlight.c
> index f323a667dc2d..a2045e6c49bd 100644
> --- a/drivers/platform/x86/dell/dell-uart-backlight.c
> +++ b/drivers/platform/x86/dell/dell-uart-backlight.c
> @@ -13,8 +13,10 @@
>  #include <linux/delay.h>
>  #include <linux/device.h>
>  #include <linux/err.h>
> +#include <linux/io.h>
>  #include <linux/module.h>
>  #include <linux/mutex.h>
> +#include <linux/pci.h>
>  #include <linux/platform_device.h>
>  #include <linux/serdev.h>
>  #include <linux/string.h>
> @@ -50,6 +52,11 @@
>  #define MIN_RESP_LEN			3
>  #define MAX_RESP_LEN			80
>  
> +/* PCH PWM backlight registers relative to Intel GPU MMIO BAR0 */
> +#define PCH_BLC_PWM_CTL		0xC8250
> +#define PCH_BLC_PWM_FREQ	0xC8254
> +#define PCH_BLC_PWM_DUTY	0xC8258
> +
>  struct dell_uart_backlight {
>  	struct mutex mutex;
>  	wait_queue_head_t wait_queue;
> @@ -64,6 +71,11 @@ struct dell_uart_backlight {
>  	int power;
>  };
>  
> +struct dell_pwm_backlight {
> +	void __iomem *mmio;
> +	u32 max_brightness;
> +};
> +
>  /* Checksum: SUM(Length and Cmd and Data) xor 0xFF */
>  static u8 dell_uart_checksum(u8 *buf, int len)
>  {
> @@ -267,6 +279,97 @@ static const struct serdev_device_ops dell_uart_bl_serdev_ops = {
>  	.write_wakeup = serdev_device_write_wakeup,
>  };
>  
> +/*
> + * Some Dell AIO models (e.g. OptiPlex 7410) declare DELL0501 in ACPI but
> + * have no physical UART scalar board.  When the UART times out fall back

Nit, one space is enough.

> + * to driving the Intel PCH PWM backlight registers directly.
> + */
> +static int dell_pwm_update_status(struct backlight_device *bd)
> +{
> +	struct dell_pwm_backlight *pwm = bl_get_data(bd);
> +	u32 duty;
> +
> +	if (bd->props.power != BACKLIGHT_POWER_ON ||
> +	    bd->props.state & BL_CORE_SUSPENDED)
> +		duty = 0;
> +	else
> +		duty = bd->props.brightness;
> +
> +	writel(duty, pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));

Please add linux/io.h to includes.

> +	return 0;
> +}
> +
> +static int dell_pwm_get_brightness(struct backlight_device *bd)
> +{
> +	struct dell_pwm_backlight *pwm = bl_get_data(bd);
> +
> +	return readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
> +}
> +
> +static const struct backlight_ops dell_pwm_backlight_ops = {
> +	.update_status  = dell_pwm_update_status,
> +	.get_brightness = dell_pwm_get_brightness,
> +};
> +
> +static int dell_pwm_fallback_register(struct device *dev)
> +{
> +	struct dell_pwm_backlight *pwm;
> +	struct backlight_properties props = {};
> +	struct backlight_device *bl;
> +	struct pci_dev *gpu;
> +	resource_size_t bar0;
> +	u32 freq, duty, ctl;
> +
> +	gpu = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL);
> +	if (!gpu || gpu->vendor != PCI_VENDOR_ID_INTEL) {
> +		pci_dev_put(gpu);
> +		dev_warn(dev, "No Intel GPU found, cannot fall back to PCH PWM\n");

Add include.

> +		return -ENODEV;
> +	}
> +
> +	bar0 = pci_resource_start(gpu, 0);
> +	pci_dev_put(gpu);

Don't duplicating pci_dev_put(), __free() can be used to autorelease it.

> +	if (!bar0) {
> +		dev_warn(dev, "Intel GPU BAR0 not assigned\n");
> +		return -ENODEV;
> +	}
> +
> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
> +	if (!pwm)
> +		return -ENOMEM;
> +
> +	pwm->mmio = devm_ioremap(dev, bar0 + PCH_BLC_PWM_CTL, 0x10);

SZ_xx + add include.

> +	if (!pwm->mmio) {
> +		dev_warn(dev, "Failed to ioremap PCH PWM registers\n");
> +		return -ENOMEM;
> +	}
> +
> +	ctl  = readl(pwm->mmio);
> +	freq = readl(pwm->mmio + (PCH_BLC_PWM_FREQ - PCH_BLC_PWM_CTL));
> +	duty = readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
> +
> +	if (!(ctl & BIT(31)) || freq == 0) {

Please name BIT(31) with a define.

> +		dev_warn(dev, "PCH PWM not enabled by BIOS (ctl=0x%08x freq=%u)\n",
> +			 ctl, freq);
> +		return -ENODEV;
> +	}
> +
> +	pwm->max_brightness = freq;
> +
> +	dev_info(dev, "Falling back to PCH PWM backlight (max=%u current=%u)\n",
> +		 freq, duty);
> +
> +	props.type = BACKLIGHT_PLATFORM;
> +	props.brightness = duty;
> +	props.max_brightness = freq;
> +
> +	bl = devm_backlight_device_register(dev, "dell_uart_backlight",
> +					    dev, pwm,
> +					    &dell_pwm_backlight_ops,
> +					    &props);

Fits to less lines.

You may also use up to 100 chars for code where it helps.

> +	return PTR_ERR_OR_ZERO(bl);
> +}
> +
>  static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
>  {
>  	u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN];
> @@ -300,18 +403,18 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
>  
>  	ret = dell_uart_bl_command(dell_bl, get_version, GET_CMD_LEN, resp, MAX_RESP_LEN);
>  	if (ret)
> -		return dev_err_probe(dev, ret, "getting firmware version\n");
> +		goto uart_fail;
>  
>  	dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA);
>  
>  	/* Initialize bl_power to a known value */
>  	ret = dell_uart_set_bl_power(dell_bl, BACKLIGHT_POWER_ON);
>  	if (ret)
> -		return ret;
> +		goto uart_fail;
>  
>  	ret = dell_uart_get_brightness(dell_bl);
>  	if (ret < 0)
> -		return ret;
> +		goto uart_fail;
>  
>  	props.type = BACKLIGHT_PLATFORM;
>  	props.brightness = ret;
> @@ -323,6 +426,13 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
>  						     &dell_uart_backlight_ops,
>  						     &props);
>  	return PTR_ERR_OR_ZERO(dell_bl->bl);
> +
> +uart_fail:
> +	if (ret == -ETIMEDOUT) {
> +		dev_info(dev, "UART backlight controller not responding, trying PCH PWM fallback\n");
> +		return dell_pwm_fallback_register(dev);

This looks like rollback path but isn't.

How about adding a function for the "normal" BL init so you can structure 
the code more logically.

> +	}
> +	return dev_err_probe(dev, ret, "UART backlight communication failed\n");
>  }
>  
>  static struct serdev_device_driver dell_uart_bl_serdev_driver = {
> 

-- 
 i.


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

* [PATCH v2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout
  2026-10-08 14:16 [PATCH] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout chitranshmandhaniya
  2026-10-08 14:30 ` Ilpo Järvinen
@ 2026-10-09  7:24 ` chitranshmandhaniya
  2026-10-09 10:48   ` Ilpo Järvinen
                     ` (4 more replies)
  1 sibling, 5 replies; 13+ messages in thread
From: chitranshmandhaniya @ 2026-10-09  7:24 UTC (permalink / raw)
  To: hansg
  Cc: ilpo.jarvinen, platform-driver-x86, linux-kernel, Chitransh_Mandhaniya

From: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>

Some Dell OptiPlex AIO 7410 (35W, board 01KP8D) ship with DELL0501 in
the ACPI DSDT (status = 0x0F) even though no UART scalar board is
physically present.  This causes acpi_video_get_backlight_type() to
return acpi_backlight_dell_uart, which makes i915 skip registering
intel_backlight.  The dell-uart-backlight driver then times out talking
to the absent board and the probe fails, leaving the machine with zero
backlight control.

A BIOS update fixes this on some models (e.g. OptiPlex 7420, board
0914RT) by setting DELL0501 _STA to 0, but Dell has not shipped a
corrected BIOS for the 7410 even in the latest release (1.37.0,
2026-05-28).

When the initial get_version command returns -ETIMEDOUT, fall back to
driving the backlight through the Intel PCH PWM registers (BLC_PWM_CTL /
BLC_PWM_FREQ / BLC_PWM_DUTY at MMIO BAR0 + 0xC8250).  The fallback
reads the BIOS-programmed PWM frequency as max_brightness and registers
a BACKLIGHT_PLATFORM device so that compositors (Weston, GNOME, KDE)
pick it up normally.

Tested on:
  - OptiPlex AIO 7410 35W (01KP8D) -- timeout, fallback activates, works
  - OptiPlex AIO 7420 35W (0914RT, old BIOS) -- timeout, fallback works
  - OptiPlex AIO 7420 35W (0914RT, new BIOS) -- DELL0501 absent, driver
    not loaded, intel_backlight works natively (no regression)

Signed-off-by: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>
---
v1 -> v2:
  - Use __free(pci_dev_put) for automatic PCI device cleanup (Ilpo)
  - Replace 0x10 with SZ_16, add linux/sizes.h (Ilpo)
  - Name BIT(31) as PCH_BLC_PWM_ENABLE (Ilpo)
  - Fix double-space in block comment (Ilpo)
  - Reduce devm_backlight_device_register() to fewer lines (Ilpo)
  - Extract dell_uart_bl_init() for the normal UART backlight path
    so the probe function reads more clearly (Ilpo)

 .../platform/x86/dell/dell-uart-backlight.c   | 157 +++++++++++++++---
 1 file changed, 134 insertions(+), 23 deletions(-)

diff --git a/drivers/platform/x86/dell/dell-uart-backlight.c b/drivers/platform/x86/dell/dell-uart-backlight.c
index f323a667dc2d..edc7cfe38069 100644
--- a/drivers/platform/x86/dell/dell-uart-backlight.c
+++ b/drivers/platform/x86/dell/dell-uart-backlight.c
@@ -13,10 +13,13 @@
 #include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/err.h>
+#include <linux/io.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/pci.h>
 #include <linux/platform_device.h>
 #include <linux/serdev.h>
+#include <linux/sizes.h>
 #include <linux/string.h>
 #include <linux/types.h>
 #include <linux/wait.h>
@@ -50,6 +53,12 @@
 #define MIN_RESP_LEN			3
 #define MAX_RESP_LEN			80
 
+/* PCH PWM backlight registers relative to Intel GPU MMIO BAR0 */
+#define PCH_BLC_PWM_CTL		0xC8250
+#define PCH_BLC_PWM_FREQ	0xC8254
+#define PCH_BLC_PWM_DUTY	0xC8258
+#define PCH_BLC_PWM_ENABLE	BIT(31)
+
 struct dell_uart_backlight {
 	struct mutex mutex;
 	wait_queue_head_t wait_queue;
@@ -64,6 +73,11 @@ struct dell_uart_backlight {
 	int power;
 };
 
+struct dell_pwm_backlight {
+	void __iomem *mmio;
+	u32 max_brightness;
+};
+
 /* Checksum: SUM(Length and Cmd and Data) xor 0xFF */
 static u8 dell_uart_checksum(u8 *buf, int len)
 {
@@ -267,32 +281,97 @@ static const struct serdev_device_ops dell_uart_bl_serdev_ops = {
 	.write_wakeup = serdev_device_write_wakeup,
 };
 
-static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
+/*
+ * Some Dell AIO models (e.g. OptiPlex 7410) declare DELL0501 in ACPI but
+ * have no physical UART scalar board. When the UART times out fall back
+ * to driving the Intel PCH PWM backlight registers directly.
+ */
+static int dell_pwm_update_status(struct backlight_device *bd)
 {
-	u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN];
+	struct dell_pwm_backlight *pwm = bl_get_data(bd);
+	u32 duty;
+
+	if (bd->props.power != BACKLIGHT_POWER_ON ||
+	    bd->props.state & BL_CORE_SUSPENDED)
+		duty = 0;
+	else
+		duty = bd->props.brightness;
+
+	writel(duty, pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
+	return 0;
+}
+
+static int dell_pwm_get_brightness(struct backlight_device *bd)
+{
+	struct dell_pwm_backlight *pwm = bl_get_data(bd);
+
+	return readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
+}
+
+static const struct backlight_ops dell_pwm_backlight_ops = {
+	.update_status  = dell_pwm_update_status,
+	.get_brightness = dell_pwm_get_brightness,
+};
+
+static int dell_pwm_fallback_register(struct device *dev)
+{
+	struct pci_dev *gpu __free(pci_dev_put) = NULL;
 	struct backlight_properties props = {};
-	struct dell_uart_backlight *dell_bl;
-	struct device *dev = &serdev->dev;
-	int ret;
+	struct dell_pwm_backlight *pwm;
+	struct backlight_device *bl;
+	resource_size_t bar0;
+	u32 freq, duty, ctl;
 
-	dell_bl = devm_kzalloc(dev, sizeof(*dell_bl), GFP_KERNEL);
-	if (!dell_bl)
+	gpu = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL);
+	if (!gpu || gpu->vendor != PCI_VENDOR_ID_INTEL) {
+		dev_warn(dev, "No Intel GPU found, cannot fall back to PCH PWM\n");
+		return -ENODEV;
+	}
+
+	bar0 = pci_resource_start(gpu, 0);
+	if (!bar0) {
+		dev_warn(dev, "Intel GPU BAR0 not assigned\n");
+		return -ENODEV;
+	}
+
+	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
+	if (!pwm)
 		return -ENOMEM;
 
-	mutex_init(&dell_bl->mutex);
-	init_waitqueue_head(&dell_bl->wait_queue);
-	dell_bl->dev = dev;
+	pwm->mmio = devm_ioremap(dev, bar0 + PCH_BLC_PWM_CTL, SZ_16);
+	if (!pwm->mmio) {
+		dev_warn(dev, "Failed to ioremap PCH PWM registers\n");
+		return -ENOMEM;
+	}
 
-	serdev_device_set_drvdata(serdev, dell_bl);
-	serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops);
+	ctl  = readl(pwm->mmio);
+	freq = readl(pwm->mmio + (PCH_BLC_PWM_FREQ - PCH_BLC_PWM_CTL));
+	duty = readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
 
-	ret = devm_serdev_device_open(dev, serdev);
-	if (ret)
-		return dev_err_probe(dev, ret, "opening UART device\n");
+	if (!(ctl & PCH_BLC_PWM_ENABLE) || freq == 0) {
+		dev_warn(dev, "PCH PWM not enabled by BIOS (ctl=0x%08x freq=%u)\n", ctl, freq);
+		return -ENODEV;
+	}
 
-	/* 9600 bps, no flow control, these are the default but set them to be sure */
-	serdev_device_set_baudrate(serdev, 9600);
-	serdev_device_set_flow_control(serdev, false);
+	pwm->max_brightness = freq;
+
+	dev_info(dev, "Falling back to PCH PWM backlight (max=%u current=%u)\n", freq, duty);
+
+	props.type = BACKLIGHT_PLATFORM;
+	props.brightness = duty;
+	props.max_brightness = freq;
+
+	bl = devm_backlight_device_register(dev, "dell_uart_backlight", dev, pwm,
+					    &dell_pwm_backlight_ops, &props);
+	return PTR_ERR_OR_ZERO(bl);
+}
+
+static int dell_uart_bl_init(struct device *dev, struct serdev_device *serdev,
+			     struct dell_uart_backlight *dell_bl)
+{
+	u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN];
+	struct backlight_properties props = {};
+	int ret;
 
 	get_version[0] = DELL_SOF(GET_CMD_LEN);
 	get_version[1] = CMD_GET_VERSION;
@@ -300,7 +379,7 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
 
 	ret = dell_uart_bl_command(dell_bl, get_version, GET_CMD_LEN, resp, MAX_RESP_LEN);
 	if (ret)
-		return dev_err_probe(dev, ret, "getting firmware version\n");
+		return ret;
 
 	dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA);
 
@@ -318,13 +397,45 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
 	props.max_brightness = DELL_BL_MAX_BRIGHTNESS;
 	props.power = dell_bl->power;
 
-	dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight",
-						     dev, dell_bl,
-						     &dell_uart_backlight_ops,
-						     &props);
+	dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight", dev, dell_bl,
+						     &dell_uart_backlight_ops, &props);
 	return PTR_ERR_OR_ZERO(dell_bl->bl);
 }
 
+static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
+{
+	struct dell_uart_backlight *dell_bl;
+	struct device *dev = &serdev->dev;
+	int ret;
+
+	dell_bl = devm_kzalloc(dev, sizeof(*dell_bl), GFP_KERNEL);
+	if (!dell_bl)
+		return -ENOMEM;
+
+	mutex_init(&dell_bl->mutex);
+	init_waitqueue_head(&dell_bl->wait_queue);
+	dell_bl->dev = dev;
+
+	serdev_device_set_drvdata(serdev, dell_bl);
+	serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops);
+
+	ret = devm_serdev_device_open(dev, serdev);
+	if (ret)
+		return dev_err_probe(dev, ret, "opening UART device\n");
+
+	/* 9600 bps, no flow control, these are the default but set them to be sure */
+	serdev_device_set_baudrate(serdev, 9600);
+	serdev_device_set_flow_control(serdev, false);
+
+	ret = dell_uart_bl_init(dev, serdev, dell_bl);
+	if (ret == -ETIMEDOUT) {
+		dev_info(dev, "UART backlight controller not responding, trying PCH PWM fallback\n");
+		return dell_pwm_fallback_register(dev);
+	}
+
+	return ret;
+}
+
 static struct serdev_device_driver dell_uart_bl_serdev_driver = {
 	.probe = dell_uart_bl_serdev_probe,
 	.driver = {
-- 
2.43.0


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

* Re: [PATCH v2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout
  2026-10-09  7:24 ` [PATCH v2] " chitranshmandhaniya
@ 2026-10-09 10:48   ` Ilpo Järvinen
  2026-10-09 12:26   ` [PATCH v3 0/2] " chitranshmandhaniya
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 13+ messages in thread
From: Ilpo Järvinen @ 2026-10-09 10:48 UTC (permalink / raw)
  To: chitranshmandhaniya
  Cc: Hans de Goede, platform-driver-x86, LKML, Chitransh_Mandhaniya

On Fri, 9 Oct 2026, chitranshmandhaniya@gmail.com wrote:

> From: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>
> 
> Some Dell OptiPlex AIO 7410 (35W, board 01KP8D) ship with DELL0501 in
> the ACPI DSDT (status = 0x0F) even though no UART scalar board is
> physically present.  This causes acpi_video_get_backlight_type() to
> return acpi_backlight_dell_uart, which makes i915 skip registering
> intel_backlight.  The dell-uart-backlight driver then times out talking
> to the absent board and the probe fails, leaving the machine with zero
> backlight control.
> 
> A BIOS update fixes this on some models (e.g. OptiPlex 7420, board
> 0914RT) by setting DELL0501 _STA to 0, but Dell has not shipped a
> corrected BIOS for the 7410 even in the latest release (1.37.0,
> 2026-05-28).
> 
> When the initial get_version command returns -ETIMEDOUT, fall back to
> driving the backlight through the Intel PCH PWM registers (BLC_PWM_CTL /
> BLC_PWM_FREQ / BLC_PWM_DUTY at MMIO BAR0 + 0xC8250).  The fallback
> reads the BIOS-programmed PWM frequency as max_brightness and registers
> a BACKLIGHT_PLATFORM device so that compositors (Weston, GNOME, KDE)
> pick it up normally.
> 
> Tested on:
>   - OptiPlex AIO 7410 35W (01KP8D) -- timeout, fallback activates, works
>   - OptiPlex AIO 7420 35W (0914RT, old BIOS) -- timeout, fallback works
>   - OptiPlex AIO 7420 35W (0914RT, new BIOS) -- DELL0501 absent, driver
>     not loaded, intel_backlight works natively (no regression)
> 
> Signed-off-by: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>
> ---
> v1 -> v2:
>   - Use __free(pci_dev_put) for automatic PCI device cleanup (Ilpo)
>   - Replace 0x10 with SZ_16, add linux/sizes.h (Ilpo)
>   - Name BIT(31) as PCH_BLC_PWM_ENABLE (Ilpo)
>   - Fix double-space in block comment (Ilpo)
>   - Reduce devm_backlight_device_register() to fewer lines (Ilpo)

>   - Extract dell_uart_bl_init() for the normal UART backlight path
>     so the probe function reads more clearly (Ilpo)

I suggest you do dell_uart_bl_init() refactor in a preparetory patch and 
do a two patch series. I believe it would make the patches easier to 
review.

-- 
 i.


>  .../platform/x86/dell/dell-uart-backlight.c   | 157 +++++++++++++++---
>  1 file changed, 134 insertions(+), 23 deletions(-)
> 
> diff --git a/drivers/platform/x86/dell/dell-uart-backlight.c b/drivers/platform/x86/dell/dell-uart-backlight.c
> index f323a667dc2d..edc7cfe38069 100644
> --- a/drivers/platform/x86/dell/dell-uart-backlight.c
> +++ b/drivers/platform/x86/dell/dell-uart-backlight.c
> @@ -13,10 +13,13 @@
>  #include <linux/delay.h>
>  #include <linux/device.h>
>  #include <linux/err.h>
> +#include <linux/io.h>
>  #include <linux/module.h>
>  #include <linux/mutex.h>
> +#include <linux/pci.h>
>  #include <linux/platform_device.h>
>  #include <linux/serdev.h>
> +#include <linux/sizes.h>
>  #include <linux/string.h>
>  #include <linux/types.h>
>  #include <linux/wait.h>
> @@ -50,6 +53,12 @@
>  #define MIN_RESP_LEN			3
>  #define MAX_RESP_LEN			80
>  
> +/* PCH PWM backlight registers relative to Intel GPU MMIO BAR0 */
> +#define PCH_BLC_PWM_CTL		0xC8250
> +#define PCH_BLC_PWM_FREQ	0xC8254
> +#define PCH_BLC_PWM_DUTY	0xC8258
> +#define PCH_BLC_PWM_ENABLE	BIT(31)
> +
>  struct dell_uart_backlight {
>  	struct mutex mutex;
>  	wait_queue_head_t wait_queue;
> @@ -64,6 +73,11 @@ struct dell_uart_backlight {
>  	int power;
>  };
>  
> +struct dell_pwm_backlight {
> +	void __iomem *mmio;
> +	u32 max_brightness;
> +};
> +
>  /* Checksum: SUM(Length and Cmd and Data) xor 0xFF */
>  static u8 dell_uart_checksum(u8 *buf, int len)
>  {
> @@ -267,32 +281,97 @@ static const struct serdev_device_ops dell_uart_bl_serdev_ops = {
>  	.write_wakeup = serdev_device_write_wakeup,
>  };
>  
> -static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
> +/*
> + * Some Dell AIO models (e.g. OptiPlex 7410) declare DELL0501 in ACPI but
> + * have no physical UART scalar board. When the UART times out fall back
> + * to driving the Intel PCH PWM backlight registers directly.
> + */
> +static int dell_pwm_update_status(struct backlight_device *bd)
>  {
> -	u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN];
> +	struct dell_pwm_backlight *pwm = bl_get_data(bd);
> +	u32 duty;
> +
> +	if (bd->props.power != BACKLIGHT_POWER_ON ||
> +	    bd->props.state & BL_CORE_SUSPENDED)
> +		duty = 0;
> +	else
> +		duty = bd->props.brightness;
> +
> +	writel(duty, pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
> +	return 0;
> +}
> +
> +static int dell_pwm_get_brightness(struct backlight_device *bd)
> +{
> +	struct dell_pwm_backlight *pwm = bl_get_data(bd);
> +
> +	return readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
> +}
> +
> +static const struct backlight_ops dell_pwm_backlight_ops = {
> +	.update_status  = dell_pwm_update_status,
> +	.get_brightness = dell_pwm_get_brightness,
> +};
> +
> +static int dell_pwm_fallback_register(struct device *dev)
> +{
> +	struct pci_dev *gpu __free(pci_dev_put) = NULL;
>  	struct backlight_properties props = {};
> -	struct dell_uart_backlight *dell_bl;
> -	struct device *dev = &serdev->dev;
> -	int ret;
> +	struct dell_pwm_backlight *pwm;
> +	struct backlight_device *bl;
> +	resource_size_t bar0;
> +	u32 freq, duty, ctl;
>  
> -	dell_bl = devm_kzalloc(dev, sizeof(*dell_bl), GFP_KERNEL);
> -	if (!dell_bl)
> +	gpu = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL);
> +	if (!gpu || gpu->vendor != PCI_VENDOR_ID_INTEL) {
> +		dev_warn(dev, "No Intel GPU found, cannot fall back to PCH PWM\n");
> +		return -ENODEV;
> +	}
> +
> +	bar0 = pci_resource_start(gpu, 0);
> +	if (!bar0) {
> +		dev_warn(dev, "Intel GPU BAR0 not assigned\n");
> +		return -ENODEV;
> +	}
> +
> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
> +	if (!pwm)
>  		return -ENOMEM;
>  
> -	mutex_init(&dell_bl->mutex);
> -	init_waitqueue_head(&dell_bl->wait_queue);
> -	dell_bl->dev = dev;
> +	pwm->mmio = devm_ioremap(dev, bar0 + PCH_BLC_PWM_CTL, SZ_16);
> +	if (!pwm->mmio) {
> +		dev_warn(dev, "Failed to ioremap PCH PWM registers\n");
> +		return -ENOMEM;
> +	}
>  
> -	serdev_device_set_drvdata(serdev, dell_bl);
> -	serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops);
> +	ctl  = readl(pwm->mmio);
> +	freq = readl(pwm->mmio + (PCH_BLC_PWM_FREQ - PCH_BLC_PWM_CTL));
> +	duty = readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
>  
> -	ret = devm_serdev_device_open(dev, serdev);
> -	if (ret)
> -		return dev_err_probe(dev, ret, "opening UART device\n");
> +	if (!(ctl & PCH_BLC_PWM_ENABLE) || freq == 0) {
> +		dev_warn(dev, "PCH PWM not enabled by BIOS (ctl=0x%08x freq=%u)\n", ctl, freq);
> +		return -ENODEV;
> +	}
>  
> -	/* 9600 bps, no flow control, these are the default but set them to be sure */
> -	serdev_device_set_baudrate(serdev, 9600);
> -	serdev_device_set_flow_control(serdev, false);
> +	pwm->max_brightness = freq;
> +
> +	dev_info(dev, "Falling back to PCH PWM backlight (max=%u current=%u)\n", freq, duty);
> +
> +	props.type = BACKLIGHT_PLATFORM;
> +	props.brightness = duty;
> +	props.max_brightness = freq;
> +
> +	bl = devm_backlight_device_register(dev, "dell_uart_backlight", dev, pwm,
> +					    &dell_pwm_backlight_ops, &props);
> +	return PTR_ERR_OR_ZERO(bl);
> +}
> +
> +static int dell_uart_bl_init(struct device *dev, struct serdev_device *serdev,
> +			     struct dell_uart_backlight *dell_bl)
> +{
> +	u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN];
> +	struct backlight_properties props = {};
> +	int ret;
>  
>  	get_version[0] = DELL_SOF(GET_CMD_LEN);
>  	get_version[1] = CMD_GET_VERSION;
> @@ -300,7 +379,7 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
>  
>  	ret = dell_uart_bl_command(dell_bl, get_version, GET_CMD_LEN, resp, MAX_RESP_LEN);
>  	if (ret)
> -		return dev_err_probe(dev, ret, "getting firmware version\n");
> +		return ret;
>  
>  	dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA);
>  
> @@ -318,13 +397,45 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
>  	props.max_brightness = DELL_BL_MAX_BRIGHTNESS;
>  	props.power = dell_bl->power;
>  
> -	dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight",
> -						     dev, dell_bl,
> -						     &dell_uart_backlight_ops,
> -						     &props);
> +	dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight", dev, dell_bl,
> +						     &dell_uart_backlight_ops, &props);
>  	return PTR_ERR_OR_ZERO(dell_bl->bl);
>  }
>  
> +static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
> +{
> +	struct dell_uart_backlight *dell_bl;
> +	struct device *dev = &serdev->dev;
> +	int ret;
> +
> +	dell_bl = devm_kzalloc(dev, sizeof(*dell_bl), GFP_KERNEL);
> +	if (!dell_bl)
> +		return -ENOMEM;
> +
> +	mutex_init(&dell_bl->mutex);
> +	init_waitqueue_head(&dell_bl->wait_queue);
> +	dell_bl->dev = dev;
> +
> +	serdev_device_set_drvdata(serdev, dell_bl);
> +	serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops);
> +
> +	ret = devm_serdev_device_open(dev, serdev);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "opening UART device\n");
> +
> +	/* 9600 bps, no flow control, these are the default but set them to be sure */
> +	serdev_device_set_baudrate(serdev, 9600);
> +	serdev_device_set_flow_control(serdev, false);
> +
> +	ret = dell_uart_bl_init(dev, serdev, dell_bl);
> +	if (ret == -ETIMEDOUT) {
> +		dev_info(dev, "UART backlight controller not responding, trying PCH PWM fallback\n");
> +		return dell_pwm_fallback_register(dev);
> +	}
> +
> +	return ret;
> +}
> +
>  static struct serdev_device_driver dell_uart_bl_serdev_driver = {
>  	.probe = dell_uart_bl_serdev_probe,
>  	.driver = {
> 

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

* [PATCH v3 0/2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout
  2026-10-09  7:24 ` [PATCH v2] " chitranshmandhaniya
  2026-10-09 10:48   ` Ilpo Järvinen
@ 2026-10-09 12:26   ` chitranshmandhaniya
  2026-10-09 12:26     ` [PATCH v3 1/2] platform/x86: dell-uart-backlight: extract dell_uart_bl_init() from probe chitranshmandhaniya
  2026-10-09 12:26     ` [PATCH v3 2/2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout chitranshmandhaniya
  2026-10-09 23:46   ` [PATCH v2] " kernel test robot
                     ` (2 subsequent siblings)
  4 siblings, 2 replies; 13+ messages in thread
From: chitranshmandhaniya @ 2026-10-09 12:26 UTC (permalink / raw)
  To: hansg
  Cc: ilpo.jarvinen, platform-driver-x86, linux-kernel, Chitransh_Mandhaniya

From: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>

Some Dell OptiPlex AIO 7410 (35W) ship with DELL0501 in ACPI but no
physical UART scalar board, leaving the machine with zero backlight
control after the UART probe times out.

Patch 1 is a preparatory refactor that extracts the UART initialization
sequence into dell_uart_bl_init() so the probe function can inspect its
return value.

Patch 2 adds a PCH PWM fallback path: when dell_uart_bl_init() returns
-ETIMEDOUT, the driver reads the Intel PCH BLC_PWM registers and
registers a BACKLIGHT_PLATFORM device.

v2 -> v3:
  - Split into two patches: preparatory refactor + fallback (Ilpo)

v1 -> v2:
  - Use __free(pci_dev_put) for automatic PCI device cleanup (Ilpo)
  - Replace 0x10 with SZ_16, add linux/sizes.h (Ilpo)
  - Name BIT(31) as PCH_BLC_PWM_ENABLE (Ilpo)
  - Fix double-space in block comment (Ilpo)
  - Reduce devm_backlight_device_register() to fewer lines (Ilpo)
  - Extract dell_uart_bl_init() for the normal UART backlight path
    so the probe function reads more clearly (Ilpo)

Chitransh Mandhaniya (2):
  platform/x86: dell-uart-backlight: extract dell_uart_bl_init() from
    probe
  platform/x86: dell-uart-backlight: fall back to PCH PWM on UART
    timeout

 .../platform/x86/dell/dell-uart-backlight.c   | 156 +++++++++++++++---
 1 file changed, 133 insertions(+), 23 deletions(-)

-- 
2.43.0


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

* [PATCH v3 1/2] platform/x86: dell-uart-backlight: extract dell_uart_bl_init() from probe
  2026-10-09 12:26   ` [PATCH v3 0/2] " chitranshmandhaniya
@ 2026-10-09 12:26     ` chitranshmandhaniya
  2026-10-09 12:26     ` [PATCH v3 2/2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout chitranshmandhaniya
  1 sibling, 0 replies; 13+ messages in thread
From: chitranshmandhaniya @ 2026-10-09 12:26 UTC (permalink / raw)
  To: hansg
  Cc: ilpo.jarvinen, platform-driver-x86, linux-kernel, Chitransh_Mandhaniya

From: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>

Move the UART backlight initialization sequence (get_version,
set_bl_power, get_brightness and backlight device registration) out of
dell_uart_bl_serdev_probe() into a new dell_uart_bl_init() helper.

No functional change. This is a preparatory refactor so that a
follow-up patch can inspect the return value of dell_uart_bl_init()
and take an alternative code path on failure.

Tested on OptiPlex AIO 7410 35W (01KP8D) and OptiPlex AIO 7420 35W
(0914RT) -- driver behaviour unchanged, backlight probes identically.

Signed-off-by: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>
---
 .../platform/x86/dell/dell-uart-backlight.c   | 59 ++++++++++---------
 1 file changed, 32 insertions(+), 27 deletions(-)

diff --git a/drivers/platform/x86/dell/dell-uart-backlight.c b/drivers/platform/x86/dell/dell-uart-backlight.c
index f323a667dc2d..e3b7f1beda5b 100644
--- a/drivers/platform/x86/dell/dell-uart-backlight.c
+++ b/drivers/platform/x86/dell/dell-uart-backlight.c
@@ -267,40 +267,19 @@ static const struct serdev_device_ops dell_uart_bl_serdev_ops = {
 	.write_wakeup = serdev_device_write_wakeup,
 };
 
-static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
+static int dell_uart_bl_init(struct device *dev, struct dell_uart_backlight *dell_bl)
 {
 	u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN];
 	struct backlight_properties props = {};
-	struct dell_uart_backlight *dell_bl;
-	struct device *dev = &serdev->dev;
 	int ret;
 
-	dell_bl = devm_kzalloc(dev, sizeof(*dell_bl), GFP_KERNEL);
-	if (!dell_bl)
-		return -ENOMEM;
-
-	mutex_init(&dell_bl->mutex);
-	init_waitqueue_head(&dell_bl->wait_queue);
-	dell_bl->dev = dev;
-
-	serdev_device_set_drvdata(serdev, dell_bl);
-	serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops);
-
-	ret = devm_serdev_device_open(dev, serdev);
-	if (ret)
-		return dev_err_probe(dev, ret, "opening UART device\n");
-
-	/* 9600 bps, no flow control, these are the default but set them to be sure */
-	serdev_device_set_baudrate(serdev, 9600);
-	serdev_device_set_flow_control(serdev, false);
-
 	get_version[0] = DELL_SOF(GET_CMD_LEN);
 	get_version[1] = CMD_GET_VERSION;
 	get_version[2] = dell_uart_checksum(get_version, 2);
 
 	ret = dell_uart_bl_command(dell_bl, get_version, GET_CMD_LEN, resp, MAX_RESP_LEN);
 	if (ret)
-		return dev_err_probe(dev, ret, "getting firmware version\n");
+		return ret;
 
 	dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA);
 
@@ -318,13 +297,39 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
 	props.max_brightness = DELL_BL_MAX_BRIGHTNESS;
 	props.power = dell_bl->power;
 
-	dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight",
-						     dev, dell_bl,
-						     &dell_uart_backlight_ops,
-						     &props);
+	dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight", dev, dell_bl,
+						     &dell_uart_backlight_ops, &props);
 	return PTR_ERR_OR_ZERO(dell_bl->bl);
 }
 
+static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
+{
+	struct dell_uart_backlight *dell_bl;
+	struct device *dev = &serdev->dev;
+	int ret;
+
+	dell_bl = devm_kzalloc(dev, sizeof(*dell_bl), GFP_KERNEL);
+	if (!dell_bl)
+		return -ENOMEM;
+
+	mutex_init(&dell_bl->mutex);
+	init_waitqueue_head(&dell_bl->wait_queue);
+	dell_bl->dev = dev;
+
+	serdev_device_set_drvdata(serdev, dell_bl);
+	serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops);
+
+	ret = devm_serdev_device_open(dev, serdev);
+	if (ret)
+		return dev_err_probe(dev, ret, "opening UART device\n");
+
+	/* 9600 bps, no flow control, these are the default but set them to be sure */
+	serdev_device_set_baudrate(serdev, 9600);
+	serdev_device_set_flow_control(serdev, false);
+
+	return dell_uart_bl_init(dev, dell_bl);
+}
+
 static struct serdev_device_driver dell_uart_bl_serdev_driver = {
 	.probe = dell_uart_bl_serdev_probe,
 	.driver = {
-- 
2.43.0


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

* [PATCH v3 2/2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout
  2026-10-09 12:26   ` [PATCH v3 0/2] " chitranshmandhaniya
  2026-10-09 12:26     ` [PATCH v3 1/2] platform/x86: dell-uart-backlight: extract dell_uart_bl_init() from probe chitranshmandhaniya
@ 2026-10-09 12:26     ` chitranshmandhaniya
  1 sibling, 0 replies; 13+ messages in thread
From: chitranshmandhaniya @ 2026-10-09 12:26 UTC (permalink / raw)
  To: hansg
  Cc: ilpo.jarvinen, platform-driver-x86, linux-kernel, Chitransh_Mandhaniya

From: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>

Some Dell OptiPlex AIO 7410 (35W, board 01KP8D) ship with DELL0501 in
the ACPI DSDT (status = 0x0F) even though no UART scalar board is
physically present.  This causes acpi_video_get_backlight_type() to
return acpi_backlight_dell_uart, which makes i915 skip registering
intel_backlight.  The dell-uart-backlight driver then times out talking
to the absent board and the probe fails, leaving the machine with zero
backlight control.

A BIOS update fixes this on some models (e.g. OptiPlex 7420, board
0914RT) by setting DELL0501 _STA to 0, but Dell has not shipped a
corrected BIOS for the 7410 even in the latest release (1.37.0,
2026-05-28).

When the initial get_version command returns -ETIMEDOUT, fall back to
driving the backlight through the Intel PCH PWM registers (BLC_PWM_CTL /
BLC_PWM_FREQ / BLC_PWM_DUTY at MMIO BAR0 + 0xC8250).  The fallback
reads the BIOS-programmed PWM frequency as max_brightness and registers
a BACKLIGHT_PLATFORM device so that compositors (Weston, GNOME, KDE)
pick it up normally.

Tested on:
  - OptiPlex AIO 7410 35W (01KP8D) -- UART times out, PCH PWM fallback
    activates, backlight fully functional via sysfs and compositor.
  - OptiPlex AIO 7420 35W (0914RT, old BIOS) -- same timeout and
    fallback, backlight works.
  - OptiPlex AIO 7420 35W (0914RT, new BIOS) -- DELL0501 absent, driver
    not loaded, intel_backlight works natively (no regression).

Signed-off-by: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>
---
 .../platform/x86/dell/dell-uart-backlight.c   | 107 +++++++++++++++++-
 1 file changed, 106 insertions(+), 1 deletion(-)

diff --git a/drivers/platform/x86/dell/dell-uart-backlight.c b/drivers/platform/x86/dell/dell-uart-backlight.c
index e3b7f1beda5b..3179f3a3f094 100644
--- a/drivers/platform/x86/dell/dell-uart-backlight.c
+++ b/drivers/platform/x86/dell/dell-uart-backlight.c
@@ -13,10 +13,13 @@
 #include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/err.h>
+#include <linux/io.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/pci.h>
 #include <linux/platform_device.h>
 #include <linux/serdev.h>
+#include <linux/sizes.h>
 #include <linux/string.h>
 #include <linux/types.h>
 #include <linux/wait.h>
@@ -50,6 +53,12 @@
 #define MIN_RESP_LEN			3
 #define MAX_RESP_LEN			80
 
+/* PCH PWM backlight registers relative to Intel GPU MMIO BAR0 */
+#define PCH_BLC_PWM_CTL		0xC8250
+#define PCH_BLC_PWM_FREQ	0xC8254
+#define PCH_BLC_PWM_DUTY	0xC8258
+#define PCH_BLC_PWM_ENABLE	BIT(31)
+
 struct dell_uart_backlight {
 	struct mutex mutex;
 	wait_queue_head_t wait_queue;
@@ -64,6 +73,11 @@ struct dell_uart_backlight {
 	int power;
 };
 
+struct dell_pwm_backlight {
+	void __iomem *mmio;
+	u32 max_brightness;
+};
+
 /* Checksum: SUM(Length and Cmd and Data) xor 0xFF */
 static u8 dell_uart_checksum(u8 *buf, int len)
 {
@@ -267,6 +281,91 @@ static const struct serdev_device_ops dell_uart_bl_serdev_ops = {
 	.write_wakeup = serdev_device_write_wakeup,
 };
 
+/*
+ * Some Dell AIO models (e.g. OptiPlex 7410) declare DELL0501 in ACPI but
+ * have no physical UART scalar board. When the UART times out fall back
+ * to driving the Intel PCH PWM backlight registers directly.
+ */
+static int dell_pwm_update_status(struct backlight_device *bd)
+{
+	struct dell_pwm_backlight *pwm = bl_get_data(bd);
+	u32 duty;
+
+	if (bd->props.power != BACKLIGHT_POWER_ON ||
+	    bd->props.state & BL_CORE_SUSPENDED)
+		duty = 0;
+	else
+		duty = bd->props.brightness;
+
+	writel(duty, pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
+	return 0;
+}
+
+static int dell_pwm_get_brightness(struct backlight_device *bd)
+{
+	struct dell_pwm_backlight *pwm = bl_get_data(bd);
+
+	return readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
+}
+
+static const struct backlight_ops dell_pwm_backlight_ops = {
+	.update_status  = dell_pwm_update_status,
+	.get_brightness = dell_pwm_get_brightness,
+};
+
+static int dell_pwm_fallback_register(struct device *dev)
+{
+	struct pci_dev *gpu __free(pci_dev_put) = NULL;
+	struct backlight_properties props = {};
+	struct dell_pwm_backlight *pwm;
+	struct backlight_device *bl;
+	resource_size_t bar0;
+	u32 freq, duty, ctl;
+
+	gpu = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL);
+	if (!gpu || gpu->vendor != PCI_VENDOR_ID_INTEL) {
+		dev_warn(dev, "No Intel GPU found, cannot fall back to PCH PWM\n");
+		return -ENODEV;
+	}
+
+	bar0 = pci_resource_start(gpu, 0);
+	if (!bar0) {
+		dev_warn(dev, "Intel GPU BAR0 not assigned\n");
+		return -ENODEV;
+	}
+
+	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
+	if (!pwm)
+		return -ENOMEM;
+
+	pwm->mmio = devm_ioremap(dev, bar0 + PCH_BLC_PWM_CTL, SZ_16);
+	if (!pwm->mmio) {
+		dev_warn(dev, "Failed to ioremap PCH PWM registers\n");
+		return -ENOMEM;
+	}
+
+	ctl  = readl(pwm->mmio);
+	freq = readl(pwm->mmio + (PCH_BLC_PWM_FREQ - PCH_BLC_PWM_CTL));
+	duty = readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
+
+	if (!(ctl & PCH_BLC_PWM_ENABLE) || freq == 0) {
+		dev_warn(dev, "PCH PWM not enabled by BIOS (ctl=0x%08x freq=%u)\n", ctl, freq);
+		return -ENODEV;
+	}
+
+	pwm->max_brightness = freq;
+
+	dev_info(dev, "Falling back to PCH PWM backlight (max=%u current=%u)\n", freq, duty);
+
+	props.type = BACKLIGHT_PLATFORM;
+	props.brightness = duty;
+	props.max_brightness = freq;
+
+	bl = devm_backlight_device_register(dev, "dell_uart_backlight", dev, pwm,
+					    &dell_pwm_backlight_ops, &props);
+	return PTR_ERR_OR_ZERO(bl);
+}
+
 static int dell_uart_bl_init(struct device *dev, struct dell_uart_backlight *dell_bl)
 {
 	u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN];
@@ -327,7 +426,13 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
 	serdev_device_set_baudrate(serdev, 9600);
 	serdev_device_set_flow_control(serdev, false);
 
-	return dell_uart_bl_init(dev, dell_bl);
+	ret = dell_uart_bl_init(dev, dell_bl);
+	if (ret == -ETIMEDOUT) {
+		dev_info(dev, "UART backlight controller not responding, trying PCH PWM fallback\n");
+		return dell_pwm_fallback_register(dev);
+	}
+
+	return ret;
 }
 
 static struct serdev_device_driver dell_uart_bl_serdev_driver = {
-- 
2.43.0


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

* Re: [PATCH v2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout
  2026-10-09  7:24 ` [PATCH v2] " chitranshmandhaniya
  2026-10-09 10:48   ` Ilpo Järvinen
  2026-10-09 12:26   ` [PATCH v3 0/2] " chitranshmandhaniya
@ 2026-10-09 23:46   ` kernel test robot
  2026-10-10 10:20   ` [PATCH v4 0/2] " chitranshmandhaniya
  2026-10-10 12:48   ` [PATCH v5] ACPI: video: force native backlight on Dell OptiPlex AIO 7410 35W chitranshmandhaniya
  4 siblings, 0 replies; 13+ messages in thread
From: kernel test robot @ 2026-10-09 23:46 UTC (permalink / raw)
  To: chitranshmandhaniya, hansg
  Cc: oe-kbuild-all, ilpo.jarvinen, platform-driver-x86, linux-kernel,
	Chitransh_Mandhaniya

Hi,

kernel test robot noticed the following build errors:

[auto build test ERROR on linus/master]
[also build test ERROR on v7.3-rc6 next-20261008]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/chitranshmandhaniya-gmail-com/platform-x86-dell-uart-backlight-fall-back-to-PCH-PWM-on-UART-timeout/20261009-125401
base:   linus/master
patch link:    https://lore.kernel.org/r/20261009072401.52509-1-chitranshmandhaniya%40gmail.com
patch subject: [PATCH v2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout
config: i386-buildonly-randconfig-1000-20261010 (https://download.01.org/0day-ci/archive/20261010/202610100706.x2lcbo7S-lkp@intel.com/config)
compiler: gcc-13 (Debian 13.3.0-16) 13.3.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261010/202610100706.x2lcbo7S-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202610100706.x2lcbo7S-lkp@intel.com/

All errors (new ones prefixed by >>):

   drivers/platform/x86/dell/dell-uart-backlight.c: In function 'dell_pwm_fallback_register':
>> drivers/platform/x86/dell/dell-uart-backlight.c:318:16: error: cleanup argument not a function
     318 |         struct pci_dev *gpu __free(pci_dev_put) = NULL;
         |                ^~~~~~~


vim +318 drivers/platform/x86/dell/dell-uart-backlight.c

   315	
   316	static int dell_pwm_fallback_register(struct device *dev)
   317	{
 > 318		struct pci_dev *gpu __free(pci_dev_put) = NULL;
   319		struct backlight_properties props = {};
   320		struct dell_pwm_backlight *pwm;
   321		struct backlight_device *bl;
   322		resource_size_t bar0;
   323		u32 freq, duty, ctl;
   324	
   325		gpu = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL);
   326		if (!gpu || gpu->vendor != PCI_VENDOR_ID_INTEL) {
   327			dev_warn(dev, "No Intel GPU found, cannot fall back to PCH PWM\n");
   328			return -ENODEV;
   329		}
   330	
   331		bar0 = pci_resource_start(gpu, 0);
   332		if (!bar0) {
   333			dev_warn(dev, "Intel GPU BAR0 not assigned\n");
   334			return -ENODEV;
   335		}
   336	
   337		pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
   338		if (!pwm)
   339			return -ENOMEM;
   340	
   341		pwm->mmio = devm_ioremap(dev, bar0 + PCH_BLC_PWM_CTL, SZ_16);
   342		if (!pwm->mmio) {
   343			dev_warn(dev, "Failed to ioremap PCH PWM registers\n");
   344			return -ENOMEM;
   345		}
   346	
   347		ctl  = readl(pwm->mmio);
   348		freq = readl(pwm->mmio + (PCH_BLC_PWM_FREQ - PCH_BLC_PWM_CTL));
   349		duty = readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
   350	
   351		if (!(ctl & PCH_BLC_PWM_ENABLE) || freq == 0) {
   352			dev_warn(dev, "PCH PWM not enabled by BIOS (ctl=0x%08x freq=%u)\n", ctl, freq);
   353			return -ENODEV;
   354		}
   355	
   356		pwm->max_brightness = freq;
   357	
   358		dev_info(dev, "Falling back to PCH PWM backlight (max=%u current=%u)\n", freq, duty);
   359	
   360		props.type = BACKLIGHT_PLATFORM;
   361		props.brightness = duty;
   362		props.max_brightness = freq;
   363	
   364		bl = devm_backlight_device_register(dev, "dell_uart_backlight", dev, pwm,
   365						    &dell_pwm_backlight_ops, &props);
   366		return PTR_ERR_OR_ZERO(bl);
   367	}
   368	

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* [PATCH v4 0/2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout
  2026-10-09  7:24 ` [PATCH v2] " chitranshmandhaniya
                     ` (2 preceding siblings ...)
  2026-10-09 23:46   ` [PATCH v2] " kernel test robot
@ 2026-10-10 10:20   ` chitranshmandhaniya
  2026-10-10 10:20     ` [PATCH v4 1/2] platform/x86: dell-uart-backlight: extract dell_uart_bl_init() from probe chitranshmandhaniya
                       ` (2 more replies)
  2026-10-10 12:48   ` [PATCH v5] ACPI: video: force native backlight on Dell OptiPlex AIO 7410 35W chitranshmandhaniya
  4 siblings, 3 replies; 13+ messages in thread
From: chitranshmandhaniya @ 2026-10-10 10:20 UTC (permalink / raw)
  To: ilpo.jarvinen
  Cc: hansg, platform-driver-x86, linux-kernel, Chitransh_Mandhaniya

From: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>

Some Dell OptiPlex AIO 7410 (35W) ship with DELL0501 in ACPI but no
physical UART scalar board, leaving the machine with zero backlight
control after the UART probe times out.

Patch 1 is a preparatory refactor that extracts the UART initialization
sequence into dell_uart_bl_init() so the probe function can inspect its
return value.

Patch 2 adds a PCH PWM fallback path: when dell_uart_bl_init() returns
-ETIMEDOUT, the driver reads the Intel PCH BLC_PWM registers and
registers a BACKLIGHT_PLATFORM device.

v3 -> v4:
  - Revert __free(pci_dev_put) to manual pci_dev_put() calls to fix
    build error when CONFIG_PCI is disabled (kernel test robot)

v2 -> v3:
  - Split into two patches: preparatory refactor + fallback (Ilpo)

v1 -> v2:
  - Use SZ_16 instead of 0x10, add linux/sizes.h (Ilpo)
  - Name BIT(31) as PCH_BLC_PWM_ENABLE (Ilpo)
  - Fix double-space in block comment (Ilpo)
  - Reduce devm_backlight_device_register() to fewer lines (Ilpo)
  - Extract dell_uart_bl_init() for the normal UART backlight path (Ilpo)

Chitransh Mandhaniya (2):
  platform/x86: dell-uart-backlight: extract dell_uart_bl_init() from
    probe
  platform/x86: dell-uart-backlight: fall back to PCH PWM on UART
    timeout

 .../platform/x86/dell/dell-uart-backlight.c   | 158 +++++++++++++++---
 1 file changed, 135 insertions(+), 23 deletions(-)

-- 
2.43.0


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

* [PATCH v4 1/2] platform/x86: dell-uart-backlight: extract dell_uart_bl_init() from probe
  2026-10-10 10:20   ` [PATCH v4 0/2] " chitranshmandhaniya
@ 2026-10-10 10:20     ` chitranshmandhaniya
  2026-10-10 10:20     ` [PATCH v4 2/2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout chitranshmandhaniya
  2026-10-10 11:41     ` [PATCH v4 0/2] " Hans de Goede
  2 siblings, 0 replies; 13+ messages in thread
From: chitranshmandhaniya @ 2026-10-10 10:20 UTC (permalink / raw)
  To: ilpo.jarvinen
  Cc: hansg, platform-driver-x86, linux-kernel, Chitransh_Mandhaniya

From: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>

Move the UART backlight initialization sequence (get_version,
set_bl_power, get_brightness and backlight device registration) out of
dell_uart_bl_serdev_probe() into a new dell_uart_bl_init() helper.

No functional change. This is a preparatory refactor so that a
follow-up patch can inspect the return value of dell_uart_bl_init()
and take an alternative code path on failure.

Tested on OptiPlex AIO 7410 35W (01KP8D) and OptiPlex AIO 7420 35W
(0914RT) -- driver behaviour unchanged, backlight probes identically.

Signed-off-by: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>
---
 .../platform/x86/dell/dell-uart-backlight.c   | 59 ++++++++++---------
 1 file changed, 32 insertions(+), 27 deletions(-)

diff --git a/drivers/platform/x86/dell/dell-uart-backlight.c b/drivers/platform/x86/dell/dell-uart-backlight.c
index f323a667dc2d..e3b7f1beda5b 100644
--- a/drivers/platform/x86/dell/dell-uart-backlight.c
+++ b/drivers/platform/x86/dell/dell-uart-backlight.c
@@ -267,40 +267,19 @@ static const struct serdev_device_ops dell_uart_bl_serdev_ops = {
 	.write_wakeup = serdev_device_write_wakeup,
 };
 
-static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
+static int dell_uart_bl_init(struct device *dev, struct dell_uart_backlight *dell_bl)
 {
 	u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN];
 	struct backlight_properties props = {};
-	struct dell_uart_backlight *dell_bl;
-	struct device *dev = &serdev->dev;
 	int ret;
 
-	dell_bl = devm_kzalloc(dev, sizeof(*dell_bl), GFP_KERNEL);
-	if (!dell_bl)
-		return -ENOMEM;
-
-	mutex_init(&dell_bl->mutex);
-	init_waitqueue_head(&dell_bl->wait_queue);
-	dell_bl->dev = dev;
-
-	serdev_device_set_drvdata(serdev, dell_bl);
-	serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops);
-
-	ret = devm_serdev_device_open(dev, serdev);
-	if (ret)
-		return dev_err_probe(dev, ret, "opening UART device\n");
-
-	/* 9600 bps, no flow control, these are the default but set them to be sure */
-	serdev_device_set_baudrate(serdev, 9600);
-	serdev_device_set_flow_control(serdev, false);
-
 	get_version[0] = DELL_SOF(GET_CMD_LEN);
 	get_version[1] = CMD_GET_VERSION;
 	get_version[2] = dell_uart_checksum(get_version, 2);
 
 	ret = dell_uart_bl_command(dell_bl, get_version, GET_CMD_LEN, resp, MAX_RESP_LEN);
 	if (ret)
-		return dev_err_probe(dev, ret, "getting firmware version\n");
+		return ret;
 
 	dev_dbg(dev, "Firmware version: %.*s\n", resp[RESP_LEN] - 3, resp + RESP_DATA);
 
@@ -318,13 +297,39 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
 	props.max_brightness = DELL_BL_MAX_BRIGHTNESS;
 	props.power = dell_bl->power;
 
-	dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight",
-						     dev, dell_bl,
-						     &dell_uart_backlight_ops,
-						     &props);
+	dell_bl->bl = devm_backlight_device_register(dev, "dell_uart_backlight", dev, dell_bl,
+						     &dell_uart_backlight_ops, &props);
 	return PTR_ERR_OR_ZERO(dell_bl->bl);
 }
 
+static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
+{
+	struct dell_uart_backlight *dell_bl;
+	struct device *dev = &serdev->dev;
+	int ret;
+
+	dell_bl = devm_kzalloc(dev, sizeof(*dell_bl), GFP_KERNEL);
+	if (!dell_bl)
+		return -ENOMEM;
+
+	mutex_init(&dell_bl->mutex);
+	init_waitqueue_head(&dell_bl->wait_queue);
+	dell_bl->dev = dev;
+
+	serdev_device_set_drvdata(serdev, dell_bl);
+	serdev_device_set_client_ops(serdev, &dell_uart_bl_serdev_ops);
+
+	ret = devm_serdev_device_open(dev, serdev);
+	if (ret)
+		return dev_err_probe(dev, ret, "opening UART device\n");
+
+	/* 9600 bps, no flow control, these are the default but set them to be sure */
+	serdev_device_set_baudrate(serdev, 9600);
+	serdev_device_set_flow_control(serdev, false);
+
+	return dell_uart_bl_init(dev, dell_bl);
+}
+
 static struct serdev_device_driver dell_uart_bl_serdev_driver = {
 	.probe = dell_uart_bl_serdev_probe,
 	.driver = {
-- 
2.43.0


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

* [PATCH v4 2/2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout
  2026-10-10 10:20   ` [PATCH v4 0/2] " chitranshmandhaniya
  2026-10-10 10:20     ` [PATCH v4 1/2] platform/x86: dell-uart-backlight: extract dell_uart_bl_init() from probe chitranshmandhaniya
@ 2026-10-10 10:20     ` chitranshmandhaniya
  2026-10-10 11:41     ` [PATCH v4 0/2] " Hans de Goede
  2 siblings, 0 replies; 13+ messages in thread
From: chitranshmandhaniya @ 2026-10-10 10:20 UTC (permalink / raw)
  To: ilpo.jarvinen
  Cc: hansg, platform-driver-x86, linux-kernel, Chitransh_Mandhaniya,
	kernel test robot

From: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>

Some Dell OptiPlex AIO 7410 (35W, board 01KP8D) ship with DELL0501 in
the ACPI DSDT (status = 0x0F) even though no UART scalar board is
physically present.  This causes acpi_video_get_backlight_type() to
return acpi_backlight_dell_uart, which makes i915 skip registering
intel_backlight.  The dell-uart-backlight driver then times out talking
to the absent board and the probe fails, leaving the machine with zero
backlight control.

A BIOS update fixes this on some models (e.g. OptiPlex 7420, board
0914RT) by setting DELL0501 _STA to 0, but Dell has not shipped a
corrected BIOS for the 7410 even in the latest release (1.37.0,
2026-05-28).

When the initial get_version command returns -ETIMEDOUT, fall back to
driving the backlight through the Intel PCH PWM registers (BLC_PWM_CTL /
BLC_PWM_FREQ / BLC_PWM_DUTY at MMIO BAR0 + 0xC8250).  The fallback
reads the BIOS-programmed PWM frequency as max_brightness and registers
a BACKLIGHT_PLATFORM device so that compositors (Weston, GNOME, KDE)
pick it up normally.

Tested on:
  - OptiPlex AIO 7410 35W (01KP8D) -- UART times out, PCH PWM fallback
    activates, backlight fully functional via sysfs and compositor.
  - OptiPlex AIO 7420 35W (0914RT, old BIOS) -- same timeout and
    fallback, backlight works.
  - OptiPlex AIO 7420 35W (0914RT, new BIOS) -- DELL0501 absent, driver
    not loaded, intel_backlight works natively (no regression).

Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202610100706.x2lcbo7S-lkp@intel.com/

Signed-off-by: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>
---
 .../platform/x86/dell/dell-uart-backlight.c   | 109 +++++++++++++++++-
 1 file changed, 108 insertions(+), 1 deletion(-)

diff --git a/drivers/platform/x86/dell/dell-uart-backlight.c b/drivers/platform/x86/dell/dell-uart-backlight.c
index e3b7f1beda5b..15ce637442df 100644
--- a/drivers/platform/x86/dell/dell-uart-backlight.c
+++ b/drivers/platform/x86/dell/dell-uart-backlight.c
@@ -13,10 +13,13 @@
 #include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/err.h>
+#include <linux/io.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/pci.h>
 #include <linux/platform_device.h>
 #include <linux/serdev.h>
+#include <linux/sizes.h>
 #include <linux/string.h>
 #include <linux/types.h>
 #include <linux/wait.h>
@@ -50,6 +53,12 @@
 #define MIN_RESP_LEN			3
 #define MAX_RESP_LEN			80
 
+/* PCH PWM backlight registers relative to Intel GPU MMIO BAR0 */
+#define PCH_BLC_PWM_CTL		0xC8250
+#define PCH_BLC_PWM_FREQ	0xC8254
+#define PCH_BLC_PWM_DUTY	0xC8258
+#define PCH_BLC_PWM_ENABLE	BIT(31)
+
 struct dell_uart_backlight {
 	struct mutex mutex;
 	wait_queue_head_t wait_queue;
@@ -64,6 +73,11 @@ struct dell_uart_backlight {
 	int power;
 };
 
+struct dell_pwm_backlight {
+	void __iomem *mmio;
+	u32 max_brightness;
+};
+
 /* Checksum: SUM(Length and Cmd and Data) xor 0xFF */
 static u8 dell_uart_checksum(u8 *buf, int len)
 {
@@ -267,6 +281,93 @@ static const struct serdev_device_ops dell_uart_bl_serdev_ops = {
 	.write_wakeup = serdev_device_write_wakeup,
 };
 
+/*
+ * Some Dell AIO models (e.g. OptiPlex 7410) declare DELL0501 in ACPI but
+ * have no physical UART scalar board. When the UART times out fall back
+ * to driving the Intel PCH PWM backlight registers directly.
+ */
+static int dell_pwm_update_status(struct backlight_device *bd)
+{
+	struct dell_pwm_backlight *pwm = bl_get_data(bd);
+	u32 duty;
+
+	if (bd->props.power != BACKLIGHT_POWER_ON ||
+	    bd->props.state & BL_CORE_SUSPENDED)
+		duty = 0;
+	else
+		duty = bd->props.brightness;
+
+	writel(duty, pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
+	return 0;
+}
+
+static int dell_pwm_get_brightness(struct backlight_device *bd)
+{
+	struct dell_pwm_backlight *pwm = bl_get_data(bd);
+
+	return readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
+}
+
+static const struct backlight_ops dell_pwm_backlight_ops = {
+	.update_status  = dell_pwm_update_status,
+	.get_brightness = dell_pwm_get_brightness,
+};
+
+static int dell_pwm_fallback_register(struct device *dev)
+{
+	struct dell_pwm_backlight *pwm;
+	struct backlight_device *bl;
+	struct pci_dev *gpu;
+	struct backlight_properties props = {};
+	resource_size_t bar0;
+	u32 freq, duty, ctl;
+
+	gpu = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL);
+	if (!gpu || gpu->vendor != PCI_VENDOR_ID_INTEL) {
+		pci_dev_put(gpu);
+		dev_warn(dev, "No Intel GPU found, cannot fall back to PCH PWM\n");
+		return -ENODEV;
+	}
+
+	bar0 = pci_resource_start(gpu, 0);
+	pci_dev_put(gpu);
+	if (!bar0) {
+		dev_warn(dev, "Intel GPU BAR0 not assigned\n");
+		return -ENODEV;
+	}
+
+	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
+	if (!pwm)
+		return -ENOMEM;
+
+	pwm->mmio = devm_ioremap(dev, bar0 + PCH_BLC_PWM_CTL, SZ_16);
+	if (!pwm->mmio) {
+		dev_warn(dev, "Failed to ioremap PCH PWM registers\n");
+		return -ENOMEM;
+	}
+
+	ctl  = readl(pwm->mmio);
+	freq = readl(pwm->mmio + (PCH_BLC_PWM_FREQ - PCH_BLC_PWM_CTL));
+	duty = readl(pwm->mmio + (PCH_BLC_PWM_DUTY - PCH_BLC_PWM_CTL));
+
+	if (!(ctl & PCH_BLC_PWM_ENABLE) || freq == 0) {
+		dev_warn(dev, "PCH PWM not enabled by BIOS (ctl=0x%08x freq=%u)\n", ctl, freq);
+		return -ENODEV;
+	}
+
+	pwm->max_brightness = freq;
+
+	dev_info(dev, "Falling back to PCH PWM backlight (max=%u current=%u)\n", freq, duty);
+
+	props.type = BACKLIGHT_PLATFORM;
+	props.brightness = duty;
+	props.max_brightness = freq;
+
+	bl = devm_backlight_device_register(dev, "dell_uart_backlight", dev, pwm,
+					    &dell_pwm_backlight_ops, &props);
+	return PTR_ERR_OR_ZERO(bl);
+}
+
 static int dell_uart_bl_init(struct device *dev, struct dell_uart_backlight *dell_bl)
 {
 	u8 get_version[GET_CMD_LEN], resp[MAX_RESP_LEN];
@@ -327,7 +428,13 @@ static int dell_uart_bl_serdev_probe(struct serdev_device *serdev)
 	serdev_device_set_baudrate(serdev, 9600);
 	serdev_device_set_flow_control(serdev, false);
 
-	return dell_uart_bl_init(dev, dell_bl);
+	ret = dell_uart_bl_init(dev, dell_bl);
+	if (ret == -ETIMEDOUT) {
+		dev_info(dev, "UART backlight controller not responding, trying PCH PWM fallback\n");
+		return dell_pwm_fallback_register(dev);
+	}
+
+	return ret;
 }
 
 static struct serdev_device_driver dell_uart_bl_serdev_driver = {
-- 
2.43.0


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

* Re: [PATCH v4 0/2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout
  2026-10-10 10:20   ` [PATCH v4 0/2] " chitranshmandhaniya
  2026-10-10 10:20     ` [PATCH v4 1/2] platform/x86: dell-uart-backlight: extract dell_uart_bl_init() from probe chitranshmandhaniya
  2026-10-10 10:20     ` [PATCH v4 2/2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout chitranshmandhaniya
@ 2026-10-10 11:41     ` Hans de Goede
  2 siblings, 0 replies; 13+ messages in thread
From: Hans de Goede @ 2026-10-10 11:41 UTC (permalink / raw)
  To: chitranshmandhaniya, ilpo.jarvinen
  Cc: platform-driver-x86, linux-kernel, Chitransh_Mandhaniya

Hi Chitransh,

On 10-Oct-26 12:20 PM, chitranshmandhaniya@gmail.com wrote:
> From: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>
> 
> Some Dell OptiPlex AIO 7410 (35W) ship with DELL0501 in ACPI but no
> physical UART scalar board, leaving the machine with zero backlight
> control after the UART probe times out.
> 
> Patch 1 is a preparatory refactor that extracts the UART initialization
> sequence into dell_uart_bl_init() so the probe function can inspect its
> return value.
> 
> Patch 2 adds a PCH PWM fallback path: when dell_uart_bl_init() returns
> -ETIMEDOUT, the driver reads the Intel PCH BLC_PWM registers and
> registers a BACKLIGHT_PLATFORM device.

Thank you for working on this, but directly doing mmio pokes on
an mmio range managed by the i915 driver is not okay:

NACK for this patch-series / this approach.

You should be able to work around this problem by passing:

acpi_backlight=native

on the kernel command-line which causes acpi_video_get_backlight_type()
to properly return native as type.

After confirming that that works, you can fix this permanently by
extending the existing DMI quirks for this in drivers/acpi/video_detect.c:

	/*
	 * Dell AIO (All in Ones) which advertise an UART attached backlight
	 * controller board in their ACPI tables (and may even have one), but
	 * which need native backlight control nevertheless.
	 */
	{
	 /* https://github.com/zabbly/linux/issues/26 */
	 .callback = video_detect_force_native,
	 /* Dell OptiPlex 5480 AIO */
	 .matches = {
		DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
		DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex 5480 AIO"),
		},
	},
	{
	 /* https://bugzilla.redhat.com/show_bug.cgi?id=2303936 */
	 .callback = video_detect_force_native,
	 /* Dell OptiPlex 7760 AIO */
	 .matches = {
		DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
		DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7760 AIO"),
		},
	},
	{
	 .callback = video_detect_force_native,
	 /* Dell OptiPlex 7770 AIO */
	 .matches = {
		DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
		DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7770 AIO"),
		},
	},


Regards,

Hans





> 
> v3 -> v4:
>   - Revert __free(pci_dev_put) to manual pci_dev_put() calls to fix
>     build error when CONFIG_PCI is disabled (kernel test robot)
> 
> v2 -> v3:
>   - Split into two patches: preparatory refactor + fallback (Ilpo)
> 
> v1 -> v2:
>   - Use SZ_16 instead of 0x10, add linux/sizes.h (Ilpo)
>   - Name BIT(31) as PCH_BLC_PWM_ENABLE (Ilpo)
>   - Fix double-space in block comment (Ilpo)
>   - Reduce devm_backlight_device_register() to fewer lines (Ilpo)
>   - Extract dell_uart_bl_init() for the normal UART backlight path (Ilpo)
> 
> Chitransh Mandhaniya (2):
>   platform/x86: dell-uart-backlight: extract dell_uart_bl_init() from
>     probe
>   platform/x86: dell-uart-backlight: fall back to PCH PWM on UART
>     timeout
> 
>  .../platform/x86/dell/dell-uart-backlight.c   | 158 +++++++++++++++---
>  1 file changed, 135 insertions(+), 23 deletions(-)
> 


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

* [PATCH v5] ACPI: video: force native backlight on Dell OptiPlex AIO 7410 35W
  2026-10-09  7:24 ` [PATCH v2] " chitranshmandhaniya
                     ` (3 preceding siblings ...)
  2026-10-10 10:20   ` [PATCH v4 0/2] " chitranshmandhaniya
@ 2026-10-10 12:48   ` chitranshmandhaniya
  4 siblings, 0 replies; 13+ messages in thread
From: chitranshmandhaniya @ 2026-10-10 12:48 UTC (permalink / raw)
  To: hansg, ilpo.jarvinen, rafael
  Cc: lenb, platform-driver-x86, linux-acpi, linux-kernel,
	Chitransh_Mandhaniya

From: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>

The Dell OptiPlex AIO 7410 35W (board 01KP8D) declares DELL0501 in its
ACPI DSDT (status = 0x0F) even though no UART scalar board is physically
present. This causes acpi_video_get_backlight_type() to return
acpi_backlight_dell_uart, which makes i915 skip registering
intel_backlight. The dell-uart-backlight driver then times out talking
to the absent board and the probe fails, leaving the machine with zero
backlight control.

A BIOS update fixes this on some models (e.g. OptiPlex 7420, board
0914RT) by setting DELL0501 _STA to 0, but Dell has not shipped a
corrected BIOS for the 7410 even in the latest release (1.37.0,
2026-05-28).

Add a DMI quirk to force native backlight on this model so that i915
registers intel_backlight and brightness control works out of the box.

Tested on:
  - OptiPlex AIO 7410 35W (01KP8D, BIOS 1.37.0) -- confirmed
    acpi_backlight=native produces working intel_backlight (raw,
    max=96000), DMI quirk has the same effect.
  - OptiPlex AIO 7420 35W (0914RT, BIOS 1.27.0) -- DELL0501 absent in
    updated BIOS, intel_backlight works natively (no regression).

Signed-off-by: Chitransh Mandhaniya <Chitransh_Mandhaniya@dell.com>
---
 drivers/acpi/video_detect.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c
index e3b69f876d24..1af14e04e597 100644
--- a/drivers/acpi/video_detect.c
+++ b/drivers/acpi/video_detect.c
@@ -883,6 +883,14 @@ static const struct dmi_system_id video_detect_dmi_table[] = {
 		DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7770 AIO"),
 		},
 	},
+	{
+	 .callback = video_detect_force_native,
+	 /* Dell OptiPlex AIO 7410 35W */
+	 .matches = {
+		DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+		DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex AIO 7410 35W"),
+		},
+	},
 
 	/*
 	 * Models which have nvidia-ec-wmi support, but should not use it.
-- 
2.43.0


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

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

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-08 14:16 [PATCH] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout chitranshmandhaniya
2026-10-08 14:30 ` Ilpo Järvinen
2026-10-09  7:24 ` [PATCH v2] " chitranshmandhaniya
2026-10-09 10:48   ` Ilpo Järvinen
2026-10-09 12:26   ` [PATCH v3 0/2] " chitranshmandhaniya
2026-10-09 12:26     ` [PATCH v3 1/2] platform/x86: dell-uart-backlight: extract dell_uart_bl_init() from probe chitranshmandhaniya
2026-10-09 12:26     ` [PATCH v3 2/2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout chitranshmandhaniya
2026-10-09 23:46   ` [PATCH v2] " kernel test robot
2026-10-10 10:20   ` [PATCH v4 0/2] " chitranshmandhaniya
2026-10-10 10:20     ` [PATCH v4 1/2] platform/x86: dell-uart-backlight: extract dell_uart_bl_init() from probe chitranshmandhaniya
2026-10-10 10:20     ` [PATCH v4 2/2] platform/x86: dell-uart-backlight: fall back to PCH PWM on UART timeout chitranshmandhaniya
2026-10-10 11:41     ` [PATCH v4 0/2] " Hans de Goede
2026-10-10 12:48   ` [PATCH v5] ACPI: video: force native backlight on Dell OptiPlex AIO 7410 35W chitranshmandhaniya

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®