* [PATCH v2 2/2] hwmon: raspberrypi: Add voltage input support
From: Shubham Chakraborty @ 2026-05-16 19:15 UTC (permalink / raw)
To: Guenter Roeck, Florian Fainelli, Jonathan Corbet
Cc: Shuah Khan, Broadcom internal kernel review list, Ray Jui,
Scott Branden, linux-hwmon, linux-doc, linux-rpi-kernel,
linux-arm-kernel, linux-kernel, Shubham Chakraborty
In-Reply-To: <20260516191555.17978-1-chakrabortyshubham66@gmail.com>
Extend the raspberrypi-hwmon driver to expose firmware-provided
voltage measurements through the hwmon subsystem.
The driver now exports the following voltage inputs:
- in0_input (core)
- in1_input (sdram_c)
- in2_input (sdram_i)
- in3_input (sdram_p)
Voltage values returned by firmware are converted from microvolts
to millivolts as expected by the hwmon subsystem.
Update the documentation related to it.
The existing undervoltage sticky alarm handling is preserved and
associated with the first voltage channel.
Tested in -
- Raspberry Pi 3b+ (Linux raspberrypi 6.12.75+rpt-rpi-v8 #1 SMP PREEMPT
Debian 1:6.12.75-1+rpt1 (2026-03-11) aarch64 GNU/Linux)
Signed-off-by: Shubham Chakraborty <chakrabortyshubham66@gmail.com>
---
Documentation/hwmon/raspberrypi-hwmon.rst | 15 ++-
drivers/hwmon/raspberrypi-hwmon.c | 134 +++++++++++++++++++++-
2 files changed, 144 insertions(+), 5 deletions(-)
diff --git a/Documentation/hwmon/raspberrypi-hwmon.rst b/Documentation/hwmon/raspberrypi-hwmon.rst
index 8038ade36490..db315184b861 100644
--- a/Documentation/hwmon/raspberrypi-hwmon.rst
+++ b/Documentation/hwmon/raspberrypi-hwmon.rst
@@ -20,6 +20,17 @@ undervoltage conditions.
Sysfs entries
-------------
-======================= ==================
+======================= ======================================================
+in0_input Core voltage in millivolts
+in1_input SDRAM controller voltage in millivolts
+in2_input SDRAM I/O voltage in millivolts
+in3_input SDRAM PHY voltage in millivolts
+in0_label "core"
+in1_label "sdram_c"
+in2_label "sdram_i"
+in3_label "sdram_p"
in0_lcrit_alarm Undervoltage alarm
-======================= ==================
+======================= ======================================================
+
+The voltage inputs and labels are only exposed if the firmware reports support
+for the corresponding voltage ID.
diff --git a/drivers/hwmon/raspberrypi-hwmon.c b/drivers/hwmon/raspberrypi-hwmon.c
index a2938881ccd2..4f96f37116f3 100644
--- a/drivers/hwmon/raspberrypi-hwmon.c
+++ b/drivers/hwmon/raspberrypi-hwmon.c
@@ -5,6 +5,7 @@
* Based on firmware/raspberrypi.c by Noralf Trønnes
*
* Copyright (C) 2018 Stefan Wahren <stefan.wahren@i2se.com>
+ * Copyright (C) 2026 Shubham Chakraborty <chakrabortyshubham66@gmail.com>
*/
#include <linux/device.h>
#include <linux/devm-helpers.h>
@@ -18,13 +19,26 @@
#define UNDERVOLTAGE_STICKY_BIT BIT(16)
+struct rpi_firmware_get_value {
+ __le32 id;
+ __le32 val;
+} __packed;
+
struct rpi_hwmon_data {
struct device *hwmon_dev;
struct rpi_firmware *fw;
+ u32 valid_inputs;
u32 last_throttled;
struct delayed_work get_values_poll_work;
};
+static const char * const rpi_hwmon_labels[] = {
+ "core",
+ "sdram_c",
+ "sdram_i",
+ "sdram_p",
+};
+
static void rpi_firmware_get_throttled(struct rpi_hwmon_data *data)
{
u32 new_uv, old_uv, value;
@@ -56,6 +70,23 @@ static void rpi_firmware_get_throttled(struct rpi_hwmon_data *data)
hwmon_notify_event(data->hwmon_dev, hwmon_in, hwmon_in_lcrit_alarm, 0);
}
+static int rpi_firmware_get_voltage(struct rpi_hwmon_data *data, u32 id,
+ long *val)
+{
+ struct rpi_firmware_get_value packet;
+ int ret;
+
+ packet.id = cpu_to_le32(id);
+ packet.val = 0;
+ ret = rpi_firmware_property(data->fw, RPI_FIRMWARE_GET_VOLTAGE,
+ &packet, sizeof(packet));
+ if (ret)
+ return ret;
+
+ *val = le32_to_cpu(packet.val) / 1000;
+ return 0;
+}
+
static void get_values_poll(struct work_struct *work)
{
struct rpi_hwmon_data *data;
@@ -77,19 +108,94 @@ static int rpi_read(struct device *dev, enum hwmon_sensor_types type,
{
struct rpi_hwmon_data *data = dev_get_drvdata(dev);
- *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
+ if (type == hwmon_in) {
+ switch (attr) {
+ case hwmon_in_input:
+ switch (channel) {
+ case 0:
+ return rpi_firmware_get_voltage(data,
+ RPI_FIRMWARE_VOLT_ID_CORE,
+ val);
+ case 1:
+ return rpi_firmware_get_voltage(data,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_C,
+ val);
+ case 2:
+ return rpi_firmware_get_voltage(data,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_I,
+ val);
+ case 3:
+ return rpi_firmware_get_voltage(data,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_P,
+ val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ case hwmon_in_lcrit_alarm:
+ if (channel == 0) {
+ *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
+ return 0;
+ }
+ return -EOPNOTSUPP;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int rpi_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ if (type == hwmon_in && attr == hwmon_in_label) {
+ if (channel >= ARRAY_SIZE(rpi_hwmon_labels))
+ return -EOPNOTSUPP;
+
+ *str = rpi_hwmon_labels[channel];
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static umode_t rpi_is_visible(const void *_data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct rpi_hwmon_data *data = _data;
+
+ if (type == hwmon_in) {
+ switch (attr) {
+ case hwmon_in_input:
+ case hwmon_in_label:
+ if (!(data->valid_inputs & BIT(channel)))
+ return 0;
+ return 0444;
+ case hwmon_in_lcrit_alarm:
+ if (channel == 0)
+ return 0444;
+ return 0;
+ default:
+ return 0;
+ }
+ }
+
return 0;
}
static const struct hwmon_channel_info * const rpi_info[] = {
HWMON_CHANNEL_INFO(in,
- HWMON_I_LCRIT_ALARM),
+ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_LCRIT_ALARM,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL),
NULL
};
static const struct hwmon_ops rpi_hwmon_ops = {
- .visible = 0444,
+ .is_visible = rpi_is_visible,
.read = rpi_read,
+ .read_string = rpi_read_string,
};
static const struct hwmon_chip_info rpi_chip_info = {
@@ -101,6 +207,7 @@ static int rpi_hwmon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rpi_hwmon_data *data;
+ long voltage;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
@@ -110,6 +217,26 @@ static int rpi_hwmon_probe(struct platform_device *pdev)
/* Parent driver assure that firmware is correct */
data->fw = dev_get_drvdata(dev->parent);
+ ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_CORE,
+ &voltage);
+ if (!ret)
+ data->valid_inputs |= BIT(0);
+
+ ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_SDRAM_C,
+ &voltage);
+ if (!ret)
+ data->valid_inputs |= BIT(1);
+
+ ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_SDRAM_I,
+ &voltage);
+ if (!ret)
+ data->valid_inputs |= BIT(2);
+
+ ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_SDRAM_P,
+ &voltage);
+ if (!ret)
+ data->valid_inputs |= BIT(3);
+
data->hwmon_dev = devm_hwmon_device_register_with_info(dev, "rpi_volt",
data,
&rpi_chip_info,
@@ -159,6 +286,7 @@ static struct platform_driver rpi_hwmon_driver = {
module_platform_driver(rpi_hwmon_driver);
MODULE_AUTHOR("Stefan Wahren <wahrenst@gmx.net>");
+MODULE_AUTHOR("Shubham Chakraborty <chakrabortyshubham66@gmail.com>");
MODULE_DESCRIPTION("Raspberry Pi voltage sensor driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:raspberrypi-hwmon");
--
2.54.0
^ permalink raw reply related
* [PATCH v2 1/2] soc: bcm2835: raspberrypi-firmware: Add voltage domain IDs
From: Shubham Chakraborty @ 2026-05-16 19:15 UTC (permalink / raw)
To: Guenter Roeck, Florian Fainelli, Jonathan Corbet
Cc: Shuah Khan, Broadcom internal kernel review list, Ray Jui,
Scott Branden, linux-hwmon, linux-doc, linux-rpi-kernel,
linux-arm-kernel, linux-kernel, Shubham Chakraborty
In-Reply-To: <20260516191555.17978-1-chakrabortyshubham66@gmail.com>
Add firmware voltage domain identifiers for the Raspberry Pi
mailbox property interface.
These IDs are used by firmware clients to query voltage rails
through the RPI_FIRMWARE_GET_VOLTAGE property.
Signed-off-by: Shubham Chakraborty <chakrabortyshubham66@gmail.com>
---
include/soc/bcm2835/raspberrypi-firmware.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/include/soc/bcm2835/raspberrypi-firmware.h b/include/soc/bcm2835/raspberrypi-firmware.h
index e1f87fbfe554..fd2e051ce05b 100644
--- a/include/soc/bcm2835/raspberrypi-firmware.h
+++ b/include/soc/bcm2835/raspberrypi-firmware.h
@@ -156,6 +156,14 @@ enum rpi_firmware_clk_id {
RPI_FIRMWARE_NUM_CLK_ID,
};
+enum rpi_firmware_volt_id {
+ RPI_FIRMWARE_VOLT_ID_RESERVED = 0,
+ RPI_FIRMWARE_VOLT_ID_CORE = 1,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_C = 2,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_I = 3,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_P = 4,
+};
+
/**
* struct rpi_firmware_clk_rate_request - Firmware Request for a rate
* @id: ID of the clock being queried
--
2.54.0
^ permalink raw reply related
* [PATCH v2 0/2] raspberrypi: firmware and hwmon voltage support
From: Shubham Chakraborty @ 2026-05-16 19:15 UTC (permalink / raw)
To: Guenter Roeck, Florian Fainelli, Jonathan Corbet
Cc: Shuah Khan, Broadcom internal kernel review list, Ray Jui,
Scott Branden, linux-hwmon, linux-doc, linux-rpi-kernel,
linux-arm-kernel, linux-kernel, Shubham Chakraborty
In-Reply-To: <20260516164407.25255-1-chakrabortyshubham66@gmail.com>
Changes in v2:
- Patch 1/2: no changes
- Patch 2/2:
- hide unsupported voltage sensors using .is_visible()
- replace the label switch with a string array
- update raspberrypi-hwmon documentation
Shubham Chakraborty (2):
soc: bcm2835: raspberrypi-firmware: Add voltage domain IDs
hwmon: raspberrypi: Add voltage input support
Documentation/hwmon/raspberrypi-hwmon.rst | 15 ++-
drivers/hwmon/raspberrypi-hwmon.c | 134 ++++++++++++++++++++-
include/soc/bcm2835/raspberrypi-firmware.h | 8 ++
3 files changed, 152 insertions(+), 5 deletions(-)
--
2.54.0
^ permalink raw reply
* Re: [PATCH] Bluetooth: btmtk: Fix FUNC_CTRL parsing for devices with zero-length payloads
From: Tristan Madani @ 2026-05-16 19:15 UTC (permalink / raw)
To: shivamkalra98
Cc: marcel, luiz.dentz, matthias.bgg, angelogioacchino.delregno,
luiz.von.dentz, linux-bluetooth, linux-kernel, linux-arm-kernel,
linux-mediatek, stable
In-Reply-To: <20260514-bluetooh-fix-mt7922-v1-1-499c878af1e5@zohomail.in>
On Thu, 14 May 2026 23:18:13 +0530, Shivam Kalra wrote:
> Fix this by making skb_pull_data() conditional: if the status payload is
> present, parse it as before; if omitted, default to BTMTK_WMT_ON_UNDONE.
Makes sense. The original check was too strict for devices that
legitimately omit the status field on FUNC_CTRL responses.
Reviewed-by: Tristan Madani <tristan@talencesecurity.com>
^ permalink raw reply
* Re: [PATCH] Enable stage 2 translation in L2
From: Wei-Lin Chang @ 2026-05-16 19:03 UTC (permalink / raw)
To: Itaru Kitayama, Marc Zyngier, Oliver Upton, Joey Gouly,
Steffen Eiden, Suzuki K Poulose, Zenghui Yu, Paolo Bonzini,
Shuah Khan
Cc: linux-arm-kernel, kvmarm, kvm, linux-kselftest, linux-kernel
In-Reply-To: <20260515-enable-s2-hello_nested-v1-1-41f3faca1a08@fujitsu.com>
On Fri, May 15, 2026 at 10:48:05AM +0900, Itaru Kitayama wrote:
> IPA size and start level are configurable at build time.
Hi Itaru,
Thanks for the effort, and sorry for not making the next version for so
long.
There are a few issues with this. Firstly, my patches aren't accepted
yet, therefore your code which builds on top of that shouldn't be posted
independently. Second, the commit message is giving too little
information about the change, which is non-trivial.
In terms of the changes themselves, see below.
>
> Signed-off-by: Itaru Kitayama <itaru.kitayama@fujitsu.com>
> ---
> Enable stage 2 translation in L2, but keep stage 1 remain off
> as Wei Lin prefers. Types are changed accordingly due to the
> recent selftest-wide changes.
> ---
> tools/testing/selftests/kvm/arm64/hello_nested.c | 11 +-
> tools/testing/selftests/kvm/include/arm64/nested.h | 38 ++-
> tools/testing/selftests/kvm/lib/arm64/hyp-entry.S | 5 +
> tools/testing/selftests/kvm/lib/arm64/nested.c | 279 ++++++++++++++++++++-
> 4 files changed, 322 insertions(+), 11 deletions(-)
>
> diff --git a/tools/testing/selftests/kvm/arm64/hello_nested.c b/tools/testing/selftests/kvm/arm64/hello_nested.c
> index 69f4d8e750e2..1ac045894b89 100644
> --- a/tools/testing/selftests/kvm/arm64/hello_nested.c
> +++ b/tools/testing/selftests/kvm/arm64/hello_nested.c
> @@ -18,9 +18,9 @@
> /*
> * TPIDR_EL2 is used to store vcpu id, so save and restore it.
> */
> -static vm_paddr_t ucall_translate_to_gpa(void *gva)
> +static gpa_t ucall_translate_to_gpa(void *gva)
> {
> - vm_paddr_t gpa;
> + gpa_t gpa;
> u64 vcpu_id = read_sysreg(tpidr_el2);
>
> GUEST_SYNC2(XLATE2GPA, gva);
> @@ -50,7 +50,7 @@ static void guest_code(void)
> struct vcpu vcpu;
> struct hyp_data hyp_data;
> int ret;
> - vm_paddr_t l2_pc, l2_stack_top;
> + gpa_t l2_pc, l2_stack_top;
> /* force 16-byte alignment for the stack pointer */
> u8 l2_stack[L2STACKSZ] __attribute__((aligned(16)));
> u64 arg1, arg2;
> @@ -92,7 +92,7 @@ int main(void)
> struct kvm_vcpu *vcpu;
> struct kvm_vm *vm;
> struct ucall uc;
> - vm_paddr_t gpa;
> + gpa_t gpa;
>
> TEST_REQUIRE(kvm_check_cap(KVM_CAP_ARM_EL2));
> vm = vm_create(1);
> @@ -102,13 +102,14 @@ int main(void)
> vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code);
> kvm_arch_vm_finalize_vcpus(vm);
>
> + prepare_hyp_state(vm, vcpu);
> while (true) {
> vcpu_run(vcpu);
>
> switch (get_ucall(vcpu, &uc)) {
> case UCALL_SYNC:
> if (uc.args[0] == XLATE2GPA) {
> - gpa = addr_gva2gpa(vm, (vm_vaddr_t)uc.args[1]);
> + gpa = addr_gva2gpa(vm, (gva_t)uc.args[1]);
> vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_TPIDR_EL2), gpa);
> }
> break;
> diff --git a/tools/testing/selftests/kvm/include/arm64/nested.h b/tools/testing/selftests/kvm/include/arm64/nested.h
> index b16a72488858..b4ccca3593db 100644
> --- a/tools/testing/selftests/kvm/include/arm64/nested.h
> +++ b/tools/testing/selftests/kvm/include/arm64/nested.h
> @@ -18,14 +18,44 @@
>
> #include <asm/ptrace.h>
> #include "kvm_util.h"
> +#include "processor.h"
>
> extern char hyp_vectors[];
>
> +#ifdef CONFIG_ARM64_64K_PAGES
> +
> +#define VTCR_EL2_TGRAN 64K
> +#define VTCR_EL2_TGRAN_SL0_BASE 3UL
> +
> +#elif defined(CONFIG_ARM64_16K_PAGES)
> +
> +#define VTCR_EL2_TGRAN 16K
> +#define VTCR_EL2_TGRAN_SL0_BASE 3UL
> +
> +#else /* 4K */
> +
> +#define VTCR_EL2_TGRAN 4K
> +#define VTCR_EL2_TGRAN_SL0_BASE 2UL
> +
> +#endif
> +
> +struct s2_config {
> + u64 granule;
> + u8 ia_bits;
> + u8 oa_bits;
> + u8 start_level;
> +};
> +
> +u64 get_l1_vtcr(const struct s2_config *cfg);
> +
> +void nested_map(struct kvm_vm *vm, const struct s2_config *cfg, gpa_t guest_pgd, uint64_t nested_paddr, uint64_t paddr, uint64_t size);
> +void nested_map_memslot(struct kvm_vm *vm, const struct s2_config *cfg, gpa_t guest_pgd, u32 memslot);
> +
> enum vcpu_sysreg {
> __INVALID_SYSREG__, /* 0 is reserved as an invalid value */
>
> SP_EL1,
> -
> + ESR_EL2,
> NR_SYS_REGS
> };
>
> @@ -47,12 +77,14 @@ struct hyp_data {
> };
>
> void prepare_hyp(void);
> -void init_vcpu(struct vcpu *vcpu, vm_paddr_t l2_pc, vm_paddr_t l2_stack_top);
> +void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top);
> int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data);
>
> u64 do_hvc(u64 action, u64 arg1, u64 arg2);
> +u64 vcpu_get_esr_el2(struct vcpu *vcpu);
> +
> u64 __guest_enter(struct vcpu *vcpu, struct cpu_context *hyp_context);
> -void __hyp_exception(u64 type);
> +void __hyp_exception(u64 type, u64 esr, u64 elr, u64 far, u64 hpfar, u64 spsr);
>
> void __sysreg_save_el1_state(struct cpu_context *ctxt);
> void __sysreg_restore_el1_state(struct cpu_context *ctxt);
> diff --git a/tools/testing/selftests/kvm/lib/arm64/hyp-entry.S b/tools/testing/selftests/kvm/lib/arm64/hyp-entry.S
> index 6341f6e05c90..fcf7bb303b77 100644
> --- a/tools/testing/selftests/kvm/lib/arm64/hyp-entry.S
> +++ b/tools/testing/selftests/kvm/lib/arm64/hyp-entry.S
> @@ -30,6 +30,11 @@ el1_error:
> b __guest_exit
>
> el2_sync:
> + mrs x1, esr_el2
> + mrs x2, elr_el2
> + mrs x3, far_el2
> + mrs x4, hpfar_el2
> + mrs x5, spsr_el2
> mov x0, #ARM_EXCEPTION_EL2_TRAP
> b __hyp_exception
>
> diff --git a/tools/testing/selftests/kvm/lib/arm64/nested.c b/tools/testing/selftests/kvm/lib/arm64/nested.c
> index b30d20b101c4..104c98d29eb9 100644
> --- a/tools/testing/selftests/kvm/lib/arm64/nested.c
> +++ b/tools/testing/selftests/kvm/lib/arm64/nested.c
> @@ -7,15 +7,269 @@
> #include "processor.h"
> #include "test_util.h"
> #include <asm/sysreg.h>
> +#include <linux/sizes.h>
> +
> +static const struct s2_config default_s2_cfg = {
> + .granule = SZ_4K,
> + .ia_bits = 40,
> + .oa_bits = 40,
> + .start_level = 0,
> +};
4K is not guaranteed at stage-2, you will need to check
ID_AA64MMFR0_EL1.TGRAN2_* . oa_bits as 40 is also not guaranteed, you
will need to check ID_AA64MMFR0_EL1.PARange for the limit.
> +
> +static u64 s2_alloc_page_table(struct kvm_vm *vm, const struct s2_config *cfg)
> +{
> + u64 nr_pages = cfg->granule >> vm->page_shift;
> +
> + TEST_ASSERT(!(cfg->granule & (vm->page_size - 1)),
> + "S2 granule 0x%lx smaller/not aligned to VM page size 0x%x",
> + cfg->granule, vm->page_size);
> +
> + return vm_phy_pages_alloc(vm, nr_pages,
> + KVM_GUEST_PAGE_TABLE_MIN_PADDR,
> + vm->memslots[MEM_REGION_PT]);
> +}
This part is using spaces instead of tabs to indent the code.
>
> void prepare_hyp(void)
> {
> - write_sysreg(HCR_EL2_E2H | HCR_EL2_RW, hcr_el2);
> + write_sysreg(HCR_EL2_E2H | HCR_EL2_RW | HCR_EL2_VM, hcr_el2);
> write_sysreg(hyp_vectors, vbar_el2);
> isb();
> }
>
> -void init_vcpu(struct vcpu *vcpu, vm_paddr_t l2_pc, vm_paddr_t l2_stack_top)
> +static unsigned int s2_granule_shift(const struct s2_config *cfg)
> +{
> + switch (cfg->granule) {
> + case SZ_4K:
> + return 12;
> + case SZ_16K:
> + return 14;
> + case SZ_64K:
> + return 16;
> + default:
> + TEST_FAIL("Unsupported stage-2 granule %u", cfg->granule);
> + }
> +}
> +
> +static unsigned int s2_level_stride(const struct s2_config *cfg)
> +{
> + return s2_granule_shift(cfg) - 3;
> +}
> +
> +static unsigned int s2_ptrs_per_table(const struct s2_config *cfg)
> +{
> + return 1U << s2_level_stride(cfg);
> +}
> +
> +static u64 s2_index_mask(const struct s2_config *cfg)
> +{
> + return s2_ptrs_per_table(cfg) - 1;
> +}
> +
> +static unsigned int s2_last_level(const struct s2_config *cfg)
> +{
> + return 3;
> +}
> +
> +static unsigned int s2_level_shift(const struct s2_config *cfg,
> + unsigned int level)
> +{
> + return s2_granule_shift(cfg) +
> + (s2_last_level(cfg) - level) * s2_level_stride(cfg);
> +}
> +
> +static u64 s2_table_mask(const struct s2_config *cfg)
> +{
> + return GENMASK_ULL(cfg->ia_bits - 1, s2_granule_shift(cfg));
> +}
> +
> +static u64 s2_output_mask(const struct s2_config *cfg)
> +{
> + return GENMASK_ULL(cfg->oa_bits - 1, s2_granule_shift(cfg));
> +}
> +
> +static u64 s2_desc_table(u64 paddr, const struct s2_config *cfg)
> +{
> + return (paddr & s2_table_mask(cfg)) | 0x3;
> +}
> +
> +#define S2_MEMATTR_NORMAL_WB 0xfUL
> +#define S2_MEMATTR_SHIFT 2
> +
> +#define S2_S2AP_R BIT(6)
> +#define S2_S2AP_W BIT(7)
> +
> +#define S2_SH_INNER (3UL << 8)
> +
> +
> +static u64 s2_desc_page(u64 paddr, u64 flags, const struct s2_config *cfg)
> +{
> + u64 desc;
> +
> + desc = paddr & s2_output_mask(cfg);
> +
> + /* Stage-2 lower attrs */
> + desc |= S2_MEMATTR_NORMAL_WB << S2_MEMATTR_SHIFT;
> + desc |= S2_S2AP_R | S2_S2AP_W;
> + desc |= S2_SH_INNER;
> + desc |= PTE_AF;
> +
> + /* L3 page descriptor: bits[1:0] = 0b11 */
> + desc |= PTE_TYPE_PAGE;
> + desc |= PTE_VALID;
> +
> + return desc;
> +}
> +
> +static inline int ipa_bits_to_ps(unsigned int ipa_bits)
> +{
> + switch (ipa_bits) {
> + case 32:
> + return 0b000;
> + case 36:
> + return 0b001;
> + case 40:
> + return 0b010;
> + case 42:
> + return 0b011;
> + case 44:
> + return 0b100;
> + case 48:
> + return 0b101;
> + case 52:
> + return 0b110;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +u64 get_l1_vtcr(const struct s2_config *cfg)
> +{
> + if (!cfg)
> + cfg = &default_s2_cfg;
> +
> + return FIELD_PREP(VTCR_EL2_PS, ipa_bits_to_ps(cfg->ia_bits)) |
oa_bits here?
> + FIELD_PREP(VTCR_EL2_TG0, VTCR_EL2_TG0_4K) |
> + FIELD_PREP(VTCR_EL2_ORGN0_MASK, VTCR_EL2_ORGN0_WBWA) |
> + FIELD_PREP(VTCR_EL2_IRGN0_MASK, VTCR_EL2_IRGN0_WBWA) |
> + FIELD_PREP(VTCR_EL2_SH0_MASK, VTCR_EL2_SH0_INNER) |
> + FIELD_PREP(VTCR_EL2_SL0, VTCR_EL2_TGRAN_SL0_BASE - cfg->start_level) |
> + FIELD_PREP(VTCR_EL2_T0SZ_MASK, 64 - cfg->ia_bits);
> +}
> +
> +void prepare_hyp_state(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
> +{
> + const struct s2_config *cfg = &default_s2_cfg;
> + u64 guest_pgd;
> +
> + vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_VTCR_EL2), get_l1_vtcr(cfg));
> +
> + guest_pgd = s2_alloc_page_table(vm, cfg);
> + nested_map_memslot(vm, cfg, guest_pgd, 0);
> +
> + pr_debug("cfg=%p ia_bits=%u oa_bits=%u granule=%u\n",
> + cfg, cfg->ia_bits, cfg->oa_bits, cfg->granule);
> +
> + vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_VTTBR_EL2), guest_pgd);
> +}
Same here about the spaces.
If you are interested, I just posted v3 with stage-2 enabled for the
nested guest. Different from your approach, in my version all stage-2
setup is done by the guest hypervisor itself. You can check it out at
[1]. Feedback is welcome.
Thanks,
Wei-Lin Chang
[1]: https://lore.kernel.org/kvmarm/20260516183003.799058-1-weilin.chang@arm.com/
> +
> +static void __nested_pg_map(struct kvm_vm *vm,
> + const struct s2_config *cfg,
> + u64 guest_pgd,
> + u64 nested_paddr,
> + u64 paddr,
> + u64 flags)
> +{
> + u64 granule = 1ULL << s2_granule_shift(cfg);
> + u64 *ptep;
> + unsigned int level;
> +
> + TEST_ASSERT(!(nested_paddr & (granule - 1)),
> + "L2 IPA not granule aligned: 0x%lx granule 0x%lx",
> + nested_paddr, granule);
> +
> + TEST_ASSERT(!(paddr & (granule - 1)),
> + "PA not granule aligned: 0x%lx granule 0x%lx",
> + paddr, granule);
> +
> + ptep = addr_gpa2hva(vm, guest_pgd);
> +
> + for (level = cfg->start_level; level < s2_last_level(cfg); level++) {
> + u64 idx;
> + u64 desc;
> +
> + idx = (nested_paddr >> s2_level_shift(cfg, level)) &
> + s2_index_mask(cfg);
> +
> + ptep += idx;
> + desc = *ptep;
> +
> + if (!desc) {
> + u64 table = s2_alloc_page_table(vm, cfg);
> +
> + desc = s2_desc_table(table, cfg);
> + *ptep = desc;
> + }
> +
> + ptep = addr_gpa2hva(vm, desc & s2_table_mask(cfg));
> + }
> +
> + ptep += (nested_paddr >> s2_granule_shift(cfg)) & s2_index_mask(cfg);
> + *ptep = s2_desc_page(paddr, flags, cfg);
> +}
> +
> +void nested_map(struct kvm_vm *vm,
> + const struct s2_config *cfg,
> + gpa_t guest_pgd,
> + u64 nested_paddr,
> + u64 paddr,
> + u64 size)
> +{
> + u64 granule;
> + size_t npages;
> +
> + if (!cfg)
> + cfg = &default_s2_cfg;
> +
> + granule = 1ULL << s2_granule_shift(cfg);
> +
> + TEST_ASSERT(!(size & (granule - 1)),
> + "Mapping size 0x%lx not aligned to granule 0x%lx",
> + size, granule);
> +
> + TEST_ASSERT(nested_paddr + size > nested_paddr, "IPA overflow");
> + TEST_ASSERT(paddr + size > paddr, "PA overflow");
> +
> + npages = size / granule;
> +
> + while (npages--) {
> + __nested_pg_map(vm, cfg, guest_pgd, nested_paddr, paddr,
> + MT_NORMAL);
> +
> + nested_paddr += granule;
> + paddr += granule;
> + }
> +}
> +
> +void nested_map_memslot(struct kvm_vm *vm,
> + const struct s2_config *cfg,
> + gpa_t guest_pgd,
> + u32 memslot)
> +{
> + struct userspace_mem_region *region;
> + u64 gpa, end;
> +
> + region = memslot2region(vm, memslot);
> +
> + gpa = region->region.guest_phys_addr;
> + end = gpa + region->region.memory_size;
> +
> + pr_debug("nested S2 map slot %u: GPA %#lx-%#lx\n", memslot, gpa, end);
> +
> + for (; gpa < end; gpa += cfg->granule)
> + nested_map(vm, cfg, guest_pgd, gpa, gpa, cfg->granule);
> +}
> +
> +void init_vcpu(struct vcpu *vcpu, gpa_t l2_pc, gpa_t l2_stack_top)
> {
> memset(vcpu, 0, sizeof(*vcpu));
> vcpu->context.regs.pc = l2_pc;
> @@ -46,13 +300,32 @@ int run_l2(struct vcpu *vcpu, struct hyp_data *hyp_data)
>
> vcpu->context.regs.pc = read_sysreg(elr_el2);
> vcpu->context.regs.pstate = read_sysreg(spsr_el2);
> + vcpu->context.sys_regs[ESR_EL2] = read_sysreg(esr_el2);
>
> __sysreg_save_el1_state(&vcpu->context);
>
> return ret;
> }
>
> -void __hyp_exception(u64 type)
> +u64 vcpu_get_esr_el2(struct vcpu *vcpu)
> {
> + return vcpu->context.sys_regs[ESR_EL2];
> +
> +}
> +
> +void __hyp_exception(u64 type, u64 esr, u64 elr, u64 far, u64 hpfar, u64 spsr)
> +{
> + u64 ec = esr >> 26;
> + u64 iss = esr & GENMASK_ULL(24, 0);
> + u64 ipa = ((hpfar & GENMASK_ULL(39, 4)) << 8) |
> + (far & GENMASK_ULL(11, 0));
> +
> + GUEST_FAIL("Unexpected hyp exception: type=%lu "
> + "ESR_EL2=%#lx EC=%#lx ISS=%#lx "
> + "ELR_EL2=%#lx FAR_EL2=%#lx HPFAR_EL2=%#lx IPA=%#lx "
> + "SPSR_EL2=%#lx",
> + type, esr, ec, iss, elr, far, hpfar, ipa, spsr);
> GUEST_FAIL("Unexpected hyp exception! type: %lx\n", type);
> +
> +
> }
>
> ---
> base-commit: eb656a0272c639d43be7a9bdd1c5f31eff3afe86
> change-id: 20260515-enable-s2-hello_nested-b360a2e9bb87
>
> Best regards,
> --
> Itaru Kitayama <itaru.kitayama@fujitsu.com>
>
^ permalink raw reply
* [PATCH v6 19/22] drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
The commit aeac23bda87f ("drm: bridge/dw_hdmi: improve HDMI
enable/disable handling") added use of PHY RXSENSE indications to avoid
triggering a full enable/disable of the HDMI block when a sink use a HPD
low voltage level pulse to indicate changes of the EDID.
HDMI Specification Version 1.4b chapter 8.5 mentions:
An HDMI Sink shall indicate any change to the contents of the E-EDID
by driving a low voltage level pulse on the Hot Plug Detect pin. This
pulse shall be at least 100 msec.
A delayed work is now used to debounce reacting on a HPD low voltage
level pulse when a sink changes the EDID. The delayed work triggers a
hotplug uevent every time the connection status or EDID has changed.
Remove RXSENSE handling to simplify the HPD interrupt handling and
instead depend on the delayed work to detect any connection status or
EDID changes.
This also ensures the initial HPD interrupt polarity is based on current
HPD status to avoid an unnecessary interrupt from being triggered
immediately at probe or resume when a sink is connected.
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Update commit message,
Collect t-b tag
v5: Add comment about interrupt generation
v4: New patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 144 ++++------------------
1 file changed, 21 insertions(+), 123 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 300dac4a6c35..39e94d22249b 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -161,11 +161,7 @@ struct dw_hdmi {
struct pinctrl_state *unwedge_state;
struct mutex mutex; /* for state below */
- enum drm_connector_force force; /* mutex-protected force state */
struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */
- bool disabled; /* DRM has disabled our bridge */
- bool rxsense; /* rxsense state */
- u8 phy_mask; /* desired phy int mask settings */
u8 mc_clkdis; /* clock disable register */
spinlock_t audio_lock;
@@ -196,14 +192,6 @@ const struct dw_hdmi_plat_data *dw_hdmi_to_plat_data(struct dw_hdmi *hdmi)
}
EXPORT_SYMBOL_GPL(dw_hdmi_to_plat_data);
-#define HDMI_IH_PHY_STAT0_RX_SENSE \
- (HDMI_IH_PHY_STAT0_RX_SENSE0 | HDMI_IH_PHY_STAT0_RX_SENSE1 | \
- HDMI_IH_PHY_STAT0_RX_SENSE2 | HDMI_IH_PHY_STAT0_RX_SENSE3)
-
-#define HDMI_PHY_RX_SENSE \
- (HDMI_PHY_RX_SENSE0 | HDMI_PHY_RX_SENSE1 | \
- HDMI_PHY_RX_SENSE2 | HDMI_PHY_RX_SENSE3)
-
static inline void hdmi_writeb(struct dw_hdmi *hdmi, u8 val, int offset)
{
regmap_write(hdmi->regm, offset << hdmi->reg_shift, val);
@@ -1702,36 +1690,25 @@ EXPORT_SYMBOL_GPL(dw_hdmi_phy_read_hpd);
void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
bool force, bool disabled, bool rxsense)
{
- u8 old_mask = hdmi->phy_mask;
-
- if (force || disabled || !rxsense)
- hdmi->phy_mask |= HDMI_PHY_RX_SENSE;
- else
- hdmi->phy_mask &= ~HDMI_PHY_RX_SENSE;
-
- if (old_mask != hdmi->phy_mask)
- hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0);
}
EXPORT_SYMBOL_GPL(dw_hdmi_phy_update_hpd);
void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data)
{
/*
- * Configure the PHY RX SENSE and HPD interrupts polarities and clear
- * any pending interrupt.
+ * Configure the PHY HPD interrupt polarity based on current HPD status
+ * and clear any pending interrupt.
*/
- hdmi_writeb(hdmi, HDMI_PHY_HPD | HDMI_PHY_RX_SENSE, HDMI_PHY_POL0);
- hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE,
- HDMI_IH_PHY_STAT0);
+ hdmi_modb(hdmi, hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD ?
+ 0 : HDMI_PHY_HPD, HDMI_PHY_HPD, HDMI_PHY_POL0);
+ hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
/* Enable cable hot plug irq. */
- hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0);
+ hdmi_writeb(hdmi, ~HDMI_PHY_HPD, HDMI_PHY_MASK0);
/* Clear and unmute interrupts. */
- hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE,
- HDMI_IH_PHY_STAT0);
- hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE),
- HDMI_IH_MUTE_PHY_STAT0);
+ hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
+ hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
}
EXPORT_SYMBOL_GPL(dw_hdmi_phy_setup_hpd);
@@ -2395,26 +2372,6 @@ static void dw_hdmi_poweroff(struct dw_hdmi *hdmi)
}
}
-/*
- * Adjust the detection of RXSENSE according to whether we have a forced
- * connection mode enabled, or whether we have been disabled. There is
- * no point processing RXSENSE interrupts if we have a forced connection
- * state, or DRM has us disabled.
- *
- * We also disable rxsense interrupts when we think we're disconnected
- * to avoid floating TDMS signals giving false rxsense interrupts.
- *
- * Note: we still need to listen for HPD interrupts even when DRM has us
- * disabled so that we can detect a connect event.
- */
-static void dw_hdmi_update_phy_mask(struct dw_hdmi *hdmi)
-{
- if (hdmi->phy.ops->update_hpd)
- hdmi->phy.ops->update_hpd(hdmi, hdmi->phy.data,
- hdmi->force, hdmi->disabled,
- hdmi->rxsense);
-}
-
static enum drm_connector_status dw_hdmi_detect(struct dw_hdmi *hdmi)
{
enum drm_connector_status result;
@@ -2512,9 +2469,7 @@ static void dw_hdmi_connector_force(struct drm_connector *connector)
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector);
mutex_lock(&hdmi->mutex);
- hdmi->force = connector->force;
hdmi->last_connector_result = connector->status;
- dw_hdmi_update_phy_mask(hdmi);
mutex_unlock(&hdmi->mutex);
dw_hdmi_connector_status_update(hdmi, connector, connector->status);
@@ -2932,10 +2887,8 @@ static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge,
struct dw_hdmi *hdmi = bridge->driver_private;
mutex_lock(&hdmi->mutex);
- hdmi->disabled = true;
hdmi->curr_conn = NULL;
dw_hdmi_poweroff(hdmi);
- dw_hdmi_update_phy_mask(hdmi);
handle_plugged_change(hdmi, false);
mutex_unlock(&hdmi->mutex);
}
@@ -2954,10 +2907,8 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge,
mode = &drm_atomic_get_new_crtc_state(state, crtc)->adjusted_mode;
mutex_lock(&hdmi->mutex);
- hdmi->disabled = false;
hdmi->curr_conn = connector;
dw_hdmi_poweron(hdmi, connector, mode);
- dw_hdmi_update_phy_mask(hdmi);
handle_plugged_change(hdmi, true);
mutex_unlock(&hdmi->mutex);
}
@@ -3060,78 +3011,29 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id)
void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense)
{
- mutex_lock(&hdmi->mutex);
-
- if (!hdmi->force) {
- /*
- * If the RX sense status indicates we're disconnected,
- * clear the software rxsense status.
- */
- if (!rx_sense)
- hdmi->rxsense = false;
-
- /*
- * Only set the software rxsense status when both
- * rxsense and hpd indicates we're connected.
- * This avoids what seems to be bad behaviour in
- * at least iMX6S versions of the phy.
- */
- if (hpd)
- hdmi->rxsense = true;
-
- dw_hdmi_update_phy_mask(hdmi);
- }
- mutex_unlock(&hdmi->mutex);
}
EXPORT_SYMBOL_GPL(dw_hdmi_setup_rx_sense);
static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
{
struct dw_hdmi *hdmi = dev_id;
- u8 intr_stat, phy_int_pol, phy_pol_mask, phy_stat;
- enum drm_connector_status status = connector_status_unknown;
-
- intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
- phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0);
- phy_stat = hdmi_readb(hdmi, HDMI_PHY_STAT0);
-
- phy_pol_mask = 0;
- if (intr_stat & HDMI_IH_PHY_STAT0_HPD)
- phy_pol_mask |= HDMI_PHY_HPD;
- if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE0)
- phy_pol_mask |= HDMI_PHY_RX_SENSE0;
- if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE1)
- phy_pol_mask |= HDMI_PHY_RX_SENSE1;
- if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE2)
- phy_pol_mask |= HDMI_PHY_RX_SENSE2;
- if (intr_stat & HDMI_IH_PHY_STAT0_RX_SENSE3)
- phy_pol_mask |= HDMI_PHY_RX_SENSE3;
-
- if (phy_pol_mask)
- hdmi_modb(hdmi, ~phy_int_pol, phy_pol_mask, HDMI_PHY_POL0);
+ u8 intr_stat;
/*
- * RX sense tells us whether the TDMS transmitters are detecting
- * load - in other words, there's something listening on the
- * other end of the link. Use this to decide whether we should
- * power on the phy as HPD may be toggled by the sink to merely
- * ask the source to re-read the EDID.
+ * Interrupt generation is accomplished in the following way:
+ * interrupt = (mask == 0) && (polarity == status)
+ * All interrupts are forwarded to the Interrupt Handler sticky bit
+ * register ih_phy_stat0 and muted using the register ih_mute_phy_stat0.
*/
- if (intr_stat &
- (HDMI_IH_PHY_STAT0_RX_SENSE | HDMI_IH_PHY_STAT0_HPD)) {
- dw_hdmi_setup_rx_sense(hdmi,
- phy_stat & HDMI_PHY_HPD,
- phy_stat & HDMI_PHY_RX_SENSE);
+ intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
+ if (intr_stat & HDMI_IH_PHY_STAT0_HPD) {
+ enum drm_connector_status status;
- if ((intr_stat & HDMI_IH_PHY_STAT0_HPD) &&
- (phy_stat & HDMI_PHY_HPD))
- status = connector_status_connected;
+ /* Set HPD interrupt polarity based on current HPD status. */
+ status = dw_hdmi_phy_read_hpd(hdmi, hdmi->phy.data);
+ hdmi_modb(hdmi, status == connector_status_connected ?
+ 0 : HDMI_PHY_HPD, HDMI_PHY_HPD, HDMI_PHY_POL0);
- if (!(phy_stat & (HDMI_PHY_HPD | HDMI_PHY_RX_SENSE)))
- status = connector_status_disconnected;
- }
-
- if (status != connector_status_unknown) {
dev_dbg(hdmi->dev, "EVENT=%s\n",
status == connector_status_connected ?
"plugin" : "plugout");
@@ -3141,8 +3043,7 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
}
hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0);
- hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE),
- HDMI_IH_MUTE_PHY_STAT0);
+ hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
return IRQ_HANDLED;
}
@@ -3343,9 +3244,6 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
hdmi->dev = dev;
hdmi->sample_rate = 48000;
hdmi->channels = 2;
- hdmi->disabled = true;
- hdmi->rxsense = true;
- hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
hdmi->mc_clkdis = 0x7f;
hdmi->last_connector_result = connector_status_disconnected;
--
2.54.0
^ permalink raw reply related
* [PATCH v6 14/22] drm: bridge: dw_hdmi: Update EDID and CEC phys addr in bridge detect()
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
Update EDID and CEC phys addr in the bridge detect() func to closely
match the behavior of a bridge connector with a HDMI bridge attached
and the dw-hdmi connector.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: New patch
This is a temporary change until dw-hdmi is fully converted into a
HDMI bridge in a future part of this multi-series effort.
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 2bb043d64b65..3649ccf8d994 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2969,8 +2969,17 @@ static enum drm_connector_status
dw_hdmi_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector)
{
struct dw_hdmi *hdmi = bridge->driver_private;
+ enum drm_connector_status status;
- return dw_hdmi_detect(hdmi);
+ status = dw_hdmi_detect(hdmi);
+
+ /*
+ * Update EDID and CEC phys addr to match the behavior of a bridge
+ * connector with a HDMI bridge attached and the dw-hdmi connector.
+ */
+ dw_hdmi_connector_status_update(hdmi, connector, status);
+
+ return status;
}
static const struct drm_edid *dw_hdmi_bridge_edid_read(struct drm_bridge *bridge,
--
2.54.0
^ permalink raw reply related
* [PATCH v6 18/22] drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
HDMI Specification Version 1.4b chapter 8.5 mentions:
An HDMI Sink shall not assert high voltage level on its Hot Plug
Detect pin when the E-EDID is not available for reading.
A Source may use a high voltage level Hot Plug Detect signal to
initiate the reading of E-EDID data.
An HDMI Sink shall indicate any change to the contents of the E-EDID
by driving a low voltage level pulse on the Hot Plug Detect pin. This
pulse shall be at least 100 msec.
Use a delayed work to debounce reacting on HPD events to improve
handling of a HPD low voltage level pulse when a sink changes the EDID.
The delayed work is only enabled between enable_hpd()/hpd_enable() and
disable_hpd()/hpd_disable() calls from core, i.e. enabled after
attach/bind/resume and disabled before detach/unbind/suspend.
The 1100 msec hotplug debounce timeout was arbitrarily picked to match
other drivers using same const, and testing using a Raspberry Pi Monitor
seem to use a 200-300 msec pulse when going from standby to power on
state.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Change back to disable_delayed_work_sync() in hpd disable ops,
Ensure HPD interrupt is masked and IRQ handler is disabled early
in dw_hdmi_remove() to prevent any irq re-arming of delayed work,
Drop use of suspend helper
v5: Change to none-sync disable_delayed_work() in hpd disable ops,
Change to cancel_delayed_work_sync() in remove,
Add cancel_delayed_work_sync() to new suspend helper
v4: Disable/mask delayed_work until enable_hpd()/hpd_enable(),
Read connector status directly from HW regs in hpd_work
v3: New patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 76 +++++++++++++++++++++--
1 file changed, 71 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 6cc7b2a860bd..300dac4a6c35 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -50,6 +50,8 @@
#define HDMI14_MAX_TMDSCLK 340000000
+#define HOTPLUG_DEBOUNCE_MS 1100
+
static const u16 csc_coeff_default[3][4] = {
{ 0x2000, 0x0000, 0x0000, 0x0000 },
{ 0x0000, 0x2000, 0x0000, 0x0000 },
@@ -185,6 +187,7 @@ struct dw_hdmi {
hdmi_codec_plugged_cb plugged_cb;
struct device *codec_dev;
enum drm_connector_status last_connector_result;
+ struct delayed_work hpd_work;
};
const struct dw_hdmi_plat_data *dw_hdmi_to_plat_data(struct dw_hdmi *hdmi)
@@ -2517,6 +2520,20 @@ static void dw_hdmi_connector_force(struct drm_connector *connector)
dw_hdmi_connector_status_update(hdmi, connector, connector->status);
}
+static void dw_hdmi_connector_enable_hpd(struct drm_connector *connector)
+{
+ struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector);
+
+ enable_delayed_work(&hdmi->hpd_work);
+}
+
+static void dw_hdmi_connector_disable_hpd(struct drm_connector *connector)
+{
+ struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector);
+
+ disable_delayed_work_sync(&hdmi->hpd_work);
+}
+
static void dw_hdmi_connector_destroy(struct drm_connector *connector)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector);
@@ -2538,6 +2555,8 @@ static const struct drm_connector_funcs dw_hdmi_connector_funcs = {
static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = {
.get_modes = dw_hdmi_connector_get_modes,
.atomic_check = dw_hdmi_connector_atomic_check,
+ .enable_hpd = dw_hdmi_connector_enable_hpd,
+ .disable_hpd = dw_hdmi_connector_disable_hpd,
};
static int dw_hdmi_connector_create(struct dw_hdmi *hdmi)
@@ -2968,6 +2987,20 @@ static const struct drm_edid *dw_hdmi_bridge_edid_read(struct drm_bridge *bridge
return dw_hdmi_edid_read(hdmi, connector);
}
+static void dw_hdmi_bridge_hpd_enable(struct drm_bridge *bridge)
+{
+ struct dw_hdmi *hdmi = bridge->driver_private;
+
+ enable_delayed_work(&hdmi->hpd_work);
+}
+
+static void dw_hdmi_bridge_hpd_disable(struct drm_bridge *bridge)
+{
+ struct dw_hdmi *hdmi = bridge->driver_private;
+
+ disable_delayed_work_sync(&hdmi->hpd_work);
+}
+
static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
@@ -2981,6 +3014,8 @@ static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
.mode_valid = dw_hdmi_bridge_mode_valid,
.detect = dw_hdmi_bridge_detect,
.edid_read = dw_hdmi_bridge_edid_read,
+ .hpd_enable = dw_hdmi_bridge_hpd_enable,
+ .hpd_disable = dw_hdmi_bridge_hpd_disable,
};
/* -----------------------------------------------------------------------------
@@ -3101,8 +3136,8 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
status == connector_status_connected ?
"plugin" : "plugout");
- if (hdmi->bridge.dev)
- drm_helper_hpd_irq_event(hdmi->bridge.dev);
+ mod_delayed_work(system_percpu_wq, &hdmi->hpd_work,
+ msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
}
hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0);
@@ -3112,6 +3147,29 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static void dw_hdmi_hpd_work(struct work_struct *work)
+{
+ struct dw_hdmi *hdmi = container_of(work, struct dw_hdmi, hpd_work.work);
+ struct drm_device *dev = hdmi->bridge.dev;
+
+ if (WARN_ON(!dev))
+ return;
+
+ /*
+ * Notify the DRM core of the HPD event using drm_helper_hpd_irq_event()
+ * instead of drm_bridge_hpd_notify(). This will cause the DRM function
+ * check_connector_changed() to be called, which in turn calls the
+ * connector detect()/force() funcs to detect any connection status or
+ * epoch changes. The bridge connector detect() func also ensures that
+ * any hpd_notify() funcs are called for all bridges in the chain.
+ *
+ * drm_bridge_hpd_notify() shares a mutex with drm_bridge_hpd_disable(),
+ * and can result in a deadlock due to the disable_delayed_work_sync()
+ * call to wait on work to complete in dw_hdmi_bridge_hpd_disable().
+ */
+ drm_helper_hpd_irq_event(dev);
+}
+
static const struct dw_hdmi_phy_data dw_hdmi_phys[] = {
{
.type = DW_HDMI_PHY_DWC_HDMI_TX_PHY,
@@ -3396,6 +3454,9 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
goto err_res;
}
+ INIT_DELAYED_WORK(&hdmi->hpd_work, dw_hdmi_hpd_work);
+ disable_delayed_work(&hdmi->hpd_work);
+
ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq,
dw_hdmi_irq, IRQF_SHARED,
dev_name(dev), hdmi);
@@ -3531,6 +3592,14 @@ EXPORT_SYMBOL_GPL(dw_hdmi_probe);
void dw_hdmi_remove(struct dw_hdmi *hdmi)
{
+ struct platform_device *pdev = to_platform_device(hdmi->dev);
+ int irq = platform_get_irq(pdev, 0);
+
+ /* Disable cable hot plug irq. */
+ hdmi_writeb(hdmi, ~0, HDMI_PHY_MASK0);
+ devm_free_irq(hdmi->dev, irq, hdmi);
+ cancel_delayed_work_sync(&hdmi->hpd_work);
+
drm_bridge_remove(&hdmi->bridge);
if (hdmi->audio && !IS_ERR(hdmi->audio))
@@ -3538,9 +3607,6 @@ void dw_hdmi_remove(struct dw_hdmi *hdmi)
if (!IS_ERR(hdmi->cec))
platform_device_unregister(hdmi->cec);
- /* Disable all interrupts */
- hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
-
if (hdmi->i2c)
i2c_del_adapter(&hdmi->i2c->adap);
else
--
2.54.0
^ permalink raw reply related
* [PATCH v6 12/22] drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update()
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
Update connector EDID and CEC phys addr from detect and force funcs to
ensure that userspace always have access to latest read EDID after a
sink use a HPD low voltage pulse to indicate that EDID has changed.
With EDID being updated in detect and force funcs, there should no
longer be a need to re-read EDID in get_modes funcs, so drop it.
This change make the dw-hdmi connector work more closely like the bridge
connector does with a hdmi bridge.
Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Pass struct dw_hdmi as a parameter,
Collect t-b tag
v5: No change
v4: Move last_connector_result assign in force ops to this patch,
Collect r-b tag
v3: Reworked 'Update EDID during hotplug processing' patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 28 ++++++++++++-----------
1 file changed, 15 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index a056e147731b..a4ecf830103d 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2473,36 +2473,36 @@ dw_hdmi_connector_status_update(struct dw_hdmi *hdmi,
{
const struct drm_edid *drm_edid;
+ if (status == connector_status_disconnected) {
+ drm_edid_connector_update(connector, NULL);
+ cec_notifier_phys_addr_invalidate(hdmi->cec_notifier);
+ return;
+ }
+
drm_edid = dw_hdmi_edid_read(hdmi, connector);
drm_edid_connector_update(connector, drm_edid);
drm_edid_free(drm_edid);
- cec_notifier_set_phys_addr(hdmi->cec_notifier,
- connector->display_info.source_physical_address);
+ if (status == connector_status_connected)
+ cec_notifier_set_phys_addr(hdmi->cec_notifier,
+ connector->display_info.source_physical_address);
}
static enum drm_connector_status
dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
- struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
- connector);
+ struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector);
enum drm_connector_status status;
status = dw_hdmi_detect(hdmi);
- if (status == connector_status_disconnected)
- cec_notifier_phys_addr_invalidate(hdmi->cec_notifier);
+ dw_hdmi_connector_status_update(hdmi, connector, status);
return status;
}
static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
{
- struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
- connector);
-
- dw_hdmi_connector_status_update(hdmi, connector, connector->status);
-
return drm_edid_connector_add_modes(connector);
}
@@ -2532,13 +2532,15 @@ static int dw_hdmi_connector_atomic_check(struct drm_connector *connector,
static void dw_hdmi_connector_force(struct drm_connector *connector)
{
- struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
- connector);
+ struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector);
mutex_lock(&hdmi->mutex);
hdmi->force = connector->force;
+ hdmi->last_connector_result = connector->status;
dw_hdmi_update_phy_mask(hdmi);
mutex_unlock(&hdmi->mutex);
+
+ dw_hdmi_connector_status_update(hdmi, connector, connector->status);
}
static void dw_hdmi_connector_destroy(struct drm_connector *connector)
--
2.54.0
^ permalink raw reply related
* [PATCH v6 07/22] drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
drmres connector cleanup typically run after devres has released the
last dw-hdmi bridge reference. Since struct dw_hdmi, where the connector
lives, is freed when the last bridge reference is released, connector
cleanup can end up accessing freed memory.
Call trace without a bridge reference held until connector cleanup:
- dw_hdmi_bridge_detach()
- dw_hdmi_bridge_destroy() <<-- struct dw_hdmi is free()
- [drm:drm_managed_release] drmres release begin
- [drm:drm_managed_release] REL (____ptrval____) drm_mode_config_init_release (0 bytes)
- dw_hdmi_connector_destroy()
- drm_connector_cleanup() <<-- drm_connector is use-after-free
[...]
- [drm:drm_managed_release] drmres release end
Hold a bridge reference for as long as the connector exists and drop it
after drm_connector_cleanup() has completed to keep struct dw_hdmi alive
until connector teardown is finished and avoids the use-after-free.
Call trace with a bridge reference held until connector cleanup:
- dw_hdmi_bridge_detach()
- [drm:drm_managed_release] drmres release begin
- [drm:drm_managed_release] REL (____ptrval____) drm_mode_config_init_release (0 bytes)
- dw_hdmi_connector_destroy()
- drm_connector_cleanup() <<-- drm_connector is destroy()
- drm_bridge_put()
- dw_hdmi_bridge_destroy() <<-- struct dw_hdmi is free()
[...]
- [drm:drm_managed_release] drmres release end
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: New patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index a176eb55418c..cbbd15578042 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2528,10 +2528,18 @@ static void dw_hdmi_connector_force(struct drm_connector *connector)
mutex_unlock(&hdmi->mutex);
}
+static void dw_hdmi_connector_destroy(struct drm_connector *connector)
+{
+ struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector);
+
+ drm_connector_cleanup(connector);
+ drm_bridge_put(&hdmi->bridge);
+}
+
static const struct drm_connector_funcs dw_hdmi_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = dw_hdmi_connector_detect,
- .destroy = drm_connector_cleanup,
+ .destroy = dw_hdmi_connector_destroy,
.force = dw_hdmi_connector_force,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
@@ -2548,6 +2556,7 @@ static int dw_hdmi_connector_create(struct dw_hdmi *hdmi)
struct drm_connector *connector = &hdmi->connector;
struct cec_connector_info conn_info;
struct cec_notifier *notifier;
+ int ret;
if (hdmi->version >= 0x200a)
connector->ycbcr_420_allowed =
@@ -2560,10 +2569,14 @@ static int dw_hdmi_connector_create(struct dw_hdmi *hdmi)
drm_connector_helper_add(connector, &dw_hdmi_connector_helper_funcs);
- drm_connector_init_with_ddc(hdmi->bridge.dev, connector,
- &dw_hdmi_connector_funcs,
- DRM_MODE_CONNECTOR_HDMIA,
- hdmi->ddc);
+ ret = drm_connector_init_with_ddc(hdmi->bridge.dev, connector,
+ &dw_hdmi_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA,
+ hdmi->ddc);
+ if (ret)
+ return ret;
+
+ drm_bridge_get(&hdmi->bridge);
/*
* drm_connector_attach_max_bpc_property() requires the
--
2.54.0
^ permalink raw reply related
* [PATCH v6 22/22] drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
The bottom half IRQ handler only modify delay of or queue a delayed work
used for HPD handling. The mod_delayed_work() called is documented as
being safe to call from any context including IRQ handler.
Merge top and bottom half IRQ handlers to simplify IRQ handling now that
HPD event is handled using a delayed work.
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect r-b tag
v5: No change
v4: New patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 34 ++++++++---------------
1 file changed, 11 insertions(+), 23 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 79851ae27496..a9b2707149b6 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2993,30 +2993,18 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id)
if (hdmi->i2c)
ret = dw_hdmi_i2c_irq(hdmi);
- intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
- if (intr_stat) {
- hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
- return IRQ_WAKE_THREAD;
- }
-
- return ret;
-}
-
-static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
-{
- struct dw_hdmi *hdmi = dev_id;
- u8 intr_stat;
-
/*
* Interrupt generation is accomplished in the following way:
* interrupt = (mask == 0) && (polarity == status)
* All interrupts are forwarded to the Interrupt Handler sticky bit
* register ih_phy_stat0 and muted using the register ih_mute_phy_stat0.
*/
- intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
- if (intr_stat & HDMI_IH_PHY_STAT0_HPD) {
+ intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0) & HDMI_IH_PHY_STAT0_HPD;
+ if (intr_stat) {
enum drm_connector_status status;
+ hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
+
/* Set HPD interrupt polarity based on current HPD status. */
status = dw_hdmi_phy_read_hpd(hdmi, hdmi->phy.data);
hdmi_modb(hdmi, status == connector_status_connected ?
@@ -3028,12 +3016,13 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
mod_delayed_work(system_percpu_wq, &hdmi->hpd_work,
msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
+
+ hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0);
+ hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
+ ret = IRQ_HANDLED;
}
- hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0);
- hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
-
- return IRQ_HANDLED;
+ return ret;
}
static void dw_hdmi_hpd_work(struct work_struct *work)
@@ -3343,9 +3332,8 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
INIT_DELAYED_WORK(&hdmi->hpd_work, dw_hdmi_hpd_work);
disable_delayed_work(&hdmi->hpd_work);
- ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq,
- dw_hdmi_irq, IRQF_SHARED,
- dev_name(dev), hdmi);
+ ret = devm_request_irq(dev, irq, dw_hdmi_hardirq, IRQF_SHARED,
+ dev_name(dev), hdmi);
if (ret)
goto err_res;
--
2.54.0
^ permalink raw reply related
* [PATCH v6 21/22] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd()
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Liu Ying, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Luca Ceresoli, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Sandy Huang,
Andy Yan, Chen-Yu Tsai, Samuel Holland
Cc: Christian Hewitt, Diederik de Haas, Nicolas Frattaroli,
Dmitry Baryshkov, dri-devel, linux-arm-kernel, linux-rockchip,
linux-amlogic, linux-sunxi, imx, linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
The dw_hdmi_phy_update_hpd() helper is empty and no longer needed after
recent RXSENSE and HPD rework, remove it.
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: No change
v4: New patch
---
drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 1 -
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 7 -------
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 2 --
drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 2 --
include/drm/bridge/dw_hdmi.h | 4 ----
5 files changed, 16 deletions(-)
diff --git a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c
index 8e8cfd66f23b..20d389dbfdc5 100644
--- a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c
+++ b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c
@@ -78,7 +78,6 @@ static const struct dw_hdmi_phy_ops imx8mp_hdmi_phy_ops = {
.disable = imx8mp_hdmi_phy_disable,
.setup_hpd = im8mp_hdmi_phy_setup_hpd,
.read_hpd = dw_hdmi_phy_read_hpd,
- .update_hpd = dw_hdmi_phy_update_hpd,
};
static int imx8mp_dw_hdmi_bind(struct device *dev)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 59864b9084f6..79851ae27496 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -1687,12 +1687,6 @@ enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi,
}
EXPORT_SYMBOL_GPL(dw_hdmi_phy_read_hpd);
-void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
- bool force, bool disabled, bool rxsense)
-{
-}
-EXPORT_SYMBOL_GPL(dw_hdmi_phy_update_hpd);
-
void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data)
{
/*
@@ -1716,7 +1710,6 @@ static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = {
.init = dw_hdmi_phy_init,
.disable = dw_hdmi_phy_disable,
.read_hpd = dw_hdmi_phy_read_hpd,
- .update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_phy_setup_hpd,
};
diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
index 0dc1eb5d2ae3..7136e713df2e 100644
--- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
+++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
@@ -413,7 +413,6 @@ static const struct dw_hdmi_phy_ops rk3228_hdmi_phy_ops = {
.init = dw_hdmi_rockchip_genphy_init,
.disable = dw_hdmi_rockchip_genphy_disable,
.read_hpd = dw_hdmi_phy_read_hpd,
- .update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_rk3228_setup_hpd,
};
@@ -449,7 +448,6 @@ static const struct dw_hdmi_phy_ops rk3328_hdmi_phy_ops = {
.init = dw_hdmi_rockchip_genphy_init,
.disable = dw_hdmi_rockchip_genphy_disable,
.read_hpd = dw_hdmi_rk3328_read_hpd,
- .update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_rk3328_setup_hpd,
};
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
index 4fa69c463dc4..2ac99b8ce8c4 100644
--- a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
+++ b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
@@ -221,7 +221,6 @@ static const struct dw_hdmi_phy_ops sun8i_a83t_hdmi_phy_ops = {
.init = sun8i_a83t_hdmi_phy_config,
.disable = sun8i_a83t_hdmi_phy_disable,
.read_hpd = dw_hdmi_phy_read_hpd,
- .update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_phy_setup_hpd,
};
@@ -395,7 +394,6 @@ static const struct dw_hdmi_phy_ops sun8i_h3_hdmi_phy_ops = {
.init = sun8i_h3_hdmi_phy_config,
.disable = sun8i_h3_hdmi_phy_disable,
.read_hpd = dw_hdmi_phy_read_hpd,
- .update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_phy_setup_hpd,
};
diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
index a612b9fa6dbb..10013b8d3adb 100644
--- a/include/drm/bridge/dw_hdmi.h
+++ b/include/drm/bridge/dw_hdmi.h
@@ -118,8 +118,6 @@ struct dw_hdmi_phy_ops {
const struct drm_display_mode *mode);
void (*disable)(struct dw_hdmi *hdmi, void *data);
enum drm_connector_status (*read_hpd)(struct dw_hdmi *hdmi, void *data);
- void (*update_hpd)(struct dw_hdmi *hdmi, void *data,
- bool force, bool disabled, bool rxsense);
void (*setup_hpd)(struct dw_hdmi *hdmi, void *data);
};
@@ -213,8 +211,6 @@ void dw_hdmi_phy_gen2_reset(struct dw_hdmi *hdmi);
enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi,
void *data);
-void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
- bool force, bool disabled, bool rxsense);
void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data);
bool dw_hdmi_bus_fmt_is_420(struct dw_hdmi *hdmi);
--
2.54.0
^ permalink raw reply related
* [PATCH v6 20/22] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense()
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Kevin Hilman, Jerome Brunet, Martin Blumenstingl
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
The dw_hdmi_setup_rx_sense() helper is empty and no longer needed after
recent RXSENSE and HPD rework, remove it.
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: No change
v4: New patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 5 -----
drivers/gpu/drm/meson/meson_dw_hdmi.c | 3 ---
include/drm/bridge/dw_hdmi.h | 2 --
3 files changed, 10 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 39e94d22249b..59864b9084f6 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3009,11 +3009,6 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id)
return ret;
}
-void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense)
-{
-}
-EXPORT_SYMBOL_GPL(dw_hdmi_setup_rx_sense);
-
static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
{
struct dw_hdmi *hdmi = dev_id;
diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c
index fef1702acb14..2a8756da569b 100644
--- a/drivers/gpu/drm/meson/meson_dw_hdmi.c
+++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c
@@ -524,9 +524,6 @@ static irqreturn_t dw_hdmi_top_thread_irq(int irq, void *dev_id)
if (stat & HDMITX_TOP_INTR_HPD_RISE)
hpd_connected = true;
- dw_hdmi_setup_rx_sense(dw_hdmi->hdmi, hpd_connected,
- hpd_connected);
-
drm_helper_hpd_irq_event(dw_hdmi->bridge->dev);
drm_bridge_hpd_notify(dw_hdmi->bridge,
hpd_connected ? connector_status_connected
diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h
index 8500dd4f99d8..a612b9fa6dbb 100644
--- a/include/drm/bridge/dw_hdmi.h
+++ b/include/drm/bridge/dw_hdmi.h
@@ -186,8 +186,6 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
void dw_hdmi_resume(struct dw_hdmi *hdmi);
-void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense);
-
int dw_hdmi_set_plugged_cb(struct dw_hdmi *hdmi, hdmi_codec_plugged_cb fn,
struct device *codec_dev);
void dw_hdmi_set_sample_non_pcm(struct dw_hdmi *hdmi, unsigned int non_pcm);
--
2.54.0
^ permalink raw reply related
* [PATCH v6 17/22] drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify()
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
The use of calls to both drm_helper_hpd_irq_event() and
drm_bridge_hpd_notify() from the HPD IRQ handler may cause multiple
hotplug uevents and modesets when the bridge connector is used.
Use of drm_helper_hpd_irq_event() cause the internal DRM function
check_connector_changed() to be called, which in turn calls the
connector detect()/force() funcs to detect any connection status or
epoch changes, and when changed trigger a hotplug uevent. This also
help ensure that EDID and CEC phys addr is updated.
If only a call drm_bridge_hpd_notify() would be used, a custom connector
status/EDID change detection logic needs to be implemented, to fully
match what check_connector_changed() already provides.
The bridge connector detect() func also ensures that any hpd_notify()
funcs are called for all bridges in the chain, so there is not really
any need to have a call to drm_bridge_hpd_notify() here.
With both calls there is two hotplug uevents, two modesets and a total
of four .hpd_notify() calls (using a bridge connector):
dw_hdmi_irq(): EVENT=plugout
drm_helper_hpd_irq_event():
dw_hdmi_bridge_hpd_notify(status=2)
[drm:check_connector_changed] [CONNECTOR:46:HDMI-A-1] status updated from connected to disconnected
[drm:check_connector_changed] [CONNECTOR:46:HDMI-A-1] Changed epoch counter 1 => 2
[drm:drm_sysfs_connector_hotplug_event] [CONNECTOR:46:HDMI-A-1] generating connector hotplug event
drm_client_hotplug():
[drm:drm_fb_helper_hotplug_event]
[drm:drm_client_modeset_probe]
[drm:drm_helper_probe_single_connector_modes] [CONNECTOR:46:HDMI-A-1]
dw_hdmi_bridge_hpd_notify(status=2)
[drm:drm_helper_probe_single_connector_modes] [CONNECTOR:46:HDMI-A-1] disconnected
[drm:drm_edid_connector_update] [CONNECTOR:46:HDMI-A-1] EDID changed, epoch counter 3
[drm:drm_client_modeset_probe] No connectors reported connected with modes
[drm:drm_client_modeset_probe] [CONNECTOR:46:HDMI-A-1] enabled? no
[drm:drm_client_firmware_config.isra.0] Not using firmware configuration
[drm:drm_client_modeset_probe] picking CRTCs for 3840x2160 config
[drm:drm_client_hotplug] fbdev: ret=0
drm_bridge_hpd_notify():
dw_hdmi_bridge_hpd_notify(status=2)
[drm:drm_sysfs_connector_hotplug_event] [CONNECTOR:46:HDMI-A-1] generating connector hotplug event
drm_client_hotplug():
[drm:drm_fb_helper_hotplug_event]
[drm:drm_client_modeset_probe]
[drm:drm_helper_probe_single_connector_modes] [CONNECTOR:46:HDMI-A-1]
dw_hdmi_bridge_hpd_notify(status=2)
[drm:drm_helper_probe_single_connector_modes] [CONNECTOR:46:HDMI-A-1] disconnected
[drm:drm_client_modeset_probe] No connectors reported connected with modes
[drm:drm_client_modeset_probe] [CONNECTOR:46:HDMI-A-1] enabled? no
[drm:drm_client_firmware_config.isra.0] Not using firmware configuration
[drm:drm_client_modeset_probe] picking CRTCs for 3840x2160 config
[drm:drm_client_hotplug] fbdev: ret=0
Change to only call drm_helper_hpd_irq_event() from HPD IRQ handler to
ensure that only one hotplug uevent is sent to userspace when connection
status or EDID changes.
With only a call the drm_helper_hpd_irq_event() there is only a single
hotplug uevent and only two .hpd_notify() calls:
dw_hdmi_irq(): EVENT=plugout
drm_helper_hpd_irq_event():
dw_hdmi_bridge_hpd_notify(status=2)
[drm:check_connector_changed] [CONNECTOR:46:HDMI-A-1] status updated from connected to disconnected
[drm:check_connector_changed] [CONNECTOR:46:HDMI-A-1] Changed epoch counter 1 => 2
[drm:drm_sysfs_connector_hotplug_event] [CONNECTOR:46:HDMI-A-1] generating connector hotplug event
drm_client_hotplug():
[drm:drm_fb_helper_hotplug_event]
[drm:drm_client_modeset_probe]
[drm:drm_helper_probe_single_connector_modes] [CONNECTOR:46:HDMI-A-1]
dw_hdmi_bridge_hpd_notify(status=2)
[drm:drm_helper_probe_single_connector_modes] [CONNECTOR:46:HDMI-A-1] disconnected
[drm:drm_edid_connector_update] [CONNECTOR:46:HDMI-A-1] EDID changed, epoch counter 3
[drm:drm_client_modeset_probe] No connectors reported connected with modes
[drm:drm_client_modeset_probe] [CONNECTOR:46:HDMI-A-1] enabled? no
[drm:drm_client_firmware_config.isra.0] Not using firmware configuration
[drm:drm_client_modeset_probe] picking CRTCs for 3840x2160 config
[drm:drm_client_hotplug] fbdev: ret=0
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Drop the call from IRQ handler instead, prior to use of a HPD
delayed work to avoid a possible deadlock with use of sync() calls
in the bridge hpd_disable() ops,
Update commit message,
Collect t-b tag
v5: New patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index e9c4e24c090c..6cc7b2a860bd 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3101,10 +3101,8 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
status == connector_status_connected ?
"plugin" : "plugout");
- if (hdmi->bridge.dev) {
+ if (hdmi->bridge.dev)
drm_helper_hpd_irq_event(hdmi->bridge.dev);
- drm_bridge_hpd_notify(&hdmi->bridge, status);
- }
}
hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0);
--
2.54.0
^ permalink raw reply related
* [PATCH v6 16/22] drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
drm_edid_connector_update() is being called from bridge connector funcs
and from detect and force funcs for dw-hdmi connector.
Change to use is_hdmi and has_audio from display_info directly instead
of keeping our own state in sink_is_hdmi and sink_has_audio.
Also remove the old and unused edid struct member and related define.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: No change
v4: Collect r-b tag
v3: No change
v2: Collect r-b tag
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 32 ++++-------------------
1 file changed, 5 insertions(+), 27 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 1402b3963ae1..e9c4e24c090c 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -45,8 +45,6 @@
#define DDC_CI_ADDR 0x37
#define DDC_SEGMENT_ADDR 0x30
-#define HDMI_EDID_LEN 512
-
/* DW-HDMI Controller >= 0x200a are at least compliant with SCDC version 1 */
#define SCDC_MIN_SOURCE_VERSION 0x1
@@ -146,8 +144,6 @@ struct dw_hdmi {
int vic;
- u8 edid[HDMI_EDID_LEN];
-
struct {
const struct dw_hdmi_phy_ops *ops;
const char *name;
@@ -157,8 +153,6 @@ struct dw_hdmi {
struct i2c_adapter *ddc;
void __iomem *regs;
- bool sink_is_hdmi;
- bool sink_has_audio;
struct pinctrl *pinctrl;
struct pinctrl_state *default_state;
@@ -2053,7 +2047,7 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
HDMI_FC_INVIDCONF_IN_I_P_INTERLACED :
HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE;
- inv_val |= hdmi->sink_is_hdmi ?
+ inv_val |= display->is_hdmi ?
HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE :
HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE;
@@ -2289,7 +2283,7 @@ static int dw_hdmi_poweron(struct dw_hdmi *hdmi,
if (hdmi->hdmi_data.enc_out_bus_format == MEDIA_BUS_FMT_FIXED)
hdmi->hdmi_data.enc_out_bus_format = MEDIA_BUS_FMT_RGB888_1X24;
- hdmi->hdmi_data.rgb_limited_range = hdmi->sink_is_hdmi &&
+ hdmi->hdmi_data.rgb_limited_range = display->is_hdmi &&
drm_default_rgb_quant_range(mode) ==
HDMI_QUANTIZATION_RANGE_LIMITED;
@@ -2309,7 +2303,7 @@ static int dw_hdmi_poweron(struct dw_hdmi *hdmi,
/* HDMI Initialization Step B.3 */
dw_hdmi_enable_video_path(hdmi);
- if (hdmi->sink_has_audio) {
+ if (display->has_audio) {
dev_dbg(hdmi->dev, "sink has audio support\n");
/* HDMI Initialization Step E - Configure audio */
@@ -2318,7 +2312,7 @@ static int dw_hdmi_poweron(struct dw_hdmi *hdmi,
}
/* not for DVI mode */
- if (hdmi->sink_is_hdmi) {
+ if (display->is_hdmi) {
dev_dbg(hdmi->dev, "%s HDMI mode\n", __func__);
/* HDMI Initialization Step F - Configure AVI InfoFrame */
@@ -2432,29 +2426,13 @@ static const struct drm_edid *dw_hdmi_edid_read(struct dw_hdmi *hdmi,
struct drm_connector *connector)
{
const struct drm_edid *drm_edid;
- const struct edid *edid;
if (!hdmi->ddc)
return NULL;
drm_edid = drm_edid_read_ddc(connector, hdmi->ddc);
- if (!drm_edid) {
+ if (!drm_edid)
dev_dbg(hdmi->dev, "failed to get edid\n");
- return NULL;
- }
-
- /*
- * FIXME: This should use connector->display_info.is_hdmi and
- * connector->display_info.has_audio from a path that has read the EDID
- * and called drm_edid_connector_update().
- */
- edid = drm_edid_raw(drm_edid);
-
- dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n",
- edid->width_cm, edid->height_cm);
-
- hdmi->sink_is_hdmi = drm_detect_hdmi_monitor(edid);
- hdmi->sink_has_audio = drm_detect_monitor_audio(edid);
return drm_edid;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v6 15/22] drm: bridge: dw_hdmi: Declare bridge CEC notifier support
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
EDID and CEC phys addr is now being updated in bridge detect() func,
making it possible to have CEC notifier support using the bridge
connector.
Add the CEC notifier bridge op to instruct the bridge connector to make
use of the generic CEC notifier helpers.
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: New patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 3649ccf8d994..1402b3963ae1 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3537,6 +3537,9 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
pdevinfo.dma_mask = 0;
hdmi->cec = platform_device_register_full(&pdevinfo);
+
+ hdmi->bridge.ops |= DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER;
+ hdmi->bridge.hdmi_cec_dev = hdmi->dev;
}
drm_bridge_add(&hdmi->bridge);
--
2.54.0
^ permalink raw reply related
* [PATCH v6 13/22] drm: bridge: dw_hdmi: Use generic CEC notifier helpers
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
The commit 8b1a8f8b2002 ("drm/display: add CEC helpers code") added
generic CEC helpers to be used by HDMI drivers.
Replace the open-coded CEC notifier handling with use of the generic CEC
notifier helpers. Ensure DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER is also
selected when DRM_DW_HDMI_CEC is enabled so that the CEC helpers is
available.
The drmm release action for the generic CEC notifier should run just
before dw_hdmi_connector_destroy(), closely matching the lifetime of
the replaced CEC notifier and the connector.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Update commit message,
Collect t-b tag
v5: Collect r-b tag
v4: New patch
---
drivers/gpu/drm/bridge/synopsys/Kconfig | 1 +
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 26 +++++------------------
2 files changed, 6 insertions(+), 21 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig
index a46df7583bcf..e6723af03b43 100644
--- a/drivers/gpu/drm/bridge/synopsys/Kconfig
+++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
@@ -49,6 +49,7 @@ config DRM_DW_HDMI_CEC
depends on DRM_DW_HDMI
select CEC_CORE
select CEC_NOTIFIER
+ select DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER
help
Support the CE interface which is part of the Synopsys
Designware HDMI block.
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index a4ecf830103d..2bb043d64b65 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -23,12 +23,11 @@
#include <linux/dma-mapping.h>
#include <linux/spinlock.h>
-#include <media/cec-notifier.h>
-
#include <linux/media-bus-format.h>
#include <linux/videodev2.h>
#include <drm/bridge/dw_hdmi.h>
+#include <drm/display/drm_hdmi_cec_helper.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_scdc_helper.h>
#include <drm/drm_atomic.h>
@@ -189,8 +188,6 @@ struct dw_hdmi {
void (*enable_audio)(struct dw_hdmi *hdmi);
void (*disable_audio)(struct dw_hdmi *hdmi);
- struct cec_notifier *cec_notifier;
-
hdmi_codec_plugged_cb plugged_cb;
struct device *codec_dev;
enum drm_connector_status last_connector_result;
@@ -2475,7 +2472,7 @@ dw_hdmi_connector_status_update(struct dw_hdmi *hdmi,
if (status == connector_status_disconnected) {
drm_edid_connector_update(connector, NULL);
- cec_notifier_phys_addr_invalidate(hdmi->cec_notifier);
+ drm_connector_cec_phys_addr_invalidate(connector);
return;
}
@@ -2484,8 +2481,7 @@ dw_hdmi_connector_status_update(struct dw_hdmi *hdmi,
drm_edid_free(drm_edid);
if (status == connector_status_connected)
- cec_notifier_set_phys_addr(hdmi->cec_notifier,
- connector->display_info.source_physical_address);
+ drm_connector_cec_phys_addr_set(connector);
}
static enum drm_connector_status
@@ -2547,9 +2543,6 @@ static void dw_hdmi_connector_destroy(struct drm_connector *connector)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector);
- cec_notifier_conn_unregister(hdmi->cec_notifier);
- hdmi->cec_notifier = NULL;
-
drm_connector_cleanup(connector);
drm_bridge_put(&hdmi->bridge);
}
@@ -2572,8 +2565,6 @@ static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs =
static int dw_hdmi_connector_create(struct dw_hdmi *hdmi)
{
struct drm_connector *connector = &hdmi->connector;
- struct cec_connector_info conn_info;
- struct cec_notifier *notifier;
int ret;
if (hdmi->version >= 0x200a)
@@ -2609,15 +2600,8 @@ static int dw_hdmi_connector_create(struct dw_hdmi *hdmi)
drm_connector_attach_encoder(connector, hdmi->bridge.encoder);
- cec_fill_conn_info_from_drm(&conn_info, connector);
-
- notifier = cec_notifier_conn_register(hdmi->dev, NULL, &conn_info);
- if (!notifier)
- return -ENOMEM;
-
- hdmi->cec_notifier = notifier;
-
- return 0;
+ return drmm_connector_hdmi_cec_notifier_register(connector, NULL,
+ hdmi->dev);
}
/* -----------------------------------------------------------------------------
--
2.54.0
^ permalink raw reply related
* [PATCH v6 06/22] drm: bridge: dw_hdmi: Remove previous_mode and mode_set
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
With the use of adjusted_mode directly from the crtc_state there is no
longer a need to store a copy in previous_mode, remove it and the now
unneeded mode_set ops.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: No change, rebase on next-20260508
v4: No change
v3: Collect r-b tag
v2: No change
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 19 +------------------
1 file changed, 1 insertion(+), 18 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 607faf4da967..a176eb55418c 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -156,8 +156,6 @@ struct dw_hdmi {
bool enabled;
} phy;
- struct drm_display_mode previous_mode;
-
struct i2c_adapter *ddc;
void __iomem *regs;
bool sink_is_hdmi;
@@ -167,7 +165,7 @@ struct dw_hdmi {
struct pinctrl_state *default_state;
struct pinctrl_state *unwedge_state;
- struct mutex mutex; /* for state below and previous_mode */
+ struct mutex mutex; /* for state below */
enum drm_connector_force force; /* mutex-protected force state */
struct drm_connector *curr_conn;/* current connector (only valid when !disabled) */
bool disabled; /* DRM has disabled our bridge */
@@ -2928,20 +2926,6 @@ dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge,
return mode_status;
}
-static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge,
- const struct drm_display_mode *orig_mode,
- const struct drm_display_mode *mode)
-{
- struct dw_hdmi *hdmi = bridge->driver_private;
-
- mutex_lock(&hdmi->mutex);
-
- /* Store the display mode for plugin/DKMS poweron events */
- drm_mode_copy(&hdmi->previous_mode, mode);
-
- mutex_unlock(&hdmi->mutex);
-}
-
static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
@@ -3005,7 +2989,6 @@ static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
.atomic_get_input_bus_fmts = dw_hdmi_bridge_atomic_get_input_bus_fmts,
.atomic_enable = dw_hdmi_bridge_atomic_enable,
.atomic_disable = dw_hdmi_bridge_atomic_disable,
- .mode_set = dw_hdmi_bridge_mode_set,
.mode_valid = dw_hdmi_bridge_mode_valid,
.detect = dw_hdmi_bridge_detect,
.edid_read = dw_hdmi_bridge_edid_read,
--
2.54.0
^ permalink raw reply related
* [PATCH v6 02/22] drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Lucas Stach
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
drm_helper_hpd_irq_event() and drm_bridge_hpd_notify() may incorrectly
be called with a connected status when HPD is high and RX sense is
changed. This typically happens when the HDMI cable is unplugged,
shortly before the HPD is changed to low.
The original intent of commit da09daf88108 ("drm: bridge: dw_hdmi: only
trigger hotplug event on link change") was to signal hotplug event at
correct interrupt states.
Based on the commit message the intent was to trigger hotplug event:
- when HPD goes high (plugin)
- when both HPD and RX sense has gone low (plugout)
However, following interrupt state changes can typically be observed
when the HDMI cable is unplugged:
- RX interrupt: HPD=high RX=low -> triggers a connected event
- HPD interrupt: HPD=low RX=low -> triggers a disconnected event
Fix this by only notify connected status on the HPD interrupt when HPD
is going high, not on the RX sense interrupt when RX sense is changed.
After this a connected event should be triggered when HPD=high at HPD
interrupt, and a disconnected event should be triggered when both
HPD=low and RX=low at either HPD or RX interrupt.
Fixes: da09daf88108 ("drm: bridge: dw_hdmi: only trigger hotplug event on link change")
Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: No change
v4: Collect r-b tag
v3: Update commit message
v2: New patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index d3e6a6562870..b7bfc0e9a6b2 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -3157,7 +3157,8 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
mutex_unlock(&hdmi->cec_notifier_mutex);
}
- if (phy_stat & HDMI_PHY_HPD)
+ if ((intr_stat & HDMI_IH_PHY_STAT0_HPD) &&
+ (phy_stat & HDMI_PHY_HPD))
status = connector_status_connected;
if (!(phy_stat & (HDMI_PHY_HPD | HDMI_PHY_RX_SENSE)))
--
2.54.0
^ permalink raw reply related
* [PATCH v6 11/22] drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update()
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
Move connector EDID update and CEC phys addr handling to a helper
function as a preparation before moving EDID refresh from get_modes
funcs to detect/force funcs.
Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Pass struct dw_hdmi as a parameter, to allow calls from bridge funcs,
Collect t-b tag
v5: No change
v4: Collect r-b tag
v3: New patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 27 ++++++++++++++---------
1 file changed, 17 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 0dd4c823c60a..a056e147731b 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2466,6 +2466,21 @@ static const struct drm_edid *dw_hdmi_edid_read(struct dw_hdmi *hdmi,
* DRM Connector Operations
*/
+static void
+dw_hdmi_connector_status_update(struct dw_hdmi *hdmi,
+ struct drm_connector *connector,
+ enum drm_connector_status status)
+{
+ const struct drm_edid *drm_edid;
+
+ drm_edid = dw_hdmi_edid_read(hdmi, connector);
+ drm_edid_connector_update(connector, drm_edid);
+ drm_edid_free(drm_edid);
+
+ cec_notifier_set_phys_addr(hdmi->cec_notifier,
+ connector->display_info.source_physical_address);
+}
+
static enum drm_connector_status
dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
@@ -2485,18 +2500,10 @@ static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
connector);
- const struct drm_edid *drm_edid;
- int ret;
- drm_edid = dw_hdmi_edid_read(hdmi, connector);
+ dw_hdmi_connector_status_update(hdmi, connector, connector->status);
- drm_edid_connector_update(connector, drm_edid);
- cec_notifier_set_phys_addr(hdmi->cec_notifier,
- connector->display_info.source_physical_address);
- ret = drm_edid_connector_add_modes(connector);
- drm_edid_free(drm_edid);
-
- return ret;
+ return drm_edid_connector_add_modes(connector);
}
static int dw_hdmi_connector_atomic_check(struct drm_connector *connector,
--
2.54.0
^ permalink raw reply related
* [PATCH v6 09/22] drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
Wait until the connector detect ops is called to invalidate CEC phys
addr instead of doing it directly from the irq handler.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: No change
v4: No change
v3: No change
v2: Collect r-b tag
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 5fd26ff8f55b..aae1b890167b 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2472,7 +2472,17 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
connector);
- return dw_hdmi_detect(hdmi);
+ enum drm_connector_status status;
+
+ status = dw_hdmi_detect(hdmi);
+
+ if (status == connector_status_disconnected) {
+ mutex_lock(&hdmi->cec_notifier_mutex);
+ cec_notifier_phys_addr_invalidate(hdmi->cec_notifier);
+ mutex_unlock(&hdmi->cec_notifier_mutex);
+ }
+
+ return status;
}
static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
@@ -3106,12 +3116,6 @@ static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
phy_stat & HDMI_PHY_HPD,
phy_stat & HDMI_PHY_RX_SENSE);
- if ((phy_stat & (HDMI_PHY_RX_SENSE | HDMI_PHY_HPD)) == 0) {
- mutex_lock(&hdmi->cec_notifier_mutex);
- cec_notifier_phys_addr_invalidate(hdmi->cec_notifier);
- mutex_unlock(&hdmi->cec_notifier_mutex);
- }
-
if ((intr_stat & HDMI_IH_PHY_STAT0_HPD) &&
(phy_stat & HDMI_PHY_HPD))
status = connector_status_connected;
--
2.54.0
^ permalink raw reply related
* [PATCH v6 10/22] drm: bridge: dw_hdmi: Remove cec_notifier_mutex
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
With CEC phys addr invalidation moved away from the irq handler there is
no longer a need for cec_notifier_mutex, remove it.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: No change, cec_notifier_conn_unregister() call moved
v4: No change
v3: No change
v2: Collect r-b tag
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 11 +----------
1 file changed, 1 insertion(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index aae1b890167b..0dd4c823c60a 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -189,7 +189,6 @@ struct dw_hdmi {
void (*enable_audio)(struct dw_hdmi *hdmi);
void (*disable_audio)(struct dw_hdmi *hdmi);
- struct mutex cec_notifier_mutex;
struct cec_notifier *cec_notifier;
hdmi_codec_plugged_cb plugged_cb;
@@ -2476,11 +2475,8 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
status = dw_hdmi_detect(hdmi);
- if (status == connector_status_disconnected) {
- mutex_lock(&hdmi->cec_notifier_mutex);
+ if (status == connector_status_disconnected)
cec_notifier_phys_addr_invalidate(hdmi->cec_notifier);
- mutex_unlock(&hdmi->cec_notifier_mutex);
- }
return status;
}
@@ -2542,10 +2538,8 @@ static void dw_hdmi_connector_destroy(struct drm_connector *connector)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector);
- mutex_lock(&hdmi->cec_notifier_mutex);
cec_notifier_conn_unregister(hdmi->cec_notifier);
hdmi->cec_notifier = NULL;
- mutex_unlock(&hdmi->cec_notifier_mutex);
drm_connector_cleanup(connector);
drm_bridge_put(&hdmi->bridge);
@@ -2612,9 +2606,7 @@ static int dw_hdmi_connector_create(struct dw_hdmi *hdmi)
if (!notifier)
return -ENOMEM;
- mutex_lock(&hdmi->cec_notifier_mutex);
hdmi->cec_notifier = notifier;
- mutex_unlock(&hdmi->cec_notifier_mutex);
return 0;
}
@@ -3323,7 +3315,6 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
mutex_init(&hdmi->mutex);
mutex_init(&hdmi->audio_mutex);
- mutex_init(&hdmi->cec_notifier_mutex);
spin_lock_init(&hdmi->audio_lock);
ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0);
--
2.54.0
^ permalink raw reply related
* [PATCH v6 08/22] drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
The CEC notifier is being unregistered when the bridge detach,
something that happens earlier than normal connector cleanup.
Change to unregister the CEC notifier at connector cleanup, in the
connector .destroy() func, to align the lifetime of the connector and
the CEC notifier and closer match a drmres handled generic CEC notifier.
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: New patch
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 16 +++++-----------
1 file changed, 5 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index cbbd15578042..5fd26ff8f55b 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2532,6 +2532,11 @@ static void dw_hdmi_connector_destroy(struct drm_connector *connector)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector);
+ mutex_lock(&hdmi->cec_notifier_mutex);
+ cec_notifier_conn_unregister(hdmi->cec_notifier);
+ hdmi->cec_notifier = NULL;
+ mutex_unlock(&hdmi->cec_notifier_mutex);
+
drm_connector_cleanup(connector);
drm_bridge_put(&hdmi->bridge);
}
@@ -2909,16 +2914,6 @@ static int dw_hdmi_bridge_attach(struct drm_bridge *bridge,
return dw_hdmi_connector_create(hdmi);
}
-static void dw_hdmi_bridge_detach(struct drm_bridge *bridge)
-{
- struct dw_hdmi *hdmi = bridge->driver_private;
-
- mutex_lock(&hdmi->cec_notifier_mutex);
- cec_notifier_conn_unregister(hdmi->cec_notifier);
- hdmi->cec_notifier = NULL;
- mutex_unlock(&hdmi->cec_notifier_mutex);
-}
-
static enum drm_mode_status
dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_info *info,
@@ -2996,7 +2991,6 @@ static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
.attach = dw_hdmi_bridge_attach,
- .detach = dw_hdmi_bridge_detach,
.atomic_check = dw_hdmi_bridge_atomic_check,
.atomic_get_output_bus_fmts = dw_hdmi_bridge_atomic_get_output_bus_fmts,
.atomic_get_input_bus_fmts = dw_hdmi_bridge_atomic_get_input_bus_fmts,
--
2.54.0
^ permalink raw reply related
* [PATCH v6 05/22] drm: bridge: dw_hdmi: Fold poweron and setup functions
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
Fold the poweron and setup functions into one function and use the
adjusted_mode directly from the new crtc_state to remove the need of
storing previous_mode.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: No change, rebase on next-20260508
v4: No change
v3: Collect r-b tag
v2: No change
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 21 ++++++++-------------
1 file changed, 8 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index aa12397b3343..607faf4da967 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2254,9 +2254,9 @@ static void hdmi_disable_overflow_interrupts(struct dw_hdmi *hdmi)
HDMI_IH_MUTE_FC_STAT2);
}
-static int dw_hdmi_setup(struct dw_hdmi *hdmi,
- const struct drm_connector *connector,
- const struct drm_display_mode *mode)
+static int dw_hdmi_poweron(struct dw_hdmi *hdmi,
+ const struct drm_connector *connector,
+ const struct drm_display_mode *mode)
{
const struct drm_display_info *display = &connector->display_info;
int ret;
@@ -2396,15 +2396,6 @@ static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi)
hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE);
}
-static void dw_hdmi_poweron(struct dw_hdmi *hdmi)
-{
- /*
- * The curr_conn field is guaranteed to be valid here, as this function
- * is only be called when !hdmi->disabled.
- */
- dw_hdmi_setup(hdmi, hdmi->curr_conn, &hdmi->previous_mode);
-}
-
static void dw_hdmi_poweroff(struct dw_hdmi *hdmi)
{
if (hdmi->phy.enabled) {
@@ -2969,15 +2960,19 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
struct dw_hdmi *hdmi = bridge->driver_private;
+ const struct drm_display_mode *mode;
struct drm_connector *connector;
+ struct drm_crtc *crtc;
connector = drm_atomic_get_new_connector_for_encoder(state,
bridge->encoder);
+ crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
+ mode = &drm_atomic_get_new_crtc_state(state, crtc)->adjusted_mode;
mutex_lock(&hdmi->mutex);
hdmi->disabled = false;
hdmi->curr_conn = connector;
- dw_hdmi_poweron(hdmi);
+ dw_hdmi_poweron(hdmi, connector, mode);
dw_hdmi_update_phy_mask(hdmi);
handle_plugged_change(hdmi, true);
mutex_unlock(&hdmi->mutex);
--
2.54.0
^ permalink raw reply related
* [PATCH v6 04/22] drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode
From: Jonas Karlman @ 2026-05-16 18:38 UTC (permalink / raw)
To: Andrzej Hajda, Neil Armstrong, Robert Foss, Heiko Stuebner,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, Luca Ceresoli,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Liu Ying, Sandy Huang, Andy Yan, Chen-Yu Tsai, Christian Hewitt,
Diederik de Haas, Nicolas Frattaroli, Dmitry Baryshkov, dri-devel,
linux-arm-kernel, linux-rockchip, linux-amlogic, linux-sunxi, imx,
linux-kernel
In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se>
Use the passed mode instead of mixing use of passed mode and the stored
previous_mode in dw_hdmi_setup(). The passed mode is currenly always the
previous_mode.
Also fix a small typo and add a variable to help shorten a code line.
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # Rock64, RockPro64, Quartz64-B
Signed-off-by: Jonas Karlman <jonas@kwiboo.se>
---
v6: Collect t-b tag
v5: No change
v4: No change
v3: Collect r-b tag
v2: Update commit message, s/type/typo/
---
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 8f7949d2c7f2..aa12397b3343 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -2258,6 +2258,7 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi,
const struct drm_connector *connector,
const struct drm_display_mode *mode)
{
+ const struct drm_display_info *display = &connector->display_info;
int ret;
hdmi_disable_overflow_interrupts(hdmi);
@@ -2303,12 +2304,10 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi,
hdmi->hdmi_data.video_mode.mdataenablepolarity = true;
/* HDMI Initialization Step B.1 */
- hdmi_av_composer(hdmi, &connector->display_info, mode);
+ hdmi_av_composer(hdmi, display, mode);
- /* HDMI Initializateion Step B.2 */
- ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data,
- &connector->display_info,
- &hdmi->previous_mode);
+ /* HDMI Initialization Step B.2 */
+ ret = hdmi->phy.ops->init(hdmi, hdmi->phy.data, display, mode);
if (ret)
return ret;
hdmi->phy.enabled = true;
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox