From david.laight.linux at gmail.com Fri May 1 01:30:50 2026 From: david.laight.linux at gmail.com (David Laight) Date: Fri, 1 May 2026 09:30:50 +0100 Subject: [PATCH 00/10] spi: Use FIELD_MODIFY() for bitfield operations In-Reply-To: <20260430155456.36998-1-18255117159@163.com> References: <20260430155456.36998-1-18255117159@163.com> Message-ID: <20260501093050.3f97cd3e@pumpkin> On Thu, 30 Apr 2026 23:54:46 +0800 Hans Zhang <18255117159 at 163.com> wrote: > Replace open-coded bitfield modifications with the standard FIELD_MODIFY() > macro across multiple SPI controller drivers. This improves readability and > adds compile-time checking without functional changes. I don't think these changes are worth the effort. The readability doesn't change much - you need to know what a slightly more obscure 'helper' does. The extra compile-time checks are pretty unlikely to ever find a problem and mostly just slow down the compile. The generated code is likely be slightly worse. And, with the best will in the world, it is easy to make silly mistakes. David > > Each patch modifies a single driver, allowing independent review and > application. > > Hans Zhang (10): > spi: amlogic-spifc-a1: Use FIELD_MODIFY() > spi: amlogic-spisg: Use FIELD_MODIFY() > spi: cadence-xspi: Use FIELD_MODIFY() > spi: meson-spicc: Use FIELD_MODIFY() > spi: nxp-xspi: Use FIELD_MODIFY() > spi: sn-f-ospi: Use FIELD_MODIFY() > spi: stm32-ospi: Use FIELD_MODIFY() > spi: stm32-qspi: Use FIELD_MODIFY() > spi: sunplus-sp7021: Use FIELD_MODIFY() > spi: uniphier: Use FIELD_MODIFY() > > drivers/spi/spi-amlogic-spifc-a1.c | 5 ++--- > drivers/spi/spi-amlogic-spisg.c | 13 +++++-------- > drivers/spi/spi-cadence-xspi.c | 3 +-- > drivers/spi/spi-meson-spicc.c | 5 ++--- > drivers/spi/spi-nxp-xspi.c | 12 ++++-------- > drivers/spi/spi-sn-f-ospi.c | 5 ++--- > drivers/spi/spi-stm32-ospi.c | 7 +++---- > drivers/spi/spi-stm32-qspi.c | 5 ++--- > drivers/spi/spi-sunplus-sp7021.c | 3 +-- > drivers/spi/spi-uniphier.c | 13 +++++-------- > 10 files changed, 27 insertions(+), 44 deletions(-) > > > base-commit: 3b3bea6d4b9c162f9e555905d96b8c1da67ecd5b From wsa+renesas at sang-engineering.com Mon May 4 01:14:48 2026 From: wsa+renesas at sang-engineering.com (Wolfram Sang) Date: Mon, 4 May 2026 10:14:48 +0200 Subject: [PATCH v5 2/8] dt-bindings: i2c: amlogic: Add compatible for T7 SOC In-Reply-To: <20260424-add-mcu-fan-khadas-vim4-v5-2-afcfa7157b23@aliel.fr> References: <20260424-add-mcu-fan-khadas-vim4-v5-0-afcfa7157b23@aliel.fr> <20260424-add-mcu-fan-khadas-vim4-v5-2-afcfa7157b23@aliel.fr> Message-ID: On Fri, Apr 24, 2026 at 04:17:33PM +0200, Ronald Claveau via B4 Relay wrote: > From: Ronald Claveau > > Add the T7 SOC compatible which fallback to AXG compatible. > > Acked-by: Rob Herring (Arm) > Signed-off-by: Ronald Claveau Applied to for-current, thanks! From hverkuil+cisco at kernel.org Mon May 4 01:22:30 2026 From: hverkuil+cisco at kernel.org (Hans Verkuil) Date: Mon, 4 May 2026 10:22:30 +0200 Subject: [PATCH] staging: media: meson: fix typo in codec files In-Reply-To: <20260429151858.28761-1-mahamaryamjavaid@gmail.com> References: <20260429151858.28761-1-mahamaryamjavaid@gmail.com> Message-ID: Hi Maha, On 29/04/2026 17:18, Maha Maryam Javaid wrote: > Fix spelling mistake: substracted -> subtracted Please combine this patch and the other meson typo patch in a single patch. There is no point in splitting them up. Regards, Hans > > Signed-off-by: Maha Maryam Javaid > --- > drivers/staging/media/meson/vdec/codec_mpeg12.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/drivers/staging/media/meson/vdec/codec_mpeg12.c b/drivers/staging/media/meson/vdec/codec_mpeg12.c > index 76e9ca7191ab..ab4374e3b2ef 100644 > --- a/drivers/staging/media/meson/vdec/codec_mpeg12.c > +++ b/drivers/staging/media/meson/vdec/codec_mpeg12.c > @@ -12,7 +12,7 @@ > #include "vdec_helpers.h" > > #define SIZE_WORKSPACE SZ_128K > -/* Offset substracted by the firmware from the workspace paddr */ > +/* Offset subtracted by the firmware from the workspace paddr */ > #define WORKSPACE_OFFSET (5 * SZ_1K) > > /* map firmware registers to known MPEG1/2 functions */ From mahamaryamjavaid at gmail.com Mon May 4 04:12:19 2026 From: mahamaryamjavaid at gmail.com (Maha Maryam Javaid) Date: Mon, 4 May 2026 07:12:19 -0400 Subject: [PATCH] staging: media: meson: fix typo in codec files Message-ID: <20260504111219.7797-1-mahamaryamjavaid@gmail.com> Fix spelling mistake: substracted -> subtracted Signed-off-by: Maha Maryam Javaid --- drivers/staging/media/meson/vdec/codec_h264.c | 2 +- drivers/staging/media/meson/vdec/codec_mpeg12.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/staging/media/meson/vdec/codec_h264.c b/drivers/staging/media/meson/vdec/codec_h264.c index 89e0f8624e5b..a6074de15118 100644 --- a/drivers/staging/media/meson/vdec/codec_h264.c +++ b/drivers/staging/media/meson/vdec/codec_h264.c @@ -16,7 +16,7 @@ #define SIZE_SEI (8 * SZ_1K) /* - * Offset added by the firmware which must be substracted + * Offset added by the firmware which must be subtracted * from the workspace phyaddr */ #define WORKSPACE_BUF_OFFSET 0x1000000 diff --git a/drivers/staging/media/meson/vdec/codec_mpeg12.c b/drivers/staging/media/meson/vdec/codec_mpeg12.c index 76e9ca7191ab..ab4374e3b2ef 100644 --- a/drivers/staging/media/meson/vdec/codec_mpeg12.c +++ b/drivers/staging/media/meson/vdec/codec_mpeg12.c @@ -12,7 +12,7 @@ #include "vdec_helpers.h" #define SIZE_WORKSPACE SZ_128K -/* Offset substracted by the firmware from the workspace paddr */ +/* Offset subtracted by the firmware from the workspace paddr */ #define WORKSPACE_OFFSET (5 * SZ_1K) /* map firmware registers to known MPEG1/2 functions */ -- 2.34.1 From gregkh at linuxfoundation.org Mon May 4 04:59:56 2026 From: gregkh at linuxfoundation.org (Greg KH) Date: Mon, 4 May 2026 13:59:56 +0200 Subject: [PATCH] staging: media: meson: fix typo in codec files In-Reply-To: <20260504111219.7797-1-mahamaryamjavaid@gmail.com> References: <20260504111219.7797-1-mahamaryamjavaid@gmail.com> Message-ID: <2026050442-rut-zipping-5de8@gregkh> On Mon, May 04, 2026 at 07:12:19AM -0400, Maha Maryam Javaid wrote: > Fix spelling mistake: substracted -> subtracted > > Signed-off-by: Maha Maryam Javaid > --- > drivers/staging/media/meson/vdec/codec_h264.c | 2 +- > drivers/staging/media/meson/vdec/codec_mpeg12.c | 2 +- > 2 files changed, 2 insertions(+), 2 deletions(-) > > diff --git a/drivers/staging/media/meson/vdec/codec_h264.c b/drivers/staging/media/meson/vdec/codec_h264.c > index 89e0f8624e5b..a6074de15118 100644 > --- a/drivers/staging/media/meson/vdec/codec_h264.c > +++ b/drivers/staging/media/meson/vdec/codec_h264.c > @@ -16,7 +16,7 @@ > #define SIZE_SEI (8 * SZ_1K) > > /* > - * Offset added by the firmware which must be substracted > + * Offset added by the firmware which must be subtracted > * from the workspace phyaddr > */ > #define WORKSPACE_BUF_OFFSET 0x1000000 > diff --git a/drivers/staging/media/meson/vdec/codec_mpeg12.c b/drivers/staging/media/meson/vdec/codec_mpeg12.c > index 76e9ca7191ab..ab4374e3b2ef 100644 > --- a/drivers/staging/media/meson/vdec/codec_mpeg12.c > +++ b/drivers/staging/media/meson/vdec/codec_mpeg12.c > @@ -12,7 +12,7 @@ > #include "vdec_helpers.h" > > #define SIZE_WORKSPACE SZ_128K > -/* Offset substracted by the firmware from the workspace paddr */ > +/* Offset subtracted by the firmware from the workspace paddr */ > #define WORKSPACE_OFFSET (5 * SZ_1K) > > /* map firmware registers to known MPEG1/2 functions */ > -- > 2.34.1 > > Hi, This is the friendly patch-bot of Greg Kroah-Hartman. You have sent him a patch that has triggered this response. He used to manually respond to these common problems, but in order to save his sanity (he kept writing the same thing over and over, yet to different people), I was created. Hopefully you will not take offence and will fix the problem in your patch and resubmit it so that it can be accepted into the Linux kernel tree. You are receiving this message because of the following common error(s) as indicated below: - This looks like a new version of a previously submitted patch, but you did not list below the --- line any changes from the previous version. Please read the section entitled "The canonical patch format" in the kernel file, Documentation/process/submitting-patches.rst for what needs to be done here to properly describe this. If you wish to discuss this problem further, or you have questions about how to resolve this issue, please feel free to respond to this email and Greg will reply once he has dug out from the pending patches received from other developers. thanks, greg k-h's patch email bot From jonas at kwiboo.se Mon May 4 12:10:41 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:41 +0000 Subject: [PATCH v4 04/17] drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-5-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 Signed-off-by: Jonas Karlman --- 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 4ecad0d2eeaa..f028a4b71aad 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 From jonas at kwiboo.se Mon May 4 12:10:37 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:37 +0000 Subject: [PATCH v4 00/17] drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup Message-ID: <20260504191059.275928-1-jonas@kwiboo.se> This is a revival of an old dw-hdmi series and is the first series part of a new effort to upstream old LibreELEC HDMI 2.0 patches for Rockchip RK33xx devices. This series ensure poweron/poweroff and CEC phys addr invalidation is happening under drm mode_config mutex lock, and also ensure EDID is updated after a HPD low voltage pulse by changing to debounce hotplug processing. These changes have mainly been tested on Rockchip RK3328, RK3399 and RK3568 devices. Rockchip uses the dw-hdmi connector, so this could use some more testing with drivers that use the bridge connector. Testing with a Rock Pi 4 (RK3399) using a Reaspberry Pi Monitor with Linux kms client console using drm.debug=0xe should log something like following: Power cycle monitor using the power button: [CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a [CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz [CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156 [CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 [CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1 [CONNECTOR:68:HDMI-A-1] Same epoch counter 10 Cable unplugged: [CONNECTOR:68:HDMI-A-1] EDID changed, epoch counter 11 [CONNECTOR:68:HDMI-A-1] status updated from connected to disconnected [CONNECTOR:68:HDMI-A-1] Changed epoch counter 10 => 12 [CONNECTOR:68:HDMI-A-1] generating connector hotplug event [CONNECTOR:68:HDMI-A-1] Sent hotplug event Cable connected: [CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a [CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz [CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156 [CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 [CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1 [CONNECTOR:68:HDMI-A-1] status updated from disconnected to connected [CONNECTOR:68:HDMI-A-1] Changed epoch counter 12 => 13 [CONNECTOR:68:HDMI-A-1] generating connector hotplug event [CONNECTOR:68:HDMI-A-1] Sent hotplug event This series has evolved into an initial part of a larger multi series effort to: - drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup - drm/bridge: dw-hdmi: Improve input/output bus format handling - drm/bridge: dw-hdmi: Convert to a HDMI bridge and use of bridge connector - drm/bridge: dw-hdmi: Add and use tmds_char_rate_valid() plat data ops - drm/meson: hdmi: Misc cleanup and use CEC notifier helpers - phy: rockchip: inno-hdmi: Change TMDS rate handling to configure() ops - drm/rockchip: dw_hdmi: Misc cleanup and propagate bus format - drm/rockchip: dw_hdmi: Enable YCbCr and Deep Color modes Link to snapshot: https://github.com/Kwiboo/linux-rockchip/commits/next-20260430-rk-hdmi-v2/ Changes in v4: - Change to use generic CEC notifier helpers - Disable/mask hpd_work until enable_hpd()/hpd_enable() - Read connector status directly from HW regs in hpd_work - Continued rework of HDP and RXSENSE interrupt handling - Collect r-b tags - Rebased on next-20260430 Link to v3: https://lore.kernel.org/r/ Changes in v3: - Rework EDID refresh handling to closer match bridge connector - Use delayed work to debounce HPD processing - Update commit messages - Collect r-b tags - Rebased on next-20260401 Link to v2: https://lore.kernel.org/r/20240908132823.3308029-1-jonas at kwiboo.se/ Changes in v2: - Add patch to disable scrambler feature when not supported - Add patch to only notify connected status on HPD interrupt - Update commit messages - Collect r-b tags - Rebased on next-20240906 Link to v1: https://lore.kernel.org/r/20240611155108.1436502-1-jonas at kwiboo.se/ Jonas Karlman (17): drm: bridge: dw_hdmi: Disable scrambler feature when not supported drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode drm: bridge: dw_hdmi: Fold poweron and setup functions drm: bridge: dw_hdmi: Remove previous_mode and mode_set drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect drm: bridge: dw_hdmi: Remove cec_notifier_mutex drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update() drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio drm: bridge: dw_hdmi: Use generic CEC notifier helpers drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense() drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd() drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 1 - drivers/gpu/drm/bridge/synopsys/Kconfig | 1 + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 436 ++++++-------------- drivers/gpu/drm/meson/meson_dw_hdmi.c | 3 - drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 2 - drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 2 - include/drm/bridge/dw_hdmi.h | 6 - 7 files changed, 134 insertions(+), 317 deletions(-) -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:38 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:38 +0000 Subject: [PATCH v4 01/17] drm: bridge: dw_hdmi: Disable scrambler feature when not supported In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-2-jonas@kwiboo.se> The scrambler feature can be left enabled when hotplugging from a sink and mode that require scrambling to a sink that does not support SCDC or scrambling. Typically a blank screen or 'no signal' message can be observed after using a HDMI 2.0 4K at 60Hz mode and then hotplugging to a sink that only support HDMI 1.4. Fix this by disabling the scrambler feature when SCDC is not supported. Fixes: 264fce6cc2c1 ("drm/bridge: dw-hdmi: Add SCDC and TMDS Scrambling support") Reported-by: Christopher Obbard Reviewed-by: Neil Armstrong Signed-off-by: Jonas Karlman --- v4: No change v3: Collect r-b tag v2: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index f4a1ebb79716..248454c45d6b 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2135,6 +2135,8 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, HDMI_MC_SWRSTZ); drm_scdc_set_scrambling(hdmi->curr_conn, 0); } + } else if (hdmi->version >= 0x200a) { + hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL); } /* Set up horizontal active pixel width */ -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:39 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:39 +0000 Subject: [PATCH v4 02/17] drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-3-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 Signed-off-by: Jonas Karlman --- 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 248454c45d6b..0647ec6632d8 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 From jonas at kwiboo.se Mon May 4 12:10:40 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:40 +0000 Subject: [PATCH v4 03/17] drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-4-jonas@kwiboo.se> Change to only call poweron/poweroff from atomic_enable/atomic_disable funcs instead of trying to be clever by keeping a bridge_is_on state and poweron/off in the hotplug irq handler. The bridge is already enabled/disabled depending on connection state with the call to drm_helper_hpd_irq_event() in hotplug irq handler. A benefit of this is that drm mode_config mutex is always held at poweron/off, something that may reduce the need for the dw-hdmi mutex. Reviewed-by: Neil Armstrong Signed-off-by: Jonas Karlman --- v4: No change v3: Collect r-b tag v2: Update commit message --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 33 ++--------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 0647ec6632d8..4ecad0d2eeaa 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -171,7 +171,6 @@ struct dw_hdmi { 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 bridge_is_on; /* indicates the bridge is on */ bool rxsense; /* rxsense state */ u8 phy_mask; /* desired phy int mask settings */ u8 mc_clkdis; /* clock disable register */ @@ -2400,8 +2399,6 @@ static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi) static void dw_hdmi_poweron(struct dw_hdmi *hdmi) { - hdmi->bridge_is_on = true; - /* * The curr_conn field is guaranteed to be valid here, as this function * is only be called when !hdmi->disabled. @@ -2415,30 +2412,6 @@ static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) hdmi->phy.ops->disable(hdmi, hdmi->phy.data); hdmi->phy.enabled = false; } - - hdmi->bridge_is_on = false; -} - -static void dw_hdmi_update_power(struct dw_hdmi *hdmi) -{ - int force = hdmi->force; - - if (hdmi->disabled) { - force = DRM_FORCE_OFF; - } else if (force == DRM_FORCE_UNSPECIFIED) { - if (hdmi->rxsense) - force = DRM_FORCE_ON; - else - force = DRM_FORCE_OFF; - } - - if (force == DRM_FORCE_OFF) { - if (hdmi->bridge_is_on) - dw_hdmi_poweroff(hdmi); - } else { - if (!hdmi->bridge_is_on) - dw_hdmi_poweron(hdmi); - } } /* @@ -2563,7 +2536,6 @@ static void dw_hdmi_connector_force(struct drm_connector *connector) mutex_lock(&hdmi->mutex); hdmi->force = connector->force; - dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); mutex_unlock(&hdmi->mutex); } @@ -2988,7 +2960,7 @@ static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, mutex_lock(&hdmi->mutex); hdmi->disabled = true; hdmi->curr_conn = NULL; - dw_hdmi_update_power(hdmi); + dw_hdmi_poweroff(hdmi); dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, false); mutex_unlock(&hdmi->mutex); @@ -3006,7 +2978,7 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, mutex_lock(&hdmi->mutex); hdmi->disabled = false; hdmi->curr_conn = connector; - dw_hdmi_update_power(hdmi); + dw_hdmi_poweron(hdmi); dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, true); mutex_unlock(&hdmi->mutex); @@ -3106,7 +3078,6 @@ void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense) if (hpd) hdmi->rxsense = true; - dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); } mutex_unlock(&hdmi->mutex); -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:44 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:44 +0000 Subject: [PATCH v4 07/17] drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-8-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 Signed-off-by: Jonas Karlman --- 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 a287bf56bd9f..059f1d241fef 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) @@ -3099,12 +3109,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 From jonas at kwiboo.se Mon May 4 12:10:43 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:43 +0000 Subject: [PATCH v4 06/17] drm: bridge: dw_hdmi: Remove previous_mode and mode_set In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-7-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 Signed-off-by: Jonas Karlman --- 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 7211beffd59e..a287bf56bd9f 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_state *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 From jonas at kwiboo.se Mon May 4 12:10:42 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:42 +0000 Subject: [PATCH v4 05/17] drm: bridge: dw_hdmi: Fold poweron and setup functions In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-6-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 Signed-off-by: Jonas Karlman --- 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 f028a4b71aad..7211beffd59e 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_state *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 From jonas at kwiboo.se Mon May 4 12:10:45 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:45 +0000 Subject: [PATCH v4 08/17] drm: bridge: dw_hdmi: Remove cec_notifier_mutex In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-9-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 Signed-off-by: Jonas Karlman --- 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 059f1d241fef..fc4f255c2a2f 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; } @@ -2594,9 +2590,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; } @@ -2910,10 +2904,8 @@ 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 @@ -3316,7 +3308,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 From jonas at kwiboo.se Mon May 4 12:10:47 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:47 +0000 Subject: [PATCH v4 10/17] drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-11-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 Signed-off-by: Jonas Karlman --- 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 | 25 ++++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 0b56ad7316e3..47616c11fcd7 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2473,33 +2473,36 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); 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(connector, status); return status; } static int dw_hdmi_connector_get_modes(struct drm_connector *connector) { - dw_hdmi_connector_status_update(connector, connector->status); - return drm_edid_connector_add_modes(connector); } @@ -2529,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(connector, connector->status); } static const struct drm_connector_funcs dw_hdmi_connector_funcs = { -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:46 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:46 +0000 Subject: [PATCH v4 09/17] drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update() In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-10-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 Signed-off-by: Jonas Karlman --- v4: Collect r-b tag v3: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 30 +++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index fc4f255c2a2f..0b56ad7316e3 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 drm_connector *connector, + enum drm_connector_status status) +{ + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); + 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) { @@ -2483,20 +2498,9 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force) 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; + dw_hdmi_connector_status_update(connector, connector->status); - drm_edid = dw_hdmi_edid_read(hdmi, connector); - - 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 From jonas at kwiboo.se Mon May 4 12:10:48 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:48 +0000 Subject: [PATCH v4 11/17] drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-12-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 Reviewed-by: Nicolas Frattaroli Signed-off-by: Jonas Karlman --- 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 47616c11fcd7..4c712aa07a89 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -46,8 +46,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 @@ -147,8 +145,6 @@ struct dw_hdmi { int vic; - u8 edid[HDMI_EDID_LEN]; - struct { const struct dw_hdmi_phy_ops *ops; const char *name; @@ -158,8 +154,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; @@ -2056,7 +2050,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; @@ -2292,7 +2286,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; @@ -2312,7 +2306,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 */ @@ -2321,7 +2315,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 */ @@ -2435,29 +2429,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 From jonas at kwiboo.se Mon May 4 12:10:49 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:49 +0000 Subject: [PATCH v4 12/17] drm: bridge: dw_hdmi: Use generic CEC notifier helpers In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-13-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. Signed-off-by: Jonas Karlman --- v4: New patch --- drivers/gpu/drm/bridge/synopsys/Kconfig | 1 + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 32 ++++------------------- 2 files changed, 6 insertions(+), 27 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 4c712aa07a89..0aa29b92327e 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -23,12 +23,11 @@ #include #include -#include - #include #include #include +#include #include #include #include @@ -183,8 +182,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; @@ -2453,7 +2450,7 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, 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; } @@ -2462,8 +2459,7 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, 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 @@ -2539,8 +2535,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; if (hdmi->version >= 0x200a) connector->ycbcr_420_allowed = @@ -2571,15 +2565,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); } /* ----------------------------------------------------------------------------- @@ -2887,14 +2874,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; - - cec_notifier_conn_unregister(hdmi->cec_notifier); - hdmi->cec_notifier = NULL; -} - static enum drm_mode_status dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge, const struct drm_display_info *info, @@ -2972,7 +2951,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 From jonas at kwiboo.se Mon May 4 12:10:50 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:50 +0000 Subject: [PATCH v4 13/17] drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-14-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 better handle a HPD low voltage level pulse when a sink changes the EDID. The delayed work is only active 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 --- 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 | 59 +++++++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 0aa29b92327e..193bdba65758 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(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 const struct drm_connector_funcs dw_hdmi_connector_funcs = { .fill_modes = drm_helper_probe_single_connector_modes, .detect = dw_hdmi_connector_detect, @@ -2530,6 +2547,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) @@ -2946,6 +2965,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, @@ -2959,6 +2992,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, }; /* ----------------------------------------------------------------------------- @@ -3079,10 +3114,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); - drm_bridge_hpd_notify(&hdmi->bridge, status); - } + mod_delayed_work(system_percpu_wq, &hdmi->hpd_work, + msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); } hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); @@ -3092,6 +3125,19 @@ 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); + enum drm_connector_status status; + + if (WARN_ON(!hdmi->bridge.dev)) + return; + + drm_helper_hpd_irq_event(hdmi->bridge.dev); + status = dw_hdmi_phy_read_hpd(hdmi, hdmi->phy.data); + drm_bridge_hpd_notify(&hdmi->bridge, status); +} + static const struct dw_hdmi_phy_data dw_hdmi_phys[] = { { .type = DW_HDMI_PHY_DWC_HDMI_TX_PHY, @@ -3376,6 +3422,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); @@ -3508,6 +3557,8 @@ EXPORT_SYMBOL_GPL(dw_hdmi_probe); void dw_hdmi_remove(struct dw_hdmi *hdmi) { + disable_delayed_work_sync(&hdmi->hpd_work); + drm_bridge_remove(&hdmi->bridge); if (hdmi->audio && !IS_ERR(hdmi->audio)) -- 2.54.0 From jonas at kwiboo.se Mon May 4 12:10:51 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:51 +0000 Subject: [PATCH v4 14/17] drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-15-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. Remove RXSENSE handling to simplify the HPD interrupt handling and instead depend on the debounced HPD work to refresh EDID. 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. Signed-off-by: Jonas Karlman --- v4: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 140 +++------------------- 1 file changed, 16 insertions(+), 124 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 193bdba65758..e3ebb0dfdaf2 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(connector, connector->status); @@ -2919,10 +2874,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); } @@ -2941,10 +2894,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); } @@ -3038,78 +2989,23 @@ 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; + u8 intr_stat; 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); + if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { + enum drm_connector_status status; - /* - * 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. - */ - 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); - - 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))) - status = connector_status_disconnected; - } + /* 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 (status != connector_status_unknown) { dev_dbg(hdmi->dev, "EVENT=%s\n", status == connector_status_connected ? "plugin" : "plugout"); @@ -3119,8 +3015,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; } @@ -3311,9 +3206,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 From jonas at kwiboo.se Mon May 4 12:10:52 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:52 +0000 Subject: [PATCH v4 15/17] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense() In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-16-jonas@kwiboo.se> The dw_hdmi_setup_rx_sense() helper is empty and no longer needed after recent RXSENSE and HPD rework, remove it. Signed-off-by: Jonas Karlman --- 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 e3ebb0dfdaf2..60b887249eba 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2987,11 +2987,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 From jonas at kwiboo.se Mon May 4 12:10:54 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:54 +0000 Subject: [PATCH v4 17/17] drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-18-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. Signed-off-by: Jonas Karlman --- v4: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 32 +++++++---------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index c0ed067154c7..a42c45ff0ade 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2971,24 +2971,12 @@ 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); + intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0) & HDMI_IH_PHY_STAT0_HPD; 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; - - intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); - if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { 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 ? @@ -3000,12 +2988,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); + 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; + } - return IRQ_HANDLED; + return ret; } static void dw_hdmi_hpd_work(struct work_struct *work) @@ -3305,9 +3294,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 From jonas at kwiboo.se Mon May 4 12:10:53 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 4 May 2026 19:10:53 +0000 Subject: [PATCH v4 16/17] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd() In-Reply-To: <20260504191059.275928-1-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> Message-ID: <20260504191059.275928-17-jonas@kwiboo.se> The dw_hdmi_phy_update_hpd() helper is empty and no longer needed after recent RXSENSE and HPD rework, remove it. Signed-off-by: Jonas Karlman --- 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 60b887249eba..c0ed067154c7 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 From neil.armstrong at linaro.org Mon May 4 14:21:55 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Mon, 4 May 2026 23:21:55 +0200 Subject: [PATCH v4 12/17] drm: bridge: dw_hdmi: Use generic CEC notifier helpers In-Reply-To: <20260504191059.275928-13-jonas@kwiboo.se> References: <20260504191059.275928-1-jonas@kwiboo.se> <20260504191059.275928-13-jonas@kwiboo.se> Message-ID: <80fb7a31-3a85-48d5-a971-4e7632778f0c@linaro.org> On 5/4/26 21:10, Jonas Karlman wrote: > 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. > > Signed-off-by: Jonas Karlman > --- > v4: New patch > --- > drivers/gpu/drm/bridge/synopsys/Kconfig | 1 + > drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 32 ++++------------------- > 2 files changed, 6 insertions(+), 27 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 4c712aa07a89..0aa29b92327e 100644 > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > @@ -23,12 +23,11 @@ > #include > #include > > -#include > - > #include > #include > > #include > +#include > #include > #include > #include > @@ -183,8 +182,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; > @@ -2453,7 +2450,7 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, > > 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; > } > > @@ -2462,8 +2459,7 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, > 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 > @@ -2539,8 +2535,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; > > if (hdmi->version >= 0x200a) > connector->ycbcr_420_allowed = > @@ -2571,15 +2565,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); > } > > /* ----------------------------------------------------------------------------- > @@ -2887,14 +2874,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; > - > - cec_notifier_conn_unregister(hdmi->cec_notifier); > - hdmi->cec_notifier = NULL; > -} > - > static enum drm_mode_status > dw_hdmi_bridge_mode_valid(struct drm_bridge *bridge, > const struct drm_display_info *info, > @@ -2972,7 +2951,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, Reviewed-by: Neil Armstrong Thanks, Neil From 18255117159 at 163.com Mon May 4 21:23:45 2026 From: 18255117159 at 163.com (Hans Zhang) Date: Tue, 5 May 2026 12:23:45 +0800 Subject: [PATCH 00/10] spi: Use FIELD_MODIFY() for bitfield operations In-Reply-To: <20260501093050.3f97cd3e@pumpkin> References: <20260430155456.36998-1-18255117159@163.com> <20260501093050.3f97cd3e@pumpkin> Message-ID: <9be240fa-ab46-4b58-9240-5e96cab8a097@163.com> On 5/1/26 16:30, David Laight wrote: > On Thu, 30 Apr 2026 23:54:46 +0800 > Hans Zhang <18255117159 at 163.com> wrote: > >> Replace open-coded bitfield modifications with the standard FIELD_MODIFY() >> macro across multiple SPI controller drivers. This improves readability and >> adds compile-time checking without functional changes. > > I don't think these changes are worth the effort. > The readability doesn't change much - you need to know what a slightly > more obscure 'helper' does. > The extra compile-time checks are pretty unlikely to ever find a problem > and mostly just slow down the compile. > The generated code is likely be slightly worse. > And, with the best will in the world, it is easy to make silly mistakes. > > David Hi David, FIELD_MODIFY() is a standard kernel helper (bitfield.h), not an obscure one. My recent power domain series using similar patterns was accepted: https://patchwork.kernel.org/project/linux-pm/cover/20260430163213.44695-1-18255117159 at 163.com/ The PCIe maintainer also values this kind of code simplification, which encouraged me to send these SPI patches. The macro offers compile-time overflow checks, and I've verified the generated assembly is identical (GCC/Clang). I believe the trade?off favours readability and safety. If you still prefer to keep the open?coded versions, I'll drop the series. Please let me know. Best regards, Hans > >> >> Each patch modifies a single driver, allowing independent review and >> application. >> >> Hans Zhang (10): >> spi: amlogic-spifc-a1: Use FIELD_MODIFY() >> spi: amlogic-spisg: Use FIELD_MODIFY() >> spi: cadence-xspi: Use FIELD_MODIFY() >> spi: meson-spicc: Use FIELD_MODIFY() >> spi: nxp-xspi: Use FIELD_MODIFY() >> spi: sn-f-ospi: Use FIELD_MODIFY() >> spi: stm32-ospi: Use FIELD_MODIFY() >> spi: stm32-qspi: Use FIELD_MODIFY() >> spi: sunplus-sp7021: Use FIELD_MODIFY() >> spi: uniphier: Use FIELD_MODIFY() >> >> drivers/spi/spi-amlogic-spifc-a1.c | 5 ++--- >> drivers/spi/spi-amlogic-spisg.c | 13 +++++-------- >> drivers/spi/spi-cadence-xspi.c | 3 +-- >> drivers/spi/spi-meson-spicc.c | 5 ++--- >> drivers/spi/spi-nxp-xspi.c | 12 ++++-------- >> drivers/spi/spi-sn-f-ospi.c | 5 ++--- >> drivers/spi/spi-stm32-ospi.c | 7 +++---- >> drivers/spi/spi-stm32-qspi.c | 5 ++--- >> drivers/spi/spi-sunplus-sp7021.c | 3 +-- >> drivers/spi/spi-uniphier.c | 13 +++++-------- >> 10 files changed, 27 insertions(+), 44 deletions(-) >> >> >> base-commit: 3b3bea6d4b9c162f9e555905d96b8c1da67ecd5b From linusw at kernel.org Tue May 5 02:21:14 2026 From: linusw at kernel.org (Linus Walleij) Date: Tue, 5 May 2026 11:21:14 +0200 Subject: [PATCH 1/2] dt-bindings: pinctl: amlogic,pinctrl-a4: Add compatible string for A9 In-Reply-To: <20260428-a9-pinctrl-v1-1-cd611bb5f52d@amlogic.com> References: <20260428-a9-pinctrl-v1-0-cd611bb5f52d@amlogic.com> <20260428-a9-pinctrl-v1-1-cd611bb5f52d@amlogic.com> Message-ID: Hi Xianwei, thanks for your patch! On Tue, Apr 28, 2026 at 10:22?AM Xianwei Zhao via B4 Relay wrote: > From: Xianwei Zhao > > Update dt-binding document for pinctrl of Amlogic A9. > > Signed-off-by: Xianwei Zhao Please update the commit message with the information the DT maintainers need to determine that a new compatible is needed, i.e. "registers are allocated differently" or "new hardware registers exist" etc. Yours, Linus Walleij From Frank.li at nxp.com Tue May 5 08:39:52 2026 From: Frank.li at nxp.com (Frank Li) Date: Tue, 5 May 2026 11:39:52 -0400 Subject: [PATCH 05/10] spi: nxp-xspi: Use FIELD_MODIFY() In-Reply-To: <20260430155456.36998-6-18255117159@163.com> References: <20260430155456.36998-1-18255117159@163.com> <20260430155456.36998-6-18255117159@163.com> Message-ID: On Thu, Apr 30, 2026 at 11:54:51PM +0800, Hans Zhang wrote: > Use FIELD_MODIFY() to remove open-coded bit manipulation. > No functional change intended. > > Signed-off-by: Hans Zhang <18255117159 at 163.com> > --- Reviewed-by: Frank Li > drivers/spi/spi-nxp-xspi.c | 12 ++++-------- > 1 file changed, 4 insertions(+), 8 deletions(-) > > diff --git a/drivers/spi/spi-nxp-xspi.c b/drivers/spi/spi-nxp-xspi.c > index 385302a6e62f..037eac24e6fd 100644 > --- a/drivers/spi/spi-nxp-xspi.c > +++ b/drivers/spi/spi-nxp-xspi.c > @@ -493,9 +493,8 @@ static void nxp_xspi_disable_ddr(struct nxp_xspi *xspi) > writel(reg, base + XSPI_MCR); > > reg &= ~XSPI_MCR_DDR_EN; > - reg &= ~XSPI_MCR_DQS_FA_SEL_MASK; > /* Use dummy pad loopback mode to sample data */ > - reg |= FIELD_PREP(XSPI_MCR_DQS_FA_SEL_MASK, 0x01); > + FIELD_MODIFY(XSPI_MCR_DQS_FA_SEL_MASK, ®, 0x01); > writel(reg, base + XSPI_MCR); > xspi->support_max_rate = 133000000; > > @@ -524,15 +523,13 @@ static void nxp_xspi_enable_ddr(struct nxp_xspi *xspi) > writel(reg, base + XSPI_MCR); > > reg |= XSPI_MCR_DDR_EN; > - reg &= ~XSPI_MCR_DQS_FA_SEL_MASK; > /* Use external dqs to sample data */ > - reg |= FIELD_PREP(XSPI_MCR_DQS_FA_SEL_MASK, 0x03); > + FIELD_MODIFY(XSPI_MCR_DQS_FA_SEL_MASK, ®, 0x03); > writel(reg, base + XSPI_MCR); > xspi->support_max_rate = 200000000; > > reg = readl(base + XSPI_FLSHCR); > - reg &= ~XSPI_FLSHCR_TDH_MASK; > - reg |= FIELD_PREP(XSPI_FLSHCR_TDH_MASK, 0x01); > + FIELD_MODIFY(XSPI_FLSHCR_TDH_MASK, ®, 0x01); > writel(reg, base + XSPI_FLSHCR); > > reg = FIELD_PREP(XSPI_SMPR_DLLFSMPFA_MASK, 0x04); > @@ -1096,8 +1093,7 @@ static int nxp_xspi_default_setup(struct nxp_xspi *xspi) > > /* Give read/write access right to EENV0 */ > reg = readl(base + XSPI_FRAD0_WORD2); > - reg &= ~XSPI_FRAD0_WORD2_MD0ACP_MASK; > - reg |= FIELD_PREP(XSPI_FRAD0_WORD2_MD0ACP_MASK, 0x03); > + FIELD_MODIFY(XSPI_FRAD0_WORD2_MD0ACP_MASK, ®, 0x03); > writel(reg, base + XSPI_FRAD0_WORD2); > > /* Enable the FRAD check for EENV0 */ > -- > 2.34.1 > From xianwei.zhao at amlogic.com Tue May 5 19:59:56 2026 From: xianwei.zhao at amlogic.com (Xianwei Zhao) Date: Wed, 6 May 2026 10:59:56 +0800 Subject: [PATCH 1/2] dt-bindings: pinctl: amlogic,pinctrl-a4: Add compatible string for A9 In-Reply-To: <20260430-notorious-cat-of-wonder-a6dacf@quoll> References: <20260428-a9-pinctrl-v1-0-cd611bb5f52d@amlogic.com> <20260428-a9-pinctrl-v1-1-cd611bb5f52d@amlogic.com> <20260430-notorious-cat-of-wonder-a6dacf@quoll> Message-ID: <659fea60-6b65-4f70-96b3-780b53740e22@amlogic.com> Hi Krzysztof, Thanks for your advice. On 2026/4/30 17:36, Krzysztof Kozlowski wrote: > On Tue, Apr 28, 2026 at 08:22:48AM +0000, Xianwei Zhao wrote: >> Update dt-binding document for pinctrl of Amlogic A9. > And why it is not compatible with a4 or a5? You have entire commit msg > for this. > Will add a commit message explaining why it is not compatible with previous SoCs. >> Signed-off-by: Xianwei Zhao >> --- >> Documentation/devicetree/bindings/pinctrl/amlogic,pinctrl-a4.yaml | 1 + >> 1 file changed, 1 insertion(+) > Best regards, > Krzysztof From xianwei.zhao at amlogic.com Tue May 5 20:01:05 2026 From: xianwei.zhao at amlogic.com (Xianwei Zhao) Date: Wed, 6 May 2026 11:01:05 +0800 Subject: [PATCH 1/2] dt-bindings: pinctl: amlogic,pinctrl-a4: Add compatible string for A9 In-Reply-To: References: <20260428-a9-pinctrl-v1-0-cd611bb5f52d@amlogic.com> <20260428-a9-pinctrl-v1-1-cd611bb5f52d@amlogic.com> Message-ID: Hi Linus, Thanks for your review. On 2026/5/5 17:21, Linus Walleij wrote: > Hi Xianwei, > > thanks for your patch! > > On Tue, Apr 28, 2026 at 10:22?AM Xianwei Zhao via B4 Relay > wrote: > >> From: Xianwei Zhao >> >> Update dt-binding document for pinctrl of Amlogic A9. >> >> Signed-off-by: Xianwei Zhao > Please update the commit message with the information the DT > maintainers need to determine that a new compatible is needed, > i.e. "registers are allocated differently" or "new hardware registers > exist" etc. Will do. From mani at kernel.org Wed May 6 07:00:27 2026 From: mani at kernel.org (Manivannan Sadhasivam) Date: Wed, 6 May 2026 19:30:27 +0530 Subject: [PATCH v7 0/2] PCI: Configure Root Port MPS during host probing In-Reply-To: <20251127170908.14850-1-18255117159@163.com> References: <20251127170908.14850-1-18255117159@163.com> Message-ID: On Fri, Nov 28, 2025 at 01:09:06AM +0800, Hans Zhang wrote: > Current PCIe initialization exhibits a key optimization gap: Root Ports > may operate with non-optimal Maximum Payload Size (MPS) settings. While > downstream device configuration is handled during bus enumeration, Root > Port MPS values inherited from firmware or hardware defaults often fail > to utilize the full capabilities supported by controller hardware. This > results in suboptimal data transfer efficiency throughout the PCIe > hierarchy. > > This patch series addresses this by: > > 1. Core PCI enhancement (Patch 1): > - Proactively configures Root Port MPS during host controller probing > - Sets initial MPS to hardware maximum (128 << dev->pcie_mpss) > - Conditional on PCIe bus tuning being enabled (PCIE_BUS_TUNE_OFF unset) > and not in PCIE_BUS_PEER2PEER mode (which requires default 128 bytes) > - Maintains backward compatibility via PCIE_BUS_TUNE_OFF check > - Preserves standard MPS negotiation during downstream enumeration > > 2. Driver cleanup (Patch 2): > - Removes redundant MPS configuration from Meson PCIe controller driver > - Functionality is now centralized in PCI core > - Simplifies driver maintenance long-term > For the series, Reviewed-by: Manivannan Sadhasivam Bjorn: Could you please take a look? This series has been floating for a while... - Mani > --- > Changes in v7: > - Exclude PCIE_BUS_PEER2PEER mode from Root Port MPS configuration > - Remove redundant check for upstream bridge (Root Ports don't have one) > - Improve commit message and code comments as per Bjorn. > > Changes for v6: > https://patchwork.kernel.org/project/linux-pci/patch/20251104165125.174168-1-18255117159 at 163.com/ > > - Modify the commit message and comments. (Bjorn) > - Patch 1/2 code logic: Add !bridge check to configure MPS only for Root Ports > without an upstream bridge (root bridges), avoiding incorrect handling of > non-root-bridge Root Ports (Niklas). > > Changes for v5: > https://patchwork.kernel.org/project/linux-pci/patch/20250620155507.1022099-1-18255117159 at 163.com/ > > - Use pcie_set_mps directly instead of pcie_write_mps. > - The patch 1 commit message were modified. > > Changes for v4: > https://patchwork.kernel.org/project/linux-pci/patch/20250510155607.390687-1-18255117159 at 163.com/ > > - The patch [v4 1/2] add a comment to explain why it was done this way. > - The patch [v4 2/2] have not been modified. > - Drop patch [v3 3/3]. The Maintainer of the pci-aardvark.c file suggests > that this patch cannot be submitted. In addition, Mani also suggests > dropping this patch until this series of issues is resolved. > > Changes for v3: > https://patchwork.kernel.org/project/linux-pci/patch/20250506173439.292460-1-18255117159 at 163.com/ > > - The new split is patch 2/3 and 3/3. > - Modify the patch 1/3 according to Niklas' suggestion. > > Changes for v2: > https://patchwork.kernel.org/project/linux-pci/patch/20250425095708.32662-1-18255117159 at 163.com/ > > - According to the Maintainer's suggestion, limit the setting of MPS > changes to platforms with controller drivers. > - Delete the MPS code set by the SOC manufacturer. > --- > > Hans Zhang (2): > PCI: Configure Root Port MPS during host probing > PCI: dwc: Remove redundant MPS configuration > > drivers/pci/controller/dwc/pci-meson.c | 17 ----------------- > drivers/pci/probe.c | 12 ++++++++++++ > 2 files changed, 12 insertions(+), 17 deletions(-) > > > base-commit: 765e56e41a5af2d456ddda6cbd617b9d3295ab4e > -- > 2.34.1 > -- ????????? ???????? From daniel.lezcano at oss.qualcomm.com Wed May 6 08:45:28 2026 From: daniel.lezcano at oss.qualcomm.com (Daniel Lezcano) Date: Wed, 6 May 2026 17:45:28 +0200 Subject: [PATCH v5 4/8] thermal: amlogic: Add support for secure monitor calibration readout In-Reply-To: <20260424-add-thermal-t7-vim4-v5-4-9040ca36afe2@aliel.fr> References: <20260424-add-thermal-t7-vim4-v5-0-9040ca36afe2@aliel.fr> <20260424-add-thermal-t7-vim4-v5-4-9040ca36afe2@aliel.fr> Message-ID: On 4/24/26 17:45, Ronald Claveau via B4 Relay wrote: [ ... ] > +static int amlogic_thermal_probe_sm(struct platform_device *pdev, > + struct amlogic_thermal *pdata) > +{ > + struct device *dev = &pdev->dev; > + struct of_phandle_args ph_args; > + int ret; > + > + ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, > + "amlogic,secure-monitor", > + 1, 0, &ph_args); > + if (ret) > + return ret; > + > + if (!ph_args.np) { > + dev_err(dev, "Failed to parse secure monitor phandle\n"); > + return -ENODEV; > + } > + > + pdata->sm_fw = meson_sm_get(ph_args.np); > + of_node_put(ph_args.np); > + if (!pdata->sm_fw) { > + dev_err(dev, "Failed to get secure monitor firmware\n"); > + return -EPROBE_DEFER; > + } > + > + pdata->tsensor_id = ph_args.args[0]; > + > + return meson_sm_get_thermal_calib(pdata->sm_fw, > + &pdata->trim_info, > + pdata->tsensor_id); This driver has a dependency on patch 2 and 3. Shall those being merged through the thermal tree ? > +} [ ... ] From neil.armstrong at linaro.org Wed May 6 08:55:14 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 6 May 2026 17:55:14 +0200 Subject: [PATCH v5 4/8] thermal: amlogic: Add support for secure monitor calibration readout In-Reply-To: References: <20260424-add-thermal-t7-vim4-v5-0-9040ca36afe2@aliel.fr> <20260424-add-thermal-t7-vim4-v5-4-9040ca36afe2@aliel.fr> Message-ID: <27d7363e-b8bc-4e69-a9d2-abd7e12fe562@linaro.org> On 5/6/26 17:45, Daniel Lezcano wrote: > On 4/24/26 17:45, Ronald Claveau via B4 Relay wrote: > > [ ... ] > >> +static int amlogic_thermal_probe_sm(struct platform_device *pdev, >> +??????????????????? struct amlogic_thermal *pdata) >> +{ >> +??? struct device *dev = &pdev->dev; >> +??? struct of_phandle_args ph_args; >> +??? int ret; >> + >> +??? ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, >> +?????????????????????????? "amlogic,secure-monitor", >> +?????????????????????????? 1, 0, &ph_args); >> +??? if (ret) >> +??????? return ret; >> + >> +??? if (!ph_args.np) { >> +??????? dev_err(dev, "Failed to parse secure monitor phandle\n"); >> +??????? return -ENODEV; >> +??? } >> + >> +??? pdata->sm_fw = meson_sm_get(ph_args.np); >> +??? of_node_put(ph_args.np); >> +??? if (!pdata->sm_fw) { >> +??????? dev_err(dev, "Failed to get secure monitor firmware\n"); >> +??????? return -EPROBE_DEFER; >> +??? } >> + >> +??? pdata->tsensor_id = ph_args.args[0]; >> + >> +??? return meson_sm_get_thermal_calib(pdata->sm_fw, >> +????????????????????? &pdata->trim_info, >> +????????????????????? pdata->tsensor_id); > > This driver has a dependency on patch 2 and 3. Shall those being merged through the thermal tree ? It's fine for me. Neil > > >> +} > > > [ ... ] > From sozdayvek at gmail.com Wed May 6 11:35:12 2026 From: sozdayvek at gmail.com (Stepan Ionichev) Date: Wed, 6 May 2026 23:35:12 +0500 Subject: [PATCH] spi: amlogic-spisg: drop misleading NULL check on exdesc Message-ID: <20260506183513.482-1-sozdayvek@gmail.com> aml_spisg_setup_transfer() takes a non-NULL exdesc pointer; the function dereferences exdesc unconditionally later in the body to populate the SPI scatter-gather descriptors (tx_ccsg / rx_ccsg). The sole caller, aml_spisg_transfer_one_message(), always passes a valid pointer derived from kcalloc(). The "if (exdesc)" guard around the memset() at the start of the function is therefore dead and misleading -- it suggests callers may pass NULL when in fact they may not. smatch flags the inconsistency: drivers/spi/spi-amlogic-spisg.c:314 aml_spisg_setup_transfer() error: we previously assumed 'exdesc' could be null (see line 261) Drop the check; the unconditional memset matches the unconditional dereferences elsewhere in the function and removes the inconsistency that smatch reports. No functional change. Signed-off-by: Stepan Ionichev --- drivers/spi/spi-amlogic-spisg.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c index 19c5eba41..b2a916496 100644 --- a/drivers/spi/spi-amlogic-spisg.c +++ b/drivers/spi/spi-amlogic-spisg.c @@ -258,8 +258,7 @@ static int aml_spisg_setup_transfer(struct spisg_device *spisg, int ret; memset(desc, 0, sizeof(*desc)); - if (exdesc) - memset(exdesc, 0, sizeof(*exdesc)); + memset(exdesc, 0, sizeof(*exdesc)); aml_spisg_set_speed(spisg, xfer->speed_hz); xfer->effective_speed_hz = spisg->effective_speed_hz; -- 2.43.0 From xianwei.zhao at amlogic.com Wed May 6 19:43:17 2026 From: xianwei.zhao at amlogic.com (Xianwei Zhao) Date: Thu, 7 May 2026 10:43:17 +0800 Subject: [PATCH] spi: amlogic-spisg: drop misleading NULL check on exdesc In-Reply-To: <20260506183513.482-1-sozdayvek@gmail.com> References: <20260506183513.482-1-sozdayvek@gmail.com> Message-ID: Hi Stepan, On 2026/5/7 02:35, Stepan Ionichev wrote: > aml_spisg_setup_transfer() takes a non-NULL exdesc pointer; the > function dereferences exdesc unconditionally later in the body to > populate the SPI scatter-gather descriptors (tx_ccsg / rx_ccsg). > The sole caller, aml_spisg_transfer_one_message(), always passes a > valid pointer derived from kcalloc(). > > The "if (exdesc)" guard around the memset() at the start of the > function is therefore dead and misleading -- it suggests callers > may pass NULL when in fact they may not. smatch flags the > inconsistency: > > drivers/spi/spi-amlogic-spisg.c:314 aml_spisg_setup_transfer() > error: we previously assumed 'exdesc' could be null (see line 261) > > Drop the check; the unconditional memset matches the unconditional > dereferences elsewhere in the function and removes the inconsistency > that smatch reports. > > No functional change. > > Signed-off-by: Stepan Ionichev > --- > drivers/spi/spi-amlogic-spisg.c | 3 +-- > 1 file changed, 1 insertion(+), 2 deletions(-) > > diff --git a/drivers/spi/spi-amlogic-spisg.c b/drivers/spi/spi-amlogic-spisg.c > index 19c5eba41..b2a916496 100644 > --- a/drivers/spi/spi-amlogic-spisg.c > +++ b/drivers/spi/spi-amlogic-spisg.c > @@ -258,8 +258,7 @@ static int aml_spisg_setup_transfer(struct spisg_device *spisg, > int ret; > > memset(desc, 0, sizeof(*desc)); > - if (exdesc) > - memset(exdesc, 0, sizeof(*exdesc)); > + memset(exdesc, 0, sizeof(*exdesc)); > aml_spisg_set_speed(spisg, xfer->speed_hz); > xfer->effective_speed_hz = spisg->effective_speed_hz; Reviewed-by: Xianwei Zhao From devnull+xianwei.zhao.amlogic.com at kernel.org Thu May 7 01:21:05 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Thu, 07 May 2026 08:21:05 +0000 Subject: [PATCH v2 0/2] pinctrl: add support amlogic a9 Message-ID: <20260507-a9-pinctrl-v2-0-49774feff2ef@amlogic.com> Add pinctrl bindings and driver about for amlogic a9. Signed-off-by: Xianwei Zhao --- Changes in v2: - Add a commit message explaining why it is not compatible with previous SoCs and rebase code. - Link to v1: https://lore.kernel.org/r/20260428-a9-pinctrl-v1-0-cd611bb5f52d at amlogic.com --- Xianwei Zhao (2): dt-bindings: pinctl: amlogic,pinctrl-a4: Add compatible string for A9 pinctrl: meson: support amlogic A9 SoC .../bindings/pinctrl/amlogic,pinctrl-a4.yaml | 1 + drivers/pinctrl/meson/pinctrl-amlogic-a4.c | 61 ++++++++++++++++++++-- 2 files changed, 57 insertions(+), 5 deletions(-) --- base-commit: eccd2fde7dbc398a6aba9120c01247beeca55aec change-id: 20260129-a9-pinctrl-a9511aa224bf Best regards, -- Xianwei Zhao From devnull+xianwei.zhao.amlogic.com at kernel.org Thu May 7 01:21:06 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Thu, 07 May 2026 08:21:06 +0000 Subject: [PATCH v2 1/2] dt-bindings: pinctl: amlogic,pinctrl-a4: Add compatible string for A9 In-Reply-To: <20260507-a9-pinctrl-v2-0-49774feff2ef@amlogic.com> References: <20260507-a9-pinctrl-v2-0-49774feff2ef@amlogic.com> Message-ID: <20260507-a9-pinctrl-v2-1-49774feff2ef@amlogic.com> From: Xianwei Zhao Update dt-binding document for pinctrl of Amlogic A9. In Amlogic A9 SoC, a bank mux register reuse other banks. The multiplexed part requires special processing and is therefore incompatible with the previous SoCs. Signed-off-by: Xianwei Zhao --- Documentation/devicetree/bindings/pinctrl/amlogic,pinctrl-a4.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/pinctrl/amlogic,pinctrl-a4.yaml b/Documentation/devicetree/bindings/pinctrl/amlogic,pinctrl-a4.yaml index 6ba66c2033b4..b69db1b95345 100644 --- a/Documentation/devicetree/bindings/pinctrl/amlogic,pinctrl-a4.yaml +++ b/Documentation/devicetree/bindings/pinctrl/amlogic,pinctrl-a4.yaml @@ -17,6 +17,7 @@ properties: oneOf: - enum: - amlogic,pinctrl-a4 + - amlogic,pinctrl-a9 - amlogic,pinctrl-s6 - amlogic,pinctrl-s7 - items: -- 2.52.0 From devnull+xianwei.zhao.amlogic.com at kernel.org Thu May 7 01:21:07 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Thu, 07 May 2026 08:21:07 +0000 Subject: [PATCH v2 2/2] pinctrl: meson: support amlogic A9 SoC In-Reply-To: <20260507-a9-pinctrl-v2-0-49774feff2ef@amlogic.com> References: <20260507-a9-pinctrl-v2-0-49774feff2ef@amlogic.com> Message-ID: <20260507-a9-pinctrl-v2-2-49774feff2ef@amlogic.com> From: Xianwei Zhao In Amlogic A9 SoC, subordinate bank reuse other master bank is not from bit0, and subordinate bank reuse multi master banks. This submission implements this situation. Signed-off-by: Xianwei Zhao --- drivers/pinctrl/meson/pinctrl-amlogic-a4.c | 61 +++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/drivers/pinctrl/meson/pinctrl-amlogic-a4.c b/drivers/pinctrl/meson/pinctrl-amlogic-a4.c index 35d27626a336..1fae372bdbad 100644 --- a/drivers/pinctrl/meson/pinctrl-amlogic-a4.c +++ b/drivers/pinctrl/meson/pinctrl-amlogic-a4.c @@ -55,14 +55,18 @@ struct aml_pio_control { * partial bank(subordinate) pins mux config use other bank(main) mux registgers * m_bank_id: the main bank which pin_id from 0, but register bit not from bit 0 * m_bit_offs: bit offset the main bank mux register + * s_bit_offs: start bit that subordinate bank use mux register * sid: start pin_id of subordinate bank * eid: end pin_id of subordinate bank + * next: subordinate bank reused multiple other bank groups. */ struct multi_mux { unsigned int m_bank_id; unsigned int m_bit_offs; + unsigned int s_bit_offs; unsigned int sid; unsigned int eid; + const struct multi_mux *next; }; struct aml_pctl_data { @@ -124,12 +128,51 @@ static const char *aml_bank_name[31] = { "GPIOCC", "TEST_N", "ANALOG" }; +static const struct multi_mux multi_mux_a9[] = { + { + .m_bank_id = AMLOGIC_GPIO_C, + .m_bit_offs = 4, + .s_bit_offs = 0, + .sid = (AMLOGIC_GPIO_D << 8) + 16, + .eid = (AMLOGIC_GPIO_D << 8) + 16, + .next = &multi_mux_a9[1], + }, { + .m_bank_id = AMLOGIC_GPIO_AO, + .m_bit_offs = 0, + .s_bit_offs = 52, + .sid = (AMLOGIC_GPIO_D << 8) + 17, + .eid = (AMLOGIC_GPIO_D << 8) + 17, + .next = NULL, + }, { + .m_bank_id = AMLOGIC_GPIO_A, + .m_bit_offs = 0, + .s_bit_offs = 80, + .sid = (AMLOGIC_GPIO_Y << 8) + 8, + .eid = (AMLOGIC_GPIO_Y << 8) + 9, + .next = NULL, + }, { + .m_bank_id = AMLOGIC_GPIO_CC, + .m_bit_offs = 24, + .s_bit_offs = 0, + .sid = (AMLOGIC_GPIO_X << 8) + 16, + .eid = (AMLOGIC_GPIO_X << 8) + 17, + .next = NULL, + }, +}; + +static const struct aml_pctl_data a9_priv_data = { + .number = ARRAY_SIZE(multi_mux_a9), + .p_mux = multi_mux_a9, +}; + static const struct multi_mux multi_mux_s7[] = { { .m_bank_id = AMLOGIC_GPIO_CC, .m_bit_offs = 24, + .s_bit_offs = 0, .sid = (AMLOGIC_GPIO_X << 8) + 16, .eid = (AMLOGIC_GPIO_X << 8) + 19, + .next = NULL, }, }; @@ -142,13 +185,17 @@ static const struct multi_mux multi_mux_s6[] = { { .m_bank_id = AMLOGIC_GPIO_CC, .m_bit_offs = 24, + .s_bit_offs = 0, .sid = (AMLOGIC_GPIO_X << 8) + 16, .eid = (AMLOGIC_GPIO_X << 8) + 19, + .next = NULL, }, { .m_bank_id = AMLOGIC_GPIO_F, .m_bit_offs = 4, + .s_bit_offs = 0, .sid = (AMLOGIC_GPIO_D << 8) + 6, .eid = (AMLOGIC_GPIO_D << 8) + 6, + .next = NULL, }, }; @@ -177,31 +224,34 @@ static int aml_pctl_set_function(struct aml_pinctrl *info, struct aml_gpio_bank *bank = gpio_chip_to_bank(range->gc); unsigned int shift; int reg; - int i; + int i, loop_count; unsigned int offset = bank->mux_bit_offs; const struct multi_mux *p_mux; /* peculiar mux reg set */ - if (bank->p_mux) { - p_mux = bank->p_mux; + loop_count = 10; + p_mux = bank->p_mux; + while (p_mux && loop_count) { if (pin_id >= p_mux->sid && pin_id <= p_mux->eid) { bank = NULL; for (i = 0; i < info->nbanks; i++) { if (info->banks[i].bank_id == p_mux->m_bank_id) { bank = &info->banks[i]; - break; + break; } } if (!bank || !bank->reg_mux) return -EINVAL; - shift = (pin_id - p_mux->sid) << 2; + shift = ((pin_id - p_mux->sid) << 2) + p_mux->s_bit_offs; reg = (shift / 32) * 4; offset = shift % 32; return regmap_update_bits(bank->reg_mux, reg, 0xf << offset, (func & 0xf) << offset); } + p_mux = p_mux->next; + loop_count--; } /* normal mux reg set */ @@ -1159,6 +1209,7 @@ static int aml_pctl_probe(struct platform_device *pdev) static const struct of_device_id aml_pctl_of_match[] = { { .compatible = "amlogic,pinctrl-a4", }, + { .compatible = "amlogic,pinctrl-a9", .data = &a9_priv_data, }, { .compatible = "amlogic,pinctrl-s7", .data = &s7_priv_data, }, { .compatible = "amlogic,pinctrl-s6", .data = &s6_priv_data, }, { /* sentinel */ } -- 2.52.0 From lee at kernel.org Thu May 7 05:53:42 2026 From: lee at kernel.org (Lee Jones) Date: Thu, 07 May 2026 13:53:42 +0100 Subject: (subset) [PATCH v4 1/8] dt-bindings: mfd: khadas: Add new compatible for Khadas VIM4 MCU In-Reply-To: <20260421-add-mcu-fan-khadas-vim4-v4-1-447114a28f2d@aliel.fr> References: <20260421-add-mcu-fan-khadas-vim4-v4-1-447114a28f2d@aliel.fr> Message-ID: <177815842272.1844364.17432398965217021542.b4-ty@b4> On Tue, 21 Apr 2026 13:49:18 +0200, Ronald Claveau wrote: > The Khadas VIM4 MCU register is slightly different > from previous boards' MCU. > This board also features a switchable power source for its fan. Applied, thanks! [1/8] dt-bindings: mfd: khadas: Add new compatible for Khadas VIM4 MCU commit: a10878e699567d88267200afdb165107567e0287 -- Lee Jones [???] From broonie at kernel.org Thu May 7 01:25:58 2026 From: broonie at kernel.org (Mark Brown) Date: Thu, 07 May 2026 17:25:58 +0900 Subject: [PATCH] spi: amlogic-spisg: drop misleading NULL check on exdesc In-Reply-To: <20260506183513.482-1-sozdayvek@gmail.com> References: <20260506183513.482-1-sozdayvek@gmail.com> Message-ID: <177814235817.749715.9876596107513114221.b4-ty@b4> On Wed, 06 May 2026 23:35:12 +0500, Stepan Ionichev wrote: > spi: amlogic-spisg: drop misleading NULL check on exdesc Applied to https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-7.2 Thanks! [1/1] spi: amlogic-spisg: drop misleading NULL check on exdesc https://git.kernel.org/broonie/spi/c/54725e3049e1 All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted. You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed. If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced. Please add any relevant lists and maintainers to the CCs when replying to this mail. Thanks, Mark From conor at kernel.org Thu May 7 10:24:39 2026 From: conor at kernel.org (Conor Dooley) Date: Thu, 7 May 2026 18:24:39 +0100 Subject: [PATCH v2 1/2] dt-bindings: pinctl: amlogic,pinctrl-a4: Add compatible string for A9 In-Reply-To: <20260507-a9-pinctrl-v2-1-49774feff2ef@amlogic.com> References: <20260507-a9-pinctrl-v2-0-49774feff2ef@amlogic.com> <20260507-a9-pinctrl-v2-1-49774feff2ef@amlogic.com> Message-ID: <20260507-juice-helpless-b04e31e10778@spud> Acked-by: Conor Dooley pw-bot: not-applicable -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 228 bytes Desc: not available URL: From dawei.feng at seu.edu.cn Thu May 7 21:24:16 2026 From: dawei.feng at seu.edu.cn (Dawei Feng) Date: Fri, 8 May 2026 12:24:16 +0800 Subject: [PATCH] crypto: amlogic - avoid double cleanup in meson_crypto_probe() Message-ID: <20260508042416.419216-1-dawei.feng@seu.edu.cn> When meson_allocate_chanlist() fails after a partial allocation, it already unwinds the allocated chanlist state through its local error path. meson_crypto_probe() then jump to error_flow and calls meson_free_chanlist() again, causing the same per-flow resources to be torn down twice. In the reproduced failure path, the second teardown re-entered crypto_engine_exit() on an already destroyed worker and KASAN reported a slab-use-after-free in kthread_destroy_worker(). Prevent double-free by handling partial allocation failures locally within meson_allocate_chanlist() and skipping the outer cleanup path. The bug was first flagged by an experimental analysis tool we are developing for kernel memory-management bugs while analyzing v6.13-rc1. The tool is still under development and is not yet publicly available. The bug was reproduced in a QEMU x86_64 guest booted with KASAN on v7.1, using the reproducer under tools/testing/meson_crypto_probe. The reproducer forces the second dma_alloc_attrs() call in the gxl-crypto probe path to return NULL, making meson_allocate_chanlist() fail after partial initialization. On the unpatched kernel this reliably triggered a slab-use-after-free. With this fix applied, the same reproducer no longer emits any KASAN report and the probe fails cleanly with -ENOMEM. ================================================================== BUG: KASAN: slab-use-after-free in kthread_destroy_worker+0xb2/0xd0 Read of size 8 at addr ff1100010c057a68 by task insmod/265 CPU: 1 UID: 0 PID: 265 Comm: insmod Tainted: G O 7.1.0-rc2-00376-g810af9adc907-dirty #10 PREEMPT(lazy) Tainted: [O]=OOT_MODULE Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.15.0-1 04/01/2014 Call Trace: dump_stack_lvl+0x68/0xa0 print_report+0xcb/0x5e0 ? __virt_addr_valid+0x21d/0x3f0 ? kthread_destroy_worker+0xb2/0xd0 ? kthread_destroy_worker+0xb2/0xd0 kasan_report+0xca/0x100 ? kthread_destroy_worker+0xb2/0xd0 kthread_destroy_worker+0xb2/0xd0 meson_crypto_probe+0x4d0/0xc10 [amlogic_gxl_crypto] platform_probe+0x99/0x140 really_probe+0x1c6/0x6a0 ? __pfx___device_attach_driver+0x10/0x10 __driver_probe_device+0x248/0x310 ? acpi_driver_match_device+0xb0/0x100 driver_probe_device+0x48/0x210 ? __pfx___device_attach_driver+0x10/0x10 __device_attach_driver+0x160/0x320 bus_for_each_drv+0x104/0x190 ? __pfx_bus_for_each_drv+0x10/0x10 ? _raw_spin_unlock_irqrestore+0x2c/0x50 __device_attach+0x19d/0x3b0 ? __pfx___device_attach+0x10/0x10 ? do_raw_spin_unlock+0x53/0x220 device_initial_probe+0x78/0xa0 bus_probe_device+0x5b/0x130 device_add+0xcfd/0x1430 ? __pfx_device_add+0x10/0x10 ? insert_resource+0x34/0x50 ? lock_release+0xc9/0x290 platform_device_add+0x24e/0x590 ? __pfx_meson_crypto_probe_repro_init+0x10/0x10 [meson_crypto_probe_repro] meson_crypto_probe_repro_init+0x330/0xff0 [meson_crypto_probe_repro] do_one_initcall+0xc0/0x450 ? __pfx_do_one_initcall+0x10/0x10 ? _raw_spin_unlock_irqrestore+0x2c/0x50 ? __create_object+0x59/0x80 ? kasan_unpoison+0x27/0x60 do_init_module+0x27b/0x7d0 ? __pfx_do_init_module+0x10/0x10 ? kasan_quarantine_put+0x84/0x1d0 ? kfree+0x32c/0x510 ? load_module+0x561e/0x5ff0 load_module+0x54fe/0x5ff0 ? __pfx_load_module+0x10/0x10 ? security_file_permission+0x20/0x40 ? kernel_read_file+0x23d/0x6e0 ? mmap_region+0x235/0x4a0 ? __pfx_kernel_read_file+0x10/0x10 ? __file_has_perm+0x2c0/0x3e0 init_module_from_file+0x158/0x180 ? __pfx_init_module_from_file+0x10/0x10 ? __lock_acquire+0x45a/0x1ba0 ? idempotent_init_module+0x315/0x610 ? lock_release+0xc9/0x290 ? lockdep_init_map_type+0x4b/0x220 ? do_raw_spin_unlock+0x53/0x220 idempotent_init_module+0x330/0x610 ? __pfx_idempotent_init_module+0x10/0x10 ? __pfx_cred_has_capability.isra.0+0x10/0x10 ? ksys_mmap_pgoff+0x385/0x520 __x64_sys_finit_module+0xbe/0x120 do_syscall_64+0x115/0x690 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f7d6d31690d Code: 5b 41 5c c3 66 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d f3 b4 0f 00 f7 d8 > RSP: 002b:00007fffc027ac68 EFLAGS: 00000246 ORIG_RAX: 0000000000000139 RAX: ffffffffffffffda RBX: 000055f7b81967c0 RCX: 00007f7d6d31690d RDX: 0000000000000000 RSI: 000055f79a0d6cd2 RDI: 0000000000000003 RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000003 R11: 0000000000000246 R12: 000055f79a0d6cd2 R13: 000055f7b8196790 R14: 000055f79a0d5888 R15: 000055f7b81968e0 Fixes: 48fe583fe541 ("crypto: amlogic - Add crypto accelerator for amlogic GXL") Cc: stable at vger.kernel.org Signed-off-by: Zilin Guan Signed-off-by: Dawei Feng --- drivers/crypto/amlogic/amlogic-gxl-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/crypto/amlogic/amlogic-gxl-core.c b/drivers/crypto/amlogic/amlogic-gxl-core.c index 1c18a5b8470e..6cb33949915f 100644 --- a/drivers/crypto/amlogic/amlogic-gxl-core.c +++ b/drivers/crypto/amlogic/amlogic-gxl-core.c @@ -291,8 +291,8 @@ static int meson_crypto_probe(struct platform_device *pdev) return 0; error_alg: meson_unregister_algs(mc); -error_flow: meson_free_chanlist(mc, MAXFLOW - 1); +error_flow: clk_disable_unprepare(mc->busclk); return err; } -- 2.34.1 From mahamaryamjavaid at gmail.com Thu May 7 22:57:31 2026 From: mahamaryamjavaid at gmail.com (Maha Maryam Javaid) Date: Fri, 8 May 2026 10:57:31 +0500 Subject: [PATCH] staging: media: meson: fix typo in codec files Message-ID: <20260508055731.19784-1-mahamaryamjavaid@gmail.com> Fix spelling mistake: substracted -> subtracted Signed-off-by: Maha Maryam Javaid --- Changes in v2: - Combined both meson typo fixes into a single patch drivers/staging/media/meson/vdec/codec_h264.c | 2 +- drivers/staging/media/meson/vdec/codec_mpeg12.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/staging/media/meson/vdec/codec_h264.c b/drivers/staging/media/meson/vdec/codec_h264.c index 89e0f8624e5b..a6074de15118 100644 --- a/drivers/staging/media/meson/vdec/codec_h264.c +++ b/drivers/staging/media/meson/vdec/codec_h264.c @@ -16,7 +16,7 @@ #define SIZE_SEI (8 * SZ_1K) /* - * Offset added by the firmware which must be substracted + * Offset added by the firmware which must be subtracted * from the workspace phyaddr */ #define WORKSPACE_BUF_OFFSET 0x1000000 diff --git a/drivers/staging/media/meson/vdec/codec_mpeg12.c b/drivers/staging/media/meson/vdec/codec_mpeg12.c index 76e9ca7191ab..ab4374e3b2ef 100644 --- a/drivers/staging/media/meson/vdec/codec_mpeg12.c +++ b/drivers/staging/media/meson/vdec/codec_mpeg12.c @@ -12,7 +12,7 @@ #include "vdec_helpers.h" #define SIZE_WORKSPACE SZ_128K -/* Offset substracted by the firmware from the workspace paddr */ +/* Offset subtracted by the firmware from the workspace paddr */ #define WORKSPACE_OFFSET (5 * SZ_1K) /* map firmware registers to known MPEG1/2 functions */ -- 2.34.1 From devnull+xianwei.zhao.amlogic.com at kernel.org Fri May 8 00:36:54 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Fri, 08 May 2026 07:36:54 +0000 Subject: [PATCH 1/3] irqchip/meson-gpio: fix incorrect register address In-Reply-To: <20260508-a9-gpio-irqchip-v1-0-9dc5f3e022e0@amlogic.com> References: <20260508-a9-gpio-irqchip-v1-0-9dc5f3e022e0@amlogic.com> Message-ID: <20260508-a9-gpio-irqchip-v1-1-9dc5f3e022e0@amlogic.com> From: Xianwei Zhao When set gpio irq type(level and single-edge) for S4, register address is REG_EDGE_POL, not both-edge trigger register. This patch fix it. Fixes: bbd6fcc76b39 ("irqchip: Add support for Amlogic A4 and A5 SoCs") Signed-off-by: Xianwei Zhao --- drivers/irqchip/irq-meson-gpio.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c index f722e9c57e2e..74a376ef452e 100644 --- a/drivers/irqchip/irq-meson-gpio.c +++ b/drivers/irqchip/irq-meson-gpio.c @@ -415,8 +415,7 @@ static int meson_s4_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl, if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)) val |= BIT(ctl->params->edge_single_offset + idx); - meson_gpio_irq_update_bits(ctl, params->edge_pol_reg, - BIT(idx) | BIT(12 + idx), val); + meson_gpio_irq_update_bits(ctl, REG_EDGE_POL, BIT(idx) | BIT(12 + idx), val); return 0; }; -- 2.52.0 From devnull+xianwei.zhao.amlogic.com at kernel.org Fri May 8 00:36:55 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Fri, 08 May 2026 07:36:55 +0000 Subject: [PATCH 2/3] dt-bindings: interrupt-controller: Add support for Amlogic A9 SoCs In-Reply-To: <20260508-a9-gpio-irqchip-v1-0-9dc5f3e022e0@amlogic.com> References: <20260508-a9-gpio-irqchip-v1-0-9dc5f3e022e0@amlogic.com> Message-ID: <20260508-a9-gpio-irqchip-v1-2-9dc5f3e022e0@amlogic.com> From: Xianwei Zhao Update dt-binding document for GPIO interrupt controller of Amlogic A9 SoCs. Signed-off-by: Xianwei Zhao --- .../amlogic,meson-gpio-intc.yaml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.yaml index d0fad930de9d..d26671913e89 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/amlogic,meson-gpio-intc.yaml @@ -38,6 +38,8 @@ properties: - amlogic,a4-gpio-intc - amlogic,a4-gpio-ao-intc - amlogic,a5-gpio-intc + - amlogic,a9-gpio-intc + - amlogic,a9-gpio-ao-intc - amlogic,c3-gpio-intc - amlogic,s6-gpio-intc - amlogic,s7-gpio-intc @@ -56,7 +58,7 @@ properties: amlogic,channel-interrupts: description: Array with the upstream hwirq numbers minItems: 2 - maxItems: 12 + maxItems: 20 $ref: /schemas/types.yaml#/definitions/uint32-array required: @@ -76,9 +78,20 @@ then: amlogic,channel-interrupts: maxItems: 2 else: - properties: - amlogic,channel-interrupts: - minItems: 8 + if: + properties: + compatible: + contains: + const: amlogic,a9-gpio-ao-intc + then: + properties: + amlogic,channel-interrupts: + minItems: 20 + else: + properties: + amlogic,channel-interrupts: + minItems: 8 + maxItems: 12 additionalProperties: false -- 2.52.0 From devnull+xianwei.zhao.amlogic.com at kernel.org Fri May 8 00:36:53 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Fri, 08 May 2026 07:36:53 +0000 Subject: [PATCH 0/3] irqchip: Add GPIO interrupt support for Amlogic A9 Message-ID: <20260508-a9-gpio-irqchip-v1-0-9dc5f3e022e0@amlogic.com> Fix one error about reg address when set irq type for S4. Add string of bindings and driver to support A9 GPIO interrupt. Signed-off-by: Xianwei Zhao --- Xianwei Zhao (3): irqchip/meson-gpio: fix incorrect register address dt-bindings: interrupt-controller: Add support for Amlogic A9 SoCs irqchip/meson-gpio: Add support for Amlogic A9 SoCs .../amlogic,meson-gpio-intc.yaml | 21 ++++-- drivers/irqchip/irq-meson-gpio.c | 78 +++++++++++++++++++++- 2 files changed, 93 insertions(+), 6 deletions(-) --- base-commit: 8b379d5e9eb7933c73e77e768d95f11ef2833c26 change-id: 20260508-a9-gpio-irqchip-d76214ec9c6d Best regards, -- Xianwei Zhao From devnull+xianwei.zhao.amlogic.com at kernel.org Fri May 8 00:36:56 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Fri, 08 May 2026 07:36:56 +0000 Subject: [PATCH 3/3] irqchip/meson-gpio: Add support for Amlogic A9 SoCs In-Reply-To: <20260508-a9-gpio-irqchip-v1-0-9dc5f3e022e0@amlogic.com> References: <20260508-a9-gpio-irqchip-v1-0-9dc5f3e022e0@amlogic.com> Message-ID: <20260508-a9-gpio-irqchip-v1-3-9dc5f3e022e0@amlogic.com> From: Xianwei Zhao The Amlogic A9 SoCs support GPIO interrupt lines: A9 IRQ Number: - 95:86 10 pins on bank Y - 85:84 2 pins on bank CC - 83:64 20 pins on bank A - 63:48 16 pins on bank Z - 47:30 18 pins on bank X - 29:22 8 pins on bank H - 21:14 8 pins on bank M - 13:0 14 pins on bank B A9 AO IRQ Number: - 38 1 pins on bank TESTN - 37:31 7 pins on bank C - 30:13 18 pins on bank D - 12:0 13 pins on bank AO Signed-off-by: Xianwei Zhao --- drivers/irqchip/irq-meson-gpio.c | 75 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c index 74a376ef452e..f68476b2c538 100644 --- a/drivers/irqchip/irq-meson-gpio.c +++ b/drivers/irqchip/irq-meson-gpio.c @@ -27,6 +27,10 @@ /* use for A1 like chips */ #define REG_PIN_A1_SEL 0x04 +/* use for A9 like chips */ +#define REG_A9_AO_POL 0x00 +#define REG_A9_AO_EDGE 0x30 + /* * Note: The S905X3 datasheet reports that BOTH_EDGE is controlled by * bits 24 to 31. Tests on the actual HW show that these bits are @@ -53,6 +57,8 @@ static void meson_a1_gpio_irq_sel_pin(struct meson_gpio_irq_controller *ctl, static void meson_a1_gpio_irq_init(struct meson_gpio_irq_controller *ctl); static int meson8_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl, unsigned int type, u32 *channel_hwirq); +static int meson_a9_ao_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl, + unsigned int type, u32 *channel_hwirq); static int meson_s4_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl, unsigned int type, u32 *channel_hwirq); @@ -116,6 +122,18 @@ struct meson_gpio_irq_params { .pin_sel_mask = 0xff, \ .nr_channels = 2, \ +#define INIT_MESON_A9_AO_COMMON_DATA(irqs) \ + INIT_MESON_COMMON(irqs, meson_a1_gpio_irq_init, \ + meson_a1_gpio_irq_sel_pin, \ + meson_a9_ao_gpio_irq_set_type) \ + .support_edge_both = true, \ + .edge_both_offset = 0, \ + .edge_single_offset = 0, \ + .edge_pol_reg = 0x2c, \ + .pol_low_offset = 0, \ + .pin_sel_mask = 0xff, \ + .nr_channels = 20, \ + #define INIT_MESON_S4_COMMON_DATA(irqs) \ INIT_MESON_COMMON(irqs, meson_a1_gpio_irq_init, \ meson_a1_gpio_irq_sel_pin, \ @@ -170,6 +188,14 @@ static const struct meson_gpio_irq_params a5_params = { INIT_MESON_S4_COMMON_DATA(99) }; +static const struct meson_gpio_irq_params a9_params = { + INIT_MESON_S4_COMMON_DATA(96) +}; + +static const struct meson_gpio_irq_params a9_ao_params = { + INIT_MESON_A9_AO_COMMON_DATA(39) +}; + static const struct meson_gpio_irq_params s4_params = { INIT_MESON_S4_COMMON_DATA(82) }; @@ -203,6 +229,8 @@ static const struct of_device_id meson_irq_gpio_matches[] __maybe_unused = { { .compatible = "amlogic,a4-gpio-ao-intc", .data = &a4_ao_params }, { .compatible = "amlogic,a4-gpio-intc", .data = &a4_params }, { .compatible = "amlogic,a5-gpio-intc", .data = &a5_params }, + { .compatible = "amlogic,a9-gpio-ao-intc", .data = &a9_ao_params }, + { .compatible = "amlogic,a9-gpio-intc", .data = &a9_params }, { .compatible = "amlogic,s6-gpio-intc", .data = &s6_params }, { .compatible = "amlogic,s7-gpio-intc", .data = &s7_params }, { .compatible = "amlogic,s7d-gpio-intc", .data = &s7_params }, @@ -375,6 +403,53 @@ static int meson8_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl, return 0; } +/* + * gpio irq relative registers for a9_ao + * -PADCTRL_GPIO_IRQ_CTRL0 + * bit[31]: enable/disable all the irq lines + * bit[0-19]: polarity trigger + * + * -PADCTRL_GPIO_IRQ_CTRL[X] + * bit[0-5]: 6 bits to choose gpio source for irq line 2*[X] - 2 + * bit[16-21]:6 bits to choose gpio source for irq line 2*[X] - 1 + * where X = 1-10 + * + * -PADCTRL_GPIO_IRQ_CTRL[11] + * bit[0-19]: both edge trigger + * + * -PADCTRL_GPIO_IRQ_CTRL[12] + * bit[0-19]: single edge trigger + */ +static int meson_a9_ao_gpio_irq_set_type(struct meson_gpio_irq_controller *ctl, + unsigned int type, u32 *channel_hwirq) +{ + const struct meson_gpio_irq_params *params = ctl->params; + unsigned int idx; + u32 val = 0; + + idx = meson_gpio_irq_get_channel_idx(ctl, channel_hwirq); + + type &= IRQ_TYPE_SENSE_MASK; + + meson_gpio_irq_update_bits(ctl, params->edge_pol_reg, BIT(idx), 0); + + if (type == IRQ_TYPE_EDGE_BOTH) { + val = BIT(ctl->params->edge_both_offset + idx); + meson_gpio_irq_update_bits(ctl, params->edge_pol_reg, val, val); + return 0; + } + + if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING)) + val = BIT(idx); + meson_gpio_irq_update_bits(ctl, REG_A9_AO_POL, BIT(idx), val); + + val = 0; + if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)) + val = BIT(idx); + meson_gpio_irq_update_bits(ctl, REG_A9_AO_EDGE, BIT(idx), val); + + return 0; +}; /* * gpio irq relative registers for s4 * -PADCTRL_GPIO_IRQ_CTRL0 -- 2.52.0 From conor at kernel.org Fri May 8 08:03:05 2026 From: conor at kernel.org (Conor Dooley) Date: Fri, 8 May 2026 16:03:05 +0100 Subject: [PATCH 2/3] dt-bindings: interrupt-controller: Add support for Amlogic A9 SoCs In-Reply-To: <20260508-a9-gpio-irqchip-v1-2-9dc5f3e022e0@amlogic.com> References: <20260508-a9-gpio-irqchip-v1-0-9dc5f3e022e0@amlogic.com> <20260508-a9-gpio-irqchip-v1-2-9dc5f3e022e0@amlogic.com> Message-ID: <20260508-barn-stoplight-b51074b42891@spud> Acked-by: Conor Dooley pw-bot: not-applicable -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 228 bytes Desc: not available URL: From nicolas at ndufresne.ca Fri May 8 10:58:51 2026 From: nicolas at ndufresne.ca (Nicolas Dufresne) Date: Fri, 08 May 2026 13:58:51 -0400 Subject: [PATCH v2] media: meson: vdec: Fix memory leak in error path of vdec_open In-Reply-To: <20260321065408.209723-1-linux.amoon@gmail.com> References: <20260321065408.209723-1-linux.amoon@gmail.com> Message-ID: <49260ed9ce09b0684ed72787b5635e2c26059297.camel@ndufresne.ca> Hi, sorry I missed your patch, catching up now. Le samedi 21 mars 2026 ? 12:24 +0530, Anand Moon a ?crit?: > The vdec_open and vdec_close functions in the Meson VDEC driver failed > to release several resources, leading to memory leaks and potential > use-after-free scenarios. > > This patch addresses: > - Missing v4l2_ctrl_handler_free() in both the close path and error > ? exit of the open path, preventing control memory leaks. > - A leak of the M2M context if vdec_init_ctrls() failed. > > The error labels in vdec_open() have been reordered to ensure a proper > Last-In-First-Out (LIFO) teardown of all initialized resources. > > This was identified via kmemleak: > unreferenced object 0xffff0000205d6878 (size 8): > ? comm "v4l_id", pid 5289, jiffies 4294938580 > ? hex dump (first 8 bytes): > ??? 40 d2 49 18 00 00 ff ff????????????????????????? @.I..... > ? backtrace (crc d3204599): > ??? kmemleak_alloc+0xc8/0xf0 > ??? __kvmalloc_node_noprof+0x60c/0x850 > ??? v4l2_ctrl_handler_init_class+0x1b4/0x2e8 [videodev] > ??? vdec_open+0x1f4/0x788 [meson_vdec] > ??? v4l2_open+0x144/0x460 [videodev] > ??? chrdev_open+0x1ac/0x500 > ??? do_dentry_open+0x3f0/0xfe8 > ??? vfs_open+0x68/0x320 > ??? do_open+0x2d8/0x9a8 > ??? path_openat+0x1d0/0x4f0 > ??? do_filp_open+0x190/0x380 > ??? do_sys_openat2+0xf8/0x1b0 > ??? __arm64_sys_openat+0x13c/0x1e8 > ??? invoke_syscall+0xdc/0x268 > ??? el0_svc_common.constprop.0+0x178/0x258 > ??? do_el0_svc+0x4c/0x70 > > Cc: Nicolas Dufresne > Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") > Signed-off-by: Anand Moon > --- > v1: https://lore.kernel.org/all/20260304100557.126488-1-linux.amoon at gmail.com/ > ?? tried to address the issue reported by Nicolas > ?? improve the commit message. > --- > ?drivers/staging/media/meson/vdec/vdec.c | 9 ++++++--- > ?1 file changed, 6 insertions(+), 3 deletions(-) > > diff --git a/drivers/staging/media/meson/vdec/vdec.c > b/drivers/staging/media/meson/vdec/vdec.c > index 4b77ec1af5a76..3a5e4ebe0b34c 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c > @@ -877,7 +877,7 @@ static int vdec_open(struct file *file) > ? if (IS_ERR(sess->m2m_dev)) { > ? dev_err(dev, "Fail to v4l2_m2m_init\n"); > ? ret = PTR_ERR(sess->m2m_dev); > - goto err_free_sess; > + goto err_m2m_release; If m2m_dev creation failed, why do you want to call v4l2_m2m_release() ? > ? } > ? > ? sess->m2m_ctx = v4l2_m2m_ctx_init(sess->m2m_dev, sess, > m2m_queue_init); > @@ -889,7 +889,7 @@ static int vdec_open(struct file *file) > ? > ? ret = vdec_init_ctrls(sess); > ? if (ret) > - goto err_m2m_release; > + goto err_m2m_ctx_release; > ? > ? sess->pixfmt_cap = formats[0].pixfmts_cap[0]; > ? sess->fmt_out = &formats[0]; > @@ -913,9 +913,11 @@ static int vdec_open(struct file *file) > ? > ? return 0; > ? > +err_m2m_ctx_release: > + v4l2_m2m_ctx_release(sess->m2m_ctx); > ?err_m2m_release: > ? v4l2_m2m_release(sess->m2m_dev); > -err_free_sess: > + v4l2_ctrl_handler_free(&sess->ctrl_handler); > ? kfree(sess); > ? return ret; > ?} > @@ -926,6 +928,7 @@ static int vdec_close(struct file *file) > ? > ? v4l2_m2m_ctx_release(sess->m2m_ctx); > ? v4l2_m2m_release(sess->m2m_dev); > + v4l2_ctrl_handler_free(&sess->ctrl_handler); > ? v4l2_fh_del(&sess->fh, file); > ? v4l2_fh_exit(&sess->fh); > ? > > base-commit: a0c83177734ab98623795e1ba2cf4b72c23de5e7 -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 228 bytes Desc: This is a digitally signed message part URL: From 18255117159 at 163.com Sat May 9 06:51:50 2026 From: 18255117159 at 163.com (Hans Zhang) Date: Sat, 9 May 2026 21:51:50 +0800 Subject: [PATCH 1/3] PCI: dwc: Add pcie_cap field and helper in designware header In-Reply-To: <20260509135152.2241235-1-18255117159@163.com> References: <20260509135152.2241235-1-18255117159@163.com> Message-ID: <20260509135152.2241235-2-18255117159@163.com> Add a pcie_cap field to struct dw_pcie to store the offset of the PCI Express Capability structure. Provide a helper dw_pcie_get_pcie_cap() which performs the capability search on first call and caches the result. This is a preparatory step for replacing repetitive capability searches in both core and platform drivers. Signed-off-by: Hans Zhang <18255117159 at 163.com> --- drivers/pci/controller/dwc/pcie-designware.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 3e69ef60165b..4baf7eb072eb 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -568,6 +568,8 @@ struct dw_pcie { * use_parent_dt_ranges to true to avoid this warning. */ bool use_parent_dt_ranges; + + u8 pcie_cap; /* PCIe capability offset */ }; #define to_dw_pcie_from_pp(port) container_of((port), struct dw_pcie, pp) @@ -805,6 +807,21 @@ static inline void dw_pcie_dbi_ro_wr_dis(struct dw_pcie *pci) dw_pcie_writel_dbi(pci, reg, val); } +/** + * dw_pcie_get_pcie_cap() - Return cached PCIe Capability offset + * @pci: DWC instance + * + * Finds and caches the offset of PCI_CAP_ID_EXP on first call. + * Returns 0 if the capability is not present. + */ +static inline u8 dw_pcie_get_pcie_cap(struct dw_pcie *pci) +{ + if (!pci->pcie_cap) + pci->pcie_cap = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + + return pci->pcie_cap; +} + static inline int dw_pcie_start_link(struct dw_pcie *pci) { if (pci->ops && pci->ops->start_link) -- 2.34.1 From 18255117159 at 163.com Sat May 9 06:51:51 2026 From: 18255117159 at 163.com (Hans Zhang) Date: Sat, 9 May 2026 21:51:51 +0800 Subject: [PATCH 2/3] PCI: dwc: Use cached PCIe capability offset in core In-Reply-To: <20260509135152.2241235-1-18255117159@163.com> References: <20260509135152.2241235-1-18255117159@163.com> Message-ID: <20260509135152.2241235-3-18255117159@163.com> Modify the DWC core functions to use the cached pcie_cap offset instead of calling dw_pcie_find_capability() each time. Signed-off-by: Hans Zhang <18255117159 at 163.com> --- drivers/pci/controller/dwc/pcie-designware-ep.c | 4 +++- .../pci/controller/dwc/pcie-designware-host.c | 4 +++- drivers/pci/controller/dwc/pcie-designware.c | 16 ++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index d4dc3b24da60..fdcb9012058d 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -1128,7 +1128,7 @@ static void dw_pcie_ep_init_non_sticky_registers(struct dw_pcie *pci) * to all other functions as well. */ if (funcs > 1) { - offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + offset = dw_pcie_get_pcie_cap(pci); func0_lnkcap = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP); func0_lnkcap = FIELD_GET(PCI_EXP_LNKCAP_MLW | PCI_EXP_LNKCAP_SLS, func0_lnkcap); @@ -1390,6 +1390,8 @@ int dw_pcie_ep_init(struct dw_pcie_ep *ep) ep->msi_msg_addr = 0; ep->msi_map_size = 0; + dw_pcie_get_pcie_cap(pci); + epc = devm_pci_epc_create(dev, &epc_ops); if (IS_ERR(epc)) { dev_err(dev, "Failed to create epc device\n"); diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index c9517a348836..7b3ba83ed616 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -575,6 +575,8 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp) raw_spin_lock_init(&pp->lock); + dw_pcie_get_pcie_cap(pci); + bridge = devm_pci_alloc_host_bridge(dev, 0); if (!bridge) return -ENOMEM; @@ -1218,7 +1220,7 @@ static int dw_pcie_pme_turn_off(struct dw_pcie *pci) int dw_pcie_suspend_noirq(struct dw_pcie *pci) { - u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + u8 offset = pci->pcie_cap; int ret = 0; u32 val; diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index c11cf61b8319..db62b93c6255 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -761,7 +761,7 @@ const char *dw_pcie_ltssm_status_string(enum dw_pcie_ltssm ltssm) */ int dw_pcie_wait_for_link(struct dw_pcie *pci) { - u32 offset, val, ltssm; + u32 val, ltssm; int retries; /* Check if the link is up or not */ @@ -807,8 +807,7 @@ int dw_pcie_wait_for_link(struct dw_pcie *pci) if (pci->max_link_speed > 2) msleep(PCIE_RESET_CONFIG_WAIT_MS); - offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); - val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA); + val = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA); dev_info(pci->dev, "PCIe Gen.%u x%u link up\n", FIELD_GET(PCI_EXP_LNKSTA_CLS, val), @@ -844,7 +843,7 @@ EXPORT_SYMBOL_GPL(dw_pcie_upconfig_setup); static void dw_pcie_link_set_max_speed(struct dw_pcie *pci) { u32 cap, ctrl2, link_speed; - u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + u8 offset = pci->pcie_cap; cap = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP); @@ -890,8 +889,7 @@ static void dw_pcie_link_set_max_speed(struct dw_pcie *pci) int dw_pcie_link_get_max_link_width(struct dw_pcie *pci) { - u8 cap = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); - u32 lnkcap = dw_pcie_readl_dbi(pci, cap + PCI_EXP_LNKCAP); + u32 lnkcap = dw_pcie_readl_dbi(pci, pci->pcie_cap + PCI_EXP_LNKCAP); return FIELD_GET(PCI_EXP_LNKCAP_MLW, lnkcap); } @@ -899,7 +897,6 @@ int dw_pcie_link_get_max_link_width(struct dw_pcie *pci) static void dw_pcie_link_set_max_link_width(struct dw_pcie *pci, u32 num_lanes) { u32 lnkcap, lwsc, plc; - u8 cap; if (!num_lanes) return; @@ -936,11 +933,10 @@ static void dw_pcie_link_set_max_link_width(struct dw_pcie *pci, u32 num_lanes) dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, plc); dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, lwsc); - cap = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); - lnkcap = dw_pcie_readl_dbi(pci, cap + PCI_EXP_LNKCAP); + lnkcap = dw_pcie_readl_dbi(pci, pci->pcie_cap + PCI_EXP_LNKCAP); lnkcap &= ~PCI_EXP_LNKCAP_MLW; lnkcap |= FIELD_PREP(PCI_EXP_LNKCAP_MLW, num_lanes); - dw_pcie_writel_dbi(pci, cap + PCI_EXP_LNKCAP, lnkcap); + dw_pcie_writel_dbi(pci, pci->pcie_cap + PCI_EXP_LNKCAP, lnkcap); } void dw_pcie_iatu_detect(struct dw_pcie *pci) -- 2.34.1 From 18255117159 at 163.com Sat May 9 06:51:49 2026 From: 18255117159 at 163.com (Hans Zhang) Date: Sat, 9 May 2026 21:51:49 +0800 Subject: [PATCH 0/3] PCI: dwc: Cache PCIe capability offset and simplify drivers Message-ID: <20260509135152.2241235-1-18255117159@163.com> The DWC PCIe core and its many platform drivers repeatedly call dw_pcie_find_capability(pci, PCI_CAP_ID_EXP) to obtain the offset of the PCI Express Capability structure. This is wasteful and makes the code verbose. And some even search for the PCI_CAP_ID_EXP offset value within the suspend/resume functions. Add a cached pcie_cap field in struct dw_pcie and a helper dw_pcie_get_pcie_cap() to initialize it once at probe time. Then replace all explicit capability searches with the cached value across the entire dwc subtree. Hans Zhang (3): PCI: dwc: Add pcie_cap field and helper in designware header PCI: dwc: Use cached PCIe capability offset in core PCI: dwc: Simplify platform drivers using cached capability offset drivers/pci/controller/dwc/pci-imx6.c | 6 +-- .../pci/controller/dwc/pci-layerscape-ep.c | 4 +- drivers/pci/controller/dwc/pci-meson.c | 4 +- .../pci/controller/dwc/pcie-designware-ep.c | 4 +- .../pci/controller/dwc/pcie-designware-host.c | 4 +- drivers/pci/controller/dwc/pcie-designware.c | 16 +++--- drivers/pci/controller/dwc/pcie-designware.h | 17 +++++++ drivers/pci/controller/dwc/pcie-dw-rockchip.c | 15 +++--- drivers/pci/controller/dwc/pcie-eswin.c | 3 +- drivers/pci/controller/dwc/pcie-fu740.c | 2 +- drivers/pci/controller/dwc/pcie-intel-gw.c | 2 +- drivers/pci/controller/dwc/pcie-qcom-ep.c | 11 ++-- drivers/pci/controller/dwc/pcie-qcom.c | 24 ++++----- drivers/pci/controller/dwc/pcie-sophgo.c | 8 ++- drivers/pci/controller/dwc/pcie-spacemit-k1.c | 5 +- drivers/pci/controller/dwc/pcie-spear13xx.c | 6 +-- drivers/pci/controller/dwc/pcie-tegra194.c | 51 +++++++------------ 17 files changed, 85 insertions(+), 97 deletions(-) base-commit: 70390501d1944d4e5b8f7352be180fceb3a44132 -- 2.34.1 From 18255117159 at 163.com Sat May 9 06:51:52 2026 From: 18255117159 at 163.com (Hans Zhang) Date: Sat, 9 May 2026 21:51:52 +0800 Subject: [PATCH 3/3] PCI: dwc: Simplify platform drivers using cached capability offset In-Reply-To: <20260509135152.2241235-1-18255117159@163.com> References: <20260509135152.2241235-1-18255117159@163.com> Message-ID: <20260509135152.2241235-4-18255117159@163.com> Replace explicit dw_pcie_find_capability(pci, PCI_CAP_ID_EXP) calls with the cached pci->pcie_cap in all DWC-based platform drivers: - i.MX6, Layerscape EP, Meson - Rockchip (also remove redundant NULL check and fix typo) - Eswin, Fu740 - Intel Gateway, Qualcomm EP, Qualcomm RC - Sophgo, Spacemit-k1, Spear13xx - Tegra194 (remove private pcie_cap_base) For drivers that need the offset before the core caches it (e.g., ls_pcie_ep_probe), use dw_pcie_get_pcie_cap() to ensure caching. Adjust variable types from u16 to u8 where appropriate. Signed-off-by: Hans Zhang <18255117159 at 163.com> --- drivers/pci/controller/dwc/pci-imx6.c | 6 +-- .../pci/controller/dwc/pci-layerscape-ep.c | 4 +- drivers/pci/controller/dwc/pci-meson.c | 4 +- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 15 +++--- drivers/pci/controller/dwc/pcie-eswin.c | 3 +- drivers/pci/controller/dwc/pcie-fu740.c | 2 +- drivers/pci/controller/dwc/pcie-intel-gw.c | 2 +- drivers/pci/controller/dwc/pcie-qcom-ep.c | 11 ++-- drivers/pci/controller/dwc/pcie-qcom.c | 24 ++++----- drivers/pci/controller/dwc/pcie-sophgo.c | 8 ++- drivers/pci/controller/dwc/pcie-spacemit-k1.c | 5 +- drivers/pci/controller/dwc/pcie-spear13xx.c | 6 +-- drivers/pci/controller/dwc/pcie-tegra194.c | 51 +++++++------------ 13 files changed, 56 insertions(+), 85 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index e35044cc5218..dc464b460fc1 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -936,10 +936,10 @@ static void imx_pcie_ltssm_enable(struct device *dev) { struct imx_pcie *imx_pcie = dev_get_drvdata(dev); const struct imx_pcie_drvdata *drvdata = imx_pcie->drvdata; - u8 offset = dw_pcie_find_capability(imx_pcie->pci, PCI_CAP_ID_EXP); + struct dw_pcie *pci = imx_pcie->pci; u32 tmp; - tmp = dw_pcie_readl_dbi(imx_pcie->pci, offset + PCI_EXP_LNKCAP); + tmp = dw_pcie_readl_dbi(pci, pci->pcie_cap + PCI_EXP_LNKCAP); phy_set_speed(imx_pcie->phy, FIELD_GET(PCI_EXP_LNKCAP_SLS, tmp)); if (drvdata->ltssm_mask) regmap_update_bits(imx_pcie->iomuxc_gpr, drvdata->ltssm_off, drvdata->ltssm_mask, @@ -965,7 +965,7 @@ static int imx_pcie_start_link(struct dw_pcie *pci) { struct imx_pcie *imx_pcie = to_imx_pcie(pci); struct device *dev = pci->dev; - u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + u8 offset = pci->pcie_cap; u32 tmp; int ret; diff --git a/drivers/pci/controller/dwc/pci-layerscape-ep.c b/drivers/pci/controller/dwc/pci-layerscape-ep.c index 8936975ff104..fdb89ae13e4a 100644 --- a/drivers/pci/controller/dwc/pci-layerscape-ep.c +++ b/drivers/pci/controller/dwc/pci-layerscape-ep.c @@ -84,7 +84,7 @@ static irqreturn_t ls_pcie_ep_event_handler(int irq, void *dev_id) if (val & PEX_PF0_PME_MES_DR_LUD) { - offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + offset = dw_pcie_get_pcie_cap(pci); /* * The values of the Maximum Link Width and Supported Link @@ -266,7 +266,7 @@ static int __init ls_pcie_ep_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pcie); - offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + offset = dw_pcie_get_pcie_cap(pci); pcie->lnkcap = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP); ret = dw_pcie_ep_init(&pci->ep); diff --git a/drivers/pci/controller/dwc/pci-meson.c b/drivers/pci/controller/dwc/pci-meson.c index 0694084f612b..e8750178fbb0 100644 --- a/drivers/pci/controller/dwc/pci-meson.c +++ b/drivers/pci/controller/dwc/pci-meson.c @@ -276,7 +276,7 @@ static void meson_set_max_payload(struct meson_pcie *mp, int size) { struct dw_pcie *pci = &mp->pci; u32 val; - u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + u8 offset = pci->pcie_cap; int max_payload_size = meson_size_to_payload(mp, size); val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_DEVCTL); @@ -292,7 +292,7 @@ static void meson_set_max_rd_req_size(struct meson_pcie *mp, int size) { struct dw_pcie *pci = &mp->pci; u32 val; - u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + u8 offset = pci->pcie_cap; int max_rd_req_size = meson_size_to_payload(mp, size); val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_DEVCTL); diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 731d93663cca..9acdc18a573e 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -366,17 +366,14 @@ static void rockchip_pcie_configure_l1ss(struct dw_pcie *pci) static void rockchip_pcie_enable_l0s(struct dw_pcie *pci) { - u32 cap, lnkcap; + u32 lnkcap; /* Enable L0S capability for all SoCs */ - cap = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); - if (cap) { - lnkcap = dw_pcie_readl_dbi(pci, cap + PCI_EXP_LNKCAP); - lnkcap |= PCI_EXP_LNKCAP_ASPM_L0S; - dw_pcie_dbi_ro_wr_en(pci); - dw_pcie_writel_dbi(pci, cap + PCI_EXP_LNKCAP, lnkcap); - dw_pcie_dbi_ro_wr_dis(pci); - } + lnkcap = dw_pcie_readl_dbi(pci, pci->pcie_cap + PCI_EXP_LNKCAP); + lnkcap |= PCI_EXP_LNKCAP_ASPM_L0S; + dw_pcie_dbi_ro_wr_en(pci); + dw_pcie_writel_dbi(pci, pci->pcie_cap + PCI_EXP_LNKCAP, lnkcap); + dw_pcie_dbi_ro_wr_dis(pci); } static int rockchip_pcie_start_link(struct dw_pcie *pci) diff --git a/drivers/pci/controller/dwc/pcie-eswin.c b/drivers/pci/controller/dwc/pcie-eswin.c index 2845832b3824..2e5b94c27026 100644 --- a/drivers/pci/controller/dwc/pcie-eswin.c +++ b/drivers/pci/controller/dwc/pcie-eswin.c @@ -84,8 +84,7 @@ static int eswin_pcie_start_link(struct dw_pcie *pci) static bool eswin_pcie_link_up(struct dw_pcie *pci) { - u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); - u16 val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA); + u16 val = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA); return val & PCI_EXP_LNKSTA_DLLLA; } diff --git a/drivers/pci/controller/dwc/pcie-fu740.c b/drivers/pci/controller/dwc/pcie-fu740.c index 66367252032b..553a940e6d89 100644 --- a/drivers/pci/controller/dwc/pcie-fu740.c +++ b/drivers/pci/controller/dwc/pcie-fu740.c @@ -179,7 +179,7 @@ static int fu740_pcie_start_link(struct dw_pcie *pci) { struct device *dev = pci->dev; struct fu740_pcie *afp = dev_get_drvdata(dev); - u8 cap_exp = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + u8 cap_exp = pci->pcie_cap; int ret; u32 orig, tmp; diff --git a/drivers/pci/controller/dwc/pcie-intel-gw.c b/drivers/pci/controller/dwc/pcie-intel-gw.c index c21906eced61..939b9dcac7fe 100644 --- a/drivers/pci/controller/dwc/pcie-intel-gw.c +++ b/drivers/pci/controller/dwc/pcie-intel-gw.c @@ -121,7 +121,7 @@ static void intel_pcie_ltssm_disable(struct intel_pcie *pcie) static void intel_pcie_link_setup(struct intel_pcie *pcie) { u32 val; - u8 offset = dw_pcie_find_capability(&pcie->pci, PCI_CAP_ID_EXP); + u8 offset = pcie->pci.pcie_cap; val = pcie_rc_cfg_rd(pcie, offset + PCI_EXP_LNKCTL); diff --git a/drivers/pci/controller/dwc/pcie-qcom-ep.c b/drivers/pci/controller/dwc/pcie-qcom-ep.c index 257c2bcb5f76..d041189be248 100644 --- a/drivers/pci/controller/dwc/pcie-qcom-ep.c +++ b/drivers/pci/controller/dwc/pcie-qcom-ep.c @@ -307,14 +307,14 @@ static void qcom_pcie_dw_write_dbi2(struct dw_pcie *pci, void __iomem *base, static void qcom_pcie_ep_icc_update(struct qcom_pcie_ep *pcie_ep) { struct dw_pcie *pci = &pcie_ep->pci; - u32 offset, status; - int speed, width; - int ret; + int speed, width, ret; + u32 status; + u8 offset; if (!pcie_ep->icc_mem) return; - offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + offset = dw_pcie_get_pcie_cap(pci); status = readw(pci->dbi_base + offset + PCI_EXP_LNKSTA); speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, status); @@ -492,14 +492,13 @@ static int qcom_pcie_perst_deassert(struct dw_pcie *pci) dw_pcie_dbi_ro_wr_en(pci); /* Set the L0s Exit Latency to 2us-4us = 0x6 */ - offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + offset = dw_pcie_get_pcie_cap(pci); val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP); val &= ~PCI_EXP_LNKCAP_L0SEL; val |= FIELD_PREP(PCI_EXP_LNKCAP_L0SEL, 0x6); dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCAP, val); /* Set the L1 Exit Latency to be 32us-64 us = 0x6 */ - offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP); val &= ~PCI_EXP_LNKCAP_L1EL; val |= FIELD_PREP(PCI_EXP_LNKCAP_L1EL, 0x6); diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index af6bf5cce65b..ddb0ae2bf64b 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -333,26 +333,22 @@ static int qcom_pcie_start_link(struct dw_pcie *pci) static void qcom_pcie_clear_aspm_l0s(struct dw_pcie *pci) { struct qcom_pcie *pcie = to_qcom_pcie(pci); - u16 offset; u32 val; if (!pcie->cfg->no_l0s) return; - offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); - dw_pcie_dbi_ro_wr_en(pci); - val = readl(pci->dbi_base + offset + PCI_EXP_LNKCAP); + val = readl(pci->dbi_base + pci->pcie_cap + PCI_EXP_LNKCAP); val &= ~PCI_EXP_LNKCAP_ASPM_L0S; - writel(val, pci->dbi_base + offset + PCI_EXP_LNKCAP); + writel(val, pci->dbi_base + pci->pcie_cap + PCI_EXP_LNKCAP); dw_pcie_dbi_ro_wr_dis(pci); } static void qcom_pcie_set_slot_nccs(struct dw_pcie *pci) { - u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); u32 val; dw_pcie_dbi_ro_wr_en(pci); @@ -362,9 +358,9 @@ static void qcom_pcie_set_slot_nccs(struct dw_pcie *pci) * notifications for the Hot-Plug commands. So set the NCCS field to * avoid waiting for the completions. */ - val = readl(pci->dbi_base + offset + PCI_EXP_SLTCAP); + val = readl(pci->dbi_base + pci->pcie_cap + PCI_EXP_SLTCAP); val |= PCI_EXP_SLTCAP_NCCS; - writel(val, pci->dbi_base + offset + PCI_EXP_SLTCAP); + writel(val, pci->dbi_base + pci->pcie_cap + PCI_EXP_SLTCAP); dw_pcie_dbi_ro_wr_dis(pci); } @@ -900,7 +896,7 @@ static int qcom_pcie_init_2_3_3(struct qcom_pcie *pcie) static int qcom_pcie_post_init_2_3_3(struct qcom_pcie *pcie) { struct dw_pcie *pci = pcie->pci; - u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + u8 offset = pci->pcie_cap; u32 val; val = readl(pcie->parf + PARF_PHY_CTRL); @@ -1209,7 +1205,7 @@ static int qcom_pcie_init_2_9_0(struct qcom_pcie *pcie) static int qcom_pcie_post_init_2_9_0(struct qcom_pcie *pcie) { struct dw_pcie *pci = pcie->pci; - u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + u8 offset = pci->pcie_cap; u32 val; int i; @@ -1254,8 +1250,7 @@ static int qcom_pcie_post_init_2_9_0(struct qcom_pcie *pcie) static bool qcom_pcie_link_up(struct dw_pcie *pci) { - u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); - u16 val = readw(pci->dbi_base + offset + PCI_EXP_LNKSTA); + u16 val = readw(pci->dbi_base + pci->pcie_cap + PCI_EXP_LNKSTA); return val & PCI_EXP_LNKSTA_DLLLA; } @@ -1559,15 +1554,14 @@ static int qcom_pcie_icc_init(struct qcom_pcie *pcie) static void qcom_pcie_icc_opp_update(struct qcom_pcie *pcie) { - u32 offset, status, width, speed; + u32 status, width, speed; struct dw_pcie *pci = pcie->pci; struct dev_pm_opp_key key = {}; unsigned long freq_kbps; struct dev_pm_opp *opp; int ret, freq_mbps; - offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); - status = readw(pci->dbi_base + offset + PCI_EXP_LNKSTA); + status = readw(pci->dbi_base + pci->pcie_cap + PCI_EXP_LNKSTA); /* Only update constraints if link is up. */ if (!(status & PCI_EXP_LNKSTA_DLLLA)) diff --git a/drivers/pci/controller/dwc/pcie-sophgo.c b/drivers/pci/controller/dwc/pcie-sophgo.c index 044088898819..5a2cd95c6de7 100644 --- a/drivers/pci/controller/dwc/pcie-sophgo.c +++ b/drivers/pci/controller/dwc/pcie-sophgo.c @@ -164,15 +164,13 @@ static void sophgo_pcie_msi_enable(struct dw_pcie_rp *pp) static void sophgo_pcie_disable_l0s_l1(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); - u32 offset, val; - - offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + u32 val; dw_pcie_dbi_ro_wr_en(pci); - val = dw_pcie_readl_dbi(pci, PCI_EXP_LNKCAP + offset); + val = dw_pcie_readl_dbi(pci, PCI_EXP_LNKCAP + pci->pcie_cap); val &= ~(PCI_EXP_LNKCAP_ASPM_L0S | PCI_EXP_LNKCAP_ASPM_L1); - dw_pcie_writel_dbi(pci, PCI_EXP_LNKCAP + offset, val); + dw_pcie_writel_dbi(pci, PCI_EXP_LNKCAP + pci->pcie_cap, val); dw_pcie_dbi_ro_wr_dis(pci); } diff --git a/drivers/pci/controller/dwc/pcie-spacemit-k1.c b/drivers/pci/controller/dwc/pcie-spacemit-k1.c index be20a520255b..6f0556336f44 100644 --- a/drivers/pci/controller/dwc/pcie-spacemit-k1.c +++ b/drivers/pci/controller/dwc/pcie-spacemit-k1.c @@ -114,12 +114,9 @@ static void k1_pcie_disable_resources(struct k1_pcie *k1) static void k1_pcie_disable_aspm_l1(struct k1_pcie *k1) { struct dw_pcie *pci = &k1->pci; - u8 offset; + u8 offset = pci->pcie_cap + PCI_EXP_LNKCAP; u32 val; - offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); - offset += PCI_EXP_LNKCAP; - dw_pcie_dbi_ro_wr_en(pci); val = dw_pcie_readl_dbi(pci, offset); val &= ~PCI_EXP_LNKCAP_ASPM_L1; diff --git a/drivers/pci/controller/dwc/pcie-spear13xx.c b/drivers/pci/controller/dwc/pcie-spear13xx.c index 01794a9d3ad2..920454266f3f 100644 --- a/drivers/pci/controller/dwc/pcie-spear13xx.c +++ b/drivers/pci/controller/dwc/pcie-spear13xx.c @@ -122,7 +122,7 @@ static int spear13xx_pcie_host_init(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); struct spear13xx_pcie *spear13xx_pcie = to_spear13xx_pcie(pci); - u32 exp_cap_off = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + u8 offset = pci->pcie_cap; u32 val; spear13xx_pcie->app_base = pci->dbi_base + 0x2000; @@ -132,9 +132,9 @@ static int spear13xx_pcie_host_init(struct dw_pcie_rp *pp) * default value in capability register is 512 bytes. So force * it to 128 here. */ - val = dw_pcie_readw_dbi(pci, exp_cap_off + PCI_EXP_DEVCTL); + val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_DEVCTL); val &= ~PCI_EXP_DEVCTL_READRQ; - dw_pcie_writew_dbi(pci, exp_cap_off + PCI_EXP_DEVCTL, val); + dw_pcie_writew_dbi(pci, offset + PCI_EXP_DEVCTL, val); dw_pcie_writew_dbi(pci, PCI_VENDOR_ID, 0x104A); dw_pcie_writew_dbi(pci, PCI_DEVICE_ID, 0xCD80); diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c index 9dcfa194050e..0d5a5e9027d9 100644 --- a/drivers/pci/controller/dwc/pcie-tegra194.c +++ b/drivers/pci/controller/dwc/pcie-tegra194.c @@ -268,7 +268,6 @@ struct tegra_pcie_dw { u32 num_lanes; u32 cid; u32 ras_des_cap; - u32 pcie_cap_base; u32 aspm_cmrt; u32 aspm_pwr_on_t; u32 aspm_l0s_enter_lat; @@ -312,7 +311,7 @@ static void tegra_pcie_icc_set(struct tegra_pcie_dw *pcie) struct dw_pcie *pci = &pcie->pci; u32 val, speed, width; - val = dw_pcie_readw_dbi(pci, pcie->pcie_cap_base + PCI_EXP_LNKSTA); + val = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA); speed = FIELD_GET(PCI_EXP_LNKSTA_CLS, val); width = FIELD_GET(PCI_EXP_LNKSTA_NLW, val); @@ -340,22 +339,22 @@ static void apply_bad_link_workaround(struct dw_pcie_rp *pp) * stable anyway, not waiting to confirm if link is really * transitioning to Gen-2 speed */ - val = dw_pcie_readw_dbi(pci, pcie->pcie_cap_base + PCI_EXP_LNKSTA); + val = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA); if (val & PCI_EXP_LNKSTA_LBMS) { current_link_width = FIELD_GET(PCI_EXP_LNKSTA_NLW, val); if (pcie->init_link_width > current_link_width) { dev_warn(pci->dev, "PCIe link is bad, width reduced\n"); - val = dw_pcie_readw_dbi(pci, pcie->pcie_cap_base + + val = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKCTL2); val &= ~PCI_EXP_LNKCTL2_TLS; val |= PCI_EXP_LNKCTL2_TLS_2_5GT; - dw_pcie_writew_dbi(pci, pcie->pcie_cap_base + + dw_pcie_writew_dbi(pci, pci->pcie_cap + PCI_EXP_LNKCTL2, val); - val = dw_pcie_readw_dbi(pci, pcie->pcie_cap_base + + val = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKCTL); val |= PCI_EXP_LNKCTL_RL; - dw_pcie_writew_dbi(pci, pcie->pcie_cap_base + + dw_pcie_writew_dbi(pci, pci->pcie_cap + PCI_EXP_LNKCTL, val); } } @@ -399,17 +398,17 @@ static irqreturn_t tegra_pcie_rp_irq_handler(int irq, void *arg) apply_bad_link_workaround(pp); } if (status_l1 & APPL_INTR_STATUS_L1_8_0_BW_MGT_INT_STS) { - val_w = dw_pcie_readw_dbi(pci, pcie->pcie_cap_base + + val_w = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA); val_w |= PCI_EXP_LNKSTA_LBMS; - dw_pcie_writew_dbi(pci, pcie->pcie_cap_base + + dw_pcie_writew_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA, val_w); appl_writel(pcie, APPL_INTR_STATUS_L1_8_0_BW_MGT_INT_STS, APPL_INTR_STATUS_L1_8_0); - val_w = dw_pcie_readw_dbi(pci, pcie->pcie_cap_base + + val_w = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA); dev_dbg(pci->dev, "Link Speed : Gen-%u\n", val_w & PCI_EXP_LNKSTA_CLS); @@ -675,7 +674,7 @@ static void init_host_aspm(struct tegra_pcie_dw *pcie) l1ss = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_L1SS); - pcie->ras_des_cap = dw_pcie_find_ext_capability(&pcie->pci, + pcie->ras_des_cap = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_VNDR); /* Enable ASPM counters */ @@ -766,15 +765,12 @@ static void tegra_pcie_enable_system_interrupts(struct dw_pcie_rp *pp) appl_writel(pcie, val, APPL_INTR_EN_L1_18); } - val_w = dw_pcie_readw_dbi(&pcie->pci, pcie->pcie_cap_base + - PCI_EXP_LNKSTA); + val_w = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA); pcie->init_link_width = FIELD_GET(PCI_EXP_LNKSTA_NLW, val_w); - val_w = dw_pcie_readw_dbi(&pcie->pci, pcie->pcie_cap_base + - PCI_EXP_LNKCTL); + val_w = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKCTL); val_w |= PCI_EXP_LNKCTL_LBMIE; - dw_pcie_writew_dbi(&pcie->pci, pcie->pcie_cap_base + PCI_EXP_LNKCTL, - val_w); + dw_pcie_writew_dbi(pci, pci->pcie_cap + PCI_EXP_LNKCTL, val_w); } static void tegra_pcie_enable_intx_interrupts(struct dw_pcie_rp *pp) @@ -903,10 +899,6 @@ static int tegra_pcie_dw_host_init(struct dw_pcie_rp *pp) pp->bridge->ops = &tegra_pci_ops; - if (!pcie->pcie_cap_base) - pcie->pcie_cap_base = dw_pcie_find_capability(&pcie->pci, - PCI_CAP_ID_EXP); - val = dw_pcie_readl_dbi(pci, PCI_IO_BASE); val &= ~(IO_BASE_IO_DECODE | IO_BASE_IO_DECODE_BIT8); dw_pcie_writel_dbi(pci, PCI_IO_BASE, val); @@ -927,10 +919,9 @@ static int tegra_pcie_dw_host_init(struct dw_pcie_rp *pp) /* Clear Slot Clock Configuration bit if SRNS configuration */ if (pcie->enable_srns) { - val_16 = dw_pcie_readw_dbi(pci, pcie->pcie_cap_base + - PCI_EXP_LNKSTA); + val_16 = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA); val_16 &= ~PCI_EXP_LNKSTA_SLC; - dw_pcie_writew_dbi(pci, pcie->pcie_cap_base + PCI_EXP_LNKSTA, + dw_pcie_writew_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA, val_16); } @@ -1047,8 +1038,7 @@ static int tegra_pcie_dw_start_link(struct dw_pcie *pci) static bool tegra_pcie_dw_link_up(struct dw_pcie *pci) { - struct tegra_pcie_dw *pcie = to_tegra_pcie(pci); - u32 val = dw_pcie_readw_dbi(pci, pcie->pcie_cap_base + PCI_EXP_LNKSTA); + u32 val = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA); return val & PCI_EXP_LNKSTA_DLLLA; } @@ -1878,16 +1868,13 @@ static void pex_ep_event_pex_rst_deassert(struct tegra_pcie_dw *pcie) dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val); } - pcie->pcie_cap_base = dw_pcie_find_capability(&pcie->pci, - PCI_CAP_ID_EXP); + dw_pcie_get_pcie_cap(pci); /* Clear Slot Clock Configuration bit if SRNS configuration */ if (pcie->enable_srns) { - val_16 = dw_pcie_readw_dbi(pci, pcie->pcie_cap_base + - PCI_EXP_LNKSTA); + val_16 = dw_pcie_readw_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA); val_16 &= ~PCI_EXP_LNKSTA_SLC; - dw_pcie_writew_dbi(pci, pcie->pcie_cap_base + PCI_EXP_LNKSTA, - val_16); + dw_pcie_writew_dbi(pci, pci->pcie_cap + PCI_EXP_LNKSTA, val_16); } clk_set_rate(pcie->core_clk, GEN4_CORE_CLK_FREQ); -- 2.34.1 From jonas at kwiboo.se Sun May 10 05:40:45 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:45 +0000 Subject: [PATCH v5 01/21] drm: bridge: dw_hdmi: Disable scrambler feature when not supported In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-2-jonas@kwiboo.se> The scrambler feature can be left enabled when hotplugging from a sink and mode that require scrambling to a sink that does not support SCDC or scrambling. Typically a blank screen or 'no signal' message can be observed after using a HDMI 2.0 4K at 60Hz mode and then hotplugging to a sink that only support HDMI 1.4. Fix this by disabling the scrambler feature when SCDC is not supported. Fixes: 264fce6cc2c1 ("drm/bridge: dw-hdmi: Add SCDC and TMDS Scrambling support") Reported-by: Christopher Obbard Reviewed-by: Neil Armstrong Signed-off-by: Jonas Karlman --- v5: No change v4: No change v3: Collect r-b tag v2: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 41b3a9cfa2f5..d3e6a6562870 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2135,6 +2135,8 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, HDMI_MC_SWRSTZ); drm_scdc_set_scrambling(hdmi->curr_conn, 0); } + } else if (hdmi->version >= 0x200a) { + hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL); } /* Set up horizontal active pixel width */ -- 2.54.0 From jonas at kwiboo.se Sun May 10 05:40:48 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:48 +0000 Subject: [PATCH v5 04/21] drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-5-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 Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sun May 10 05:40:46 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:46 +0000 Subject: [PATCH v5 02/21] drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-3-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 Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sun May 10 05:40:47 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:47 +0000 Subject: [PATCH v5 03/21] drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-4-jonas@kwiboo.se> Change to only call poweron/poweroff from atomic_enable/atomic_disable funcs instead of trying to be clever by keeping a bridge_is_on state and poweron/off in the hotplug irq handler. The bridge is already enabled/disabled depending on connection state with the call to drm_helper_hpd_irq_event() in hotplug irq handler. A benefit of this is that drm mode_config mutex is always held at poweron/off, something that may reduce the need for the dw-hdmi mutex. Reviewed-by: Neil Armstrong Signed-off-by: Jonas Karlman --- v5: No change v4: No change v3: Collect r-b tag v2: Update commit message --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 33 ++--------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index b7bfc0e9a6b2..8f7949d2c7f2 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -171,7 +171,6 @@ struct dw_hdmi { 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 bridge_is_on; /* indicates the bridge is on */ bool rxsense; /* rxsense state */ u8 phy_mask; /* desired phy int mask settings */ u8 mc_clkdis; /* clock disable register */ @@ -2400,8 +2399,6 @@ static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi) static void dw_hdmi_poweron(struct dw_hdmi *hdmi) { - hdmi->bridge_is_on = true; - /* * The curr_conn field is guaranteed to be valid here, as this function * is only be called when !hdmi->disabled. @@ -2415,30 +2412,6 @@ static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) hdmi->phy.ops->disable(hdmi, hdmi->phy.data); hdmi->phy.enabled = false; } - - hdmi->bridge_is_on = false; -} - -static void dw_hdmi_update_power(struct dw_hdmi *hdmi) -{ - int force = hdmi->force; - - if (hdmi->disabled) { - force = DRM_FORCE_OFF; - } else if (force == DRM_FORCE_UNSPECIFIED) { - if (hdmi->rxsense) - force = DRM_FORCE_ON; - else - force = DRM_FORCE_OFF; - } - - if (force == DRM_FORCE_OFF) { - if (hdmi->bridge_is_on) - dw_hdmi_poweroff(hdmi); - } else { - if (!hdmi->bridge_is_on) - dw_hdmi_poweron(hdmi); - } } /* @@ -2563,7 +2536,6 @@ static void dw_hdmi_connector_force(struct drm_connector *connector) mutex_lock(&hdmi->mutex); hdmi->force = connector->force; - dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); mutex_unlock(&hdmi->mutex); } @@ -2988,7 +2960,7 @@ static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, mutex_lock(&hdmi->mutex); hdmi->disabled = true; hdmi->curr_conn = NULL; - dw_hdmi_update_power(hdmi); + dw_hdmi_poweroff(hdmi); dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, false); mutex_unlock(&hdmi->mutex); @@ -3006,7 +2978,7 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, mutex_lock(&hdmi->mutex); hdmi->disabled = false; hdmi->curr_conn = connector; - dw_hdmi_update_power(hdmi); + dw_hdmi_poweron(hdmi); dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, true); mutex_unlock(&hdmi->mutex); @@ -3106,7 +3078,6 @@ void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense) if (hpd) hdmi->rxsense = true; - dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); } mutex_unlock(&hdmi->mutex); -- 2.54.0 From jonas at kwiboo.se Sun May 10 05:40:44 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:44 +0000 Subject: [PATCH v5 00/21] drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup Message-ID: <20260510124111.1226584-1-jonas@kwiboo.se> This is a revival of an old dw-hdmi series and is the first series part of a new effort to upstream old LibreELEC HDMI 2.0 patches for Rockchip RK33xx devices. This series ensure poweron/poweroff and CEC phys addr invalidation is happening under drm mode_config mutex lock, and also ensure EDID is updated after a HPD low voltage pulse by changing to debounce hotplug processing. These changes have mainly been tested on Rockchip RK3328, RK3399 and RK3568 devices using both the dw-hdmi connector and also using a basic convert to use a bridge connector. Testing with a Rock Pi 4 (RK3399) using a Reaspberry Pi Monitor with Linux kms client console using drm.debug=0xe should log something like following: Power cycle monitor using the power button: [CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a [CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz [CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156 [CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 [CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1 [CONNECTOR:68:HDMI-A-1] Same epoch counter 10 Cable unplugged: [CONNECTOR:68:HDMI-A-1] EDID changed, epoch counter 11 [CONNECTOR:68:HDMI-A-1] status updated from connected to disconnected [CONNECTOR:68:HDMI-A-1] Changed epoch counter 10 => 12 [CONNECTOR:68:HDMI-A-1] generating connector hotplug event [CONNECTOR:68:HDMI-A-1] Sent hotplug event Cable connected: [CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a [CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz [CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156 [CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 [CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1 [CONNECTOR:68:HDMI-A-1] status updated from disconnected to connected [CONNECTOR:68:HDMI-A-1] Changed epoch counter 12 => 13 [CONNECTOR:68:HDMI-A-1] generating connector hotplug event [CONNECTOR:68:HDMI-A-1] Sent hotplug event This series has evolved into an initial part of a larger multi series effort to: - drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup [v5] - drm/bridge: dw-hdmi: Improve input/output bus format handling - drm/bridge: dw-hdmi: Convert to a HDMI bridge and use of bridge connector - drm/bridge: dw-hdmi: Add and use tmds_char_rate_valid() plat data ops - drm/meson: hdmi: Misc cleanup and use CEC notifier helpers - phy: rockchip: inno-hdmi: Change TMDS rate handling to configure() ops [v2] - drm/rockchip: dw_hdmi: Misc cleanup and propagate bus format - drm/rockchip: dw_hdmi: Enable YCbCr and Deep Color modes Link to snapshot: https://github.com/Kwiboo/linux-rockchip/commits/next-20260508-rk-hdmi-v3/ Changes in v5: - Add patch that holds a bridge ref until connector cleanup, to fix a use-after-free issue during connector cleanup - Add patch that unregister CEC notifier during connector cleanup - Add patch that adds a common suspend helper - Add patch that drops call to drm_bridge_hpd_notify() - Collect r-b tag - Rebased on next-20260508 Link to v4: https://lore.kernel.org/dri-devel/20260504191059.275928-1-jonas at kwiboo.se/ Changes in v4: - Change to use generic CEC notifier helpers - Disable/mask hpd_work until enable_hpd()/hpd_enable() - Read connector status directly from HW regs in hpd_work - Continued rework of HDP and RXSENSE interrupt handling - Collect r-b tags - Rebased on next-20260430 Link to v3: https://lore.kernel.org/dri-devel/20260403185303.80748-1-jonas at kwiboo.se/ Changes in v3: - Rework EDID refresh handling to closer match bridge connector - Use delayed work to debounce HPD processing - Update commit messages - Collect r-b tags - Rebased on next-20260401 Link to v2: https://lore.kernel.org/dri-devel/20240908132823.3308029-1-jonas at kwiboo.se/ Changes in v2: - Add patch to disable scrambler feature when not supported - Add patch to only notify connected status on HPD interrupt - Update commit messages - Collect r-b tags - Rebased on next-20240906 Link to v1: https://lore.kernel.org/dri-devel/20240611155108.1436502-1-jonas at kwiboo.se/ Jonas Karlman (21): drm: bridge: dw_hdmi: Disable scrambler feature when not supported drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode drm: bridge: dw_hdmi: Fold poweron and setup functions drm: bridge: dw_hdmi: Remove previous_mode and mode_set drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect drm: bridge: dw_hdmi: Remove cec_notifier_mutex drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update() drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio drm: bridge: dw_hdmi: Use generic CEC notifier helpers drm: bridge: dw_hdmi: Add common suspend helper drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense() drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd() drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 5 +- drivers/gpu/drm/bridge/synopsys/Kconfig | 1 + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 483 +++++++------------- drivers/gpu/drm/meson/meson_dw_hdmi.c | 5 +- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 13 +- drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 2 - include/drm/bridge/dw_hdmi.h | 7 +- 7 files changed, 194 insertions(+), 322 deletions(-) -- 2.54.0 From jonas at kwiboo.se Sun May 10 05:40:49 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:49 +0000 Subject: [PATCH v5 05/21] drm: bridge: dw_hdmi: Fold poweron and setup functions In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-6-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 Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sun May 10 05:40:51 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:51 +0000 Subject: [PATCH v5 07/21] drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-8-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 Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sun May 10 05:40:50 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:50 +0000 Subject: [PATCH v5 06/21] drm: bridge: dw_hdmi: Remove previous_mode and mode_set In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-7-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 Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sun May 10 05:40:55 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:55 +0000 Subject: [PATCH v5 11/21] drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update() In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-12-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 Signed-off-by: Jonas Karlman --- v5: No change v4: Collect r-b tag v3: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 30 +++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 0dd4c823c60a..71f90a5f5e8b 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 drm_connector *connector, + enum drm_connector_status status) +{ + struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); + 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) { @@ -2483,20 +2498,9 @@ dw_hdmi_connector_detect(struct drm_connector *connector, bool force) 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; + dw_hdmi_connector_status_update(connector, connector->status); - drm_edid = dw_hdmi_edid_read(hdmi, connector); - - 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 From jonas at kwiboo.se Sun May 10 05:40:56 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:56 +0000 Subject: [PATCH v5 12/21] drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-13-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 Signed-off-by: Jonas Karlman --- 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 | 25 ++++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 71f90a5f5e8b..006eaf3fb8cb 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2473,33 +2473,36 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector); 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(connector, status); return status; } static int dw_hdmi_connector_get_modes(struct drm_connector *connector) { - dw_hdmi_connector_status_update(connector, connector->status); - return drm_edid_connector_add_modes(connector); } @@ -2529,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(connector, connector->status); } static void dw_hdmi_connector_destroy(struct drm_connector *connector) -- 2.54.0 From jonas at kwiboo.se Sun May 10 05:40:58 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:58 +0000 Subject: [PATCH v5 14/21] drm: bridge: dw_hdmi: Use generic CEC notifier helpers In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-15-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. Reviewed-by: Neil Armstrong Signed-off-by: Jonas Karlman --- 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 6fb21399f137..bf893c754539 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -23,12 +23,11 @@ #include #include -#include - #include #include #include +#include #include #include #include @@ -183,8 +182,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; @@ -2453,7 +2450,7 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, 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; } @@ -2462,8 +2459,7 @@ dw_hdmi_connector_status_update(struct drm_connector *connector, 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 @@ -2525,9 +2521,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); } @@ -2550,8 +2543,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) @@ -2587,15 +2578,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 From jonas at kwiboo.se Sun May 10 05:40:59 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:59 +0000 Subject: [PATCH v5 15/21] drm: bridge: dw_hdmi: Add common suspend helper In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-16-jonas@kwiboo.se> Add a dw_hdmi_suspend() helper as the suspend-side counterpart to dw_hdmi_resume() and call it from the i.MX8MP, Amlogic and Rockchip platform PM suspend callbacks. This is a preparatory change to route suspend handling through common dw-hdmi code so follow-up changes can add shared suspend cleanup in one place. Signed-off-by: Jonas Karlman --- v5: New patch --- drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 4 ++++ drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 5 +++++ drivers/gpu/drm/meson/meson_dw_hdmi.c | 2 ++ drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 11 ++++++++++- include/drm/bridge/dw_hdmi.h | 1 + 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c index 8e8cfd66f23b..f15b3f8e1dbc 100644 --- a/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c +++ b/drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c @@ -176,6 +176,10 @@ static void imx8mp_dw_hdmi_remove(struct platform_device *pdev) static int imx8mp_dw_hdmi_pm_suspend(struct device *dev) { + struct imx8mp_hdmi *hdmi = dev_get_drvdata(dev); + + dw_hdmi_suspend(hdmi->dw_hdmi); + return 0; } diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index bf893c754539..a6f2702bca83 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -3568,6 +3568,11 @@ void dw_hdmi_unbind(struct dw_hdmi *hdmi) } EXPORT_SYMBOL_GPL(dw_hdmi_unbind); +void dw_hdmi_suspend(struct dw_hdmi *hdmi) +{ +} +EXPORT_SYMBOL_GPL(dw_hdmi_suspend); + void dw_hdmi_resume(struct dw_hdmi *hdmi) { dw_hdmi_init_hw(hdmi); diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c index fef1702acb14..9ba83bf4787c 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -809,6 +809,8 @@ static int __maybe_unused meson_dw_hdmi_pm_suspend(struct device *dev) if (!meson_dw_hdmi) return 0; + dw_hdmi_suspend(meson_dw_hdmi->hdmi); + /* Reset TOP */ meson_dw_hdmi->data->top_write(meson_dw_hdmi, HDMITX_TOP_SW_RESET, 0); diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 0dc1eb5d2ae3..9677424075eb 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -651,6 +651,15 @@ static void dw_hdmi_rockchip_remove(struct platform_device *pdev) component_del(&pdev->dev, &dw_hdmi_rockchip_ops); } +static int __maybe_unused dw_hdmi_rockchip_suspend(struct device *dev) +{ + struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); + + dw_hdmi_suspend(hdmi->hdmi); + + return 0; +} + static int __maybe_unused dw_hdmi_rockchip_resume(struct device *dev) { struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); @@ -661,7 +670,7 @@ static int __maybe_unused dw_hdmi_rockchip_resume(struct device *dev) } static const struct dev_pm_ops dw_hdmi_rockchip_pm = { - SET_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_rockchip_resume) + SET_SYSTEM_SLEEP_PM_OPS(dw_hdmi_rockchip_suspend, dw_hdmi_rockchip_resume) }; struct platform_driver dw_hdmi_rockchip_pltfm_driver = { diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index 8500dd4f99d8..d587c4076a25 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -184,6 +184,7 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev, struct drm_encoder *encoder, const struct dw_hdmi_plat_data *plat_data); +void dw_hdmi_suspend(struct dw_hdmi *hdmi); void dw_hdmi_resume(struct dw_hdmi *hdmi); void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense); -- 2.54.0 From jonas at kwiboo.se Sun May 10 05:40:52 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:52 +0000 Subject: [PATCH v5 08/21] drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-9-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. Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sun May 10 05:41:05 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:41:05 +0000 Subject: [PATCH v5 21/21] drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-22-jonas@kwiboo.se> The use of calls to both drm_helper_hpd_irq_event() and drm_bridge_hpd_notify() in HPD delayed_work 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. For dw-hdmi connector 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. Update of EDID and CEC phys addr typically is delayed until userspace trigger a modeset and fill_modes()/get_modes() ops is called. 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_hardirq(): EVENT=plugout dw_hdmi_hpd_work() 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() in HPD delayed_work to ensure there is only one hotplug uevent and that EDID and CEC phys addr is updated in a timely manner, independent from userspace having to react the hotplug uevent. 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_hardirq(): EVENT=plugout dw_hdmi_hpd_work() 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 Signed-off-by: Jonas Karlman --- v5: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 2ea8ce5eca36..d9c9d03f8eff 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -3019,14 +3019,28 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) static void dw_hdmi_hpd_work(struct work_struct *work) { struct dw_hdmi *hdmi = container_of(work, struct dw_hdmi, hpd_work.work); - enum drm_connector_status status; if (WARN_ON(!hdmi->bridge.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. Something that also triggers EDID and CEC phys address + * updates. + * + * If we were to instead call drm_bridge_hpd_notify() here, we would + * have to implement a very similar change detection logic or fully + * relay on userspace to react on a hotplug uevent to ensure EDID and + * CEC phys address are updated. + * + * The bridge connector detect() func also ensures that hpd_notify() + * funcs are called for all bridges in the chain. + */ + drm_helper_hpd_irq_event(hdmi->bridge.dev); - status = dw_hdmi_phy_read_hpd(hdmi, hdmi->phy.data); - drm_bridge_hpd_notify(&hdmi->bridge, status); } static const struct dw_hdmi_phy_data dw_hdmi_phys[] = { -- 2.54.0 From jonas at kwiboo.se Sun May 10 05:41:03 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:41:03 +0000 Subject: [PATCH v5 19/21] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd() In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-20-jonas@kwiboo.se> The dw_hdmi_phy_update_hpd() helper is empty and no longer needed after recent RXSENSE and HPD rework, remove it. Signed-off-by: Jonas Karlman --- 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 f15b3f8e1dbc..8948bc2e2abb 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 a388efeb6a87..70068764deaa 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 9677424075eb..b69c142059ea 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 cea6618f097c..aadd89402ad1 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); }; @@ -214,8 +212,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 From jonas at kwiboo.se Sun May 10 05:41:01 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:41:01 +0000 Subject: [PATCH v5 17/21] drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-18-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. Remove RXSENSE handling to simplify the HPD interrupt handling and instead depend on the debounced HPD work to refresh EDID. 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. Signed-off-by: Jonas Karlman --- 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 f32eea9106b0..7fd2de41bc02 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(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); } @@ -3051,78 +3002,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"); @@ -3132,8 +3034,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; } @@ -3324,9 +3225,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 From jonas at kwiboo.se Sun May 10 05:41:02 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:41:02 +0000 Subject: [PATCH v5 18/21] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense() In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-19-jonas@kwiboo.se> The dw_hdmi_setup_rx_sense() helper is empty and no longer needed after recent RXSENSE and HPD rework, remove it. Signed-off-by: Jonas Karlman --- 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 7fd2de41bc02..a388efeb6a87 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -3000,11 +3000,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 9ba83bf4787c..3ad84cebf2c9 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 d587c4076a25..cea6618f097c 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -187,8 +187,6 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev, void dw_hdmi_suspend(struct dw_hdmi *hdmi); 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 From jonas at kwiboo.se Sun May 10 05:40:53 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:53 +0000 Subject: [PATCH v5 09/21] drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-10-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 Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sun May 10 05:40:54 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:54 +0000 Subject: [PATCH v5 10/21] drm: bridge: dw_hdmi: Remove cec_notifier_mutex In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-11-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 Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sun May 10 05:40:57 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:40:57 +0000 Subject: [PATCH v5 13/21] drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-14-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 Reviewed-by: Nicolas Frattaroli Signed-off-by: Jonas Karlman --- 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 006eaf3fb8cb..6fb21399f137 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -46,8 +46,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 @@ -147,8 +145,6 @@ struct dw_hdmi { int vic; - u8 edid[HDMI_EDID_LEN]; - struct { const struct dw_hdmi_phy_ops *ops; const char *name; @@ -158,8 +154,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; @@ -2056,7 +2050,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; @@ -2292,7 +2286,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; @@ -2312,7 +2306,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 */ @@ -2321,7 +2315,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 */ @@ -2435,29 +2429,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 From jonas at kwiboo.se Sun May 10 05:41:04 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:41:04 +0000 Subject: [PATCH v5 20/21] drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-21-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. Signed-off-by: Jonas Karlman --- 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 70068764deaa..2ea8ce5eca36 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2984,30 +2984,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 ? @@ -3019,12 +3007,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) @@ -3324,9 +3313,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 From jonas at kwiboo.se Sun May 10 05:41:00 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sun, 10 May 2026 12:41:00 +0000 Subject: [PATCH v5 16/21] drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: <20260510124111.1226584-17-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 active 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 --- 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 | 60 +++++++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index a6f2702bca83..f32eea9106b0 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(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(&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) @@ -2959,6 +2978,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(&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, @@ -2972,6 +3005,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, }; /* ----------------------------------------------------------------------------- @@ -3092,10 +3127,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); - drm_bridge_hpd_notify(&hdmi->bridge, status); - } + mod_delayed_work(system_percpu_wq, &hdmi->hpd_work, + msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); } hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); @@ -3105,6 +3138,19 @@ 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); + enum drm_connector_status status; + + if (WARN_ON(!hdmi->bridge.dev)) + return; + + drm_helper_hpd_irq_event(hdmi->bridge.dev); + status = dw_hdmi_phy_read_hpd(hdmi, hdmi->phy.data); + drm_bridge_hpd_notify(&hdmi->bridge, status); +} + static const struct dw_hdmi_phy_data dw_hdmi_phys[] = { { .type = DW_HDMI_PHY_DWC_HDMI_TX_PHY, @@ -3389,6 +3435,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); @@ -3521,6 +3570,8 @@ EXPORT_SYMBOL_GPL(dw_hdmi_probe); void dw_hdmi_remove(struct dw_hdmi *hdmi) { + cancel_delayed_work_sync(&hdmi->hpd_work); + drm_bridge_remove(&hdmi->bridge); if (hdmi->audio && !IS_ERR(hdmi->audio)) @@ -3570,6 +3621,7 @@ EXPORT_SYMBOL_GPL(dw_hdmi_unbind); void dw_hdmi_suspend(struct dw_hdmi *hdmi) { + cancel_delayed_work_sync(&hdmi->hpd_work); } EXPORT_SYMBOL_GPL(dw_hdmi_suspend); -- 2.54.0 From patrice.chotard at foss.st.com Mon May 11 00:59:06 2026 From: patrice.chotard at foss.st.com (Patrice CHOTARD) Date: Mon, 11 May 2026 09:59:06 +0200 Subject: [PATCH 08/10] spi: stm32-qspi: Use FIELD_MODIFY() In-Reply-To: <20260430155456.36998-9-18255117159@163.com> References: <20260430155456.36998-1-18255117159@163.com> <20260430155456.36998-9-18255117159@163.com> Message-ID: <283c1eb1-670f-45f3-bd33-dc8f9b0776ed@foss.st.com> On 4/30/26 17:54, Hans Zhang wrote: > Use FIELD_MODIFY() to remove open-coded bit manipulation. > No functional change intended. > > Signed-off-by: Hans Zhang <18255117159 at 163.com> > --- > drivers/spi/spi-stm32-qspi.c | 5 ++--- > 1 file changed, 2 insertions(+), 3 deletions(-) > > diff --git a/drivers/spi/spi-stm32-qspi.c b/drivers/spi/spi-stm32-qspi.c > index df1bbacec90a..ea69fe25686f 100644 > --- a/drivers/spi/spi-stm32-qspi.c > +++ b/drivers/spi/spi-stm32-qspi.c > @@ -374,9 +374,8 @@ static int stm32_qspi_send(struct spi_device *spi, const struct spi_mem_op *op) > int timeout, err = 0, err_poll_status = 0; > > cr = readl_relaxed(qspi->io_base + QSPI_CR); > - cr &= ~CR_PRESC_MASK & ~CR_FSEL; > - cr |= FIELD_PREP(CR_PRESC_MASK, flash->presc); > - cr |= FIELD_PREP(CR_FSEL, flash->cs); > + FIELD_MODIFY(CR_PRESC_MASK, &cr, flash->presc); > + FIELD_MODIFY(CR_FSEL, &cr, flash->cs); > writel_relaxed(cr, qspi->io_base + QSPI_CR); > > if (op->data.nbytes) Hi Hans Reviewed-by: Patrice Chotard Thanks Patrice From patrice.chotard at foss.st.com Mon May 11 01:00:17 2026 From: patrice.chotard at foss.st.com (Patrice CHOTARD) Date: Mon, 11 May 2026 10:00:17 +0200 Subject: [PATCH 07/10] spi: stm32-ospi: Use FIELD_MODIFY() In-Reply-To: <20260430155456.36998-8-18255117159@163.com> References: <20260430155456.36998-1-18255117159@163.com> <20260430155456.36998-8-18255117159@163.com> Message-ID: <4e9c1ede-d42f-4894-a3d1-28e095903f6e@foss.st.com> On 4/30/26 17:54, Hans Zhang wrote: > Use FIELD_MODIFY() to remove open-coded bit manipulation. > No functional change intended. > > Signed-off-by: Hans Zhang <18255117159 at 163.com> > --- > drivers/spi/spi-stm32-ospi.c | 7 +++---- > 1 file changed, 3 insertions(+), 4 deletions(-) > > diff --git a/drivers/spi/spi-stm32-ospi.c b/drivers/spi/spi-stm32-ospi.c > index 4461c6e24b9e..3757f6ba8fc6 100644 > --- a/drivers/spi/spi-stm32-ospi.c > +++ b/drivers/spi/spi-stm32-ospi.c > @@ -470,10 +470,9 @@ static int stm32_ospi_send(struct spi_device *spi, const struct spi_mem_op *op) > u8 cs = spi->chip_select[ffs(spi->cs_index_mask) - 1]; > > cr = readl_relaxed(ospi->regs_base + OSPI_CR); > - cr &= ~CR_CSSEL; > - cr |= FIELD_PREP(CR_CSSEL, cs); > - cr &= ~CR_FMODE_MASK; > - cr |= FIELD_PREP(CR_FMODE_MASK, ospi->fmode); > + FIELD_MODIFY(CR_CSSEL, &cr, cs); > + > + FIELD_MODIFY(CR_FMODE_MASK, &cr, ospi->fmode); > writel_relaxed(cr, regs_base + OSPI_CR); > > if (op->data.nbytes) Hi Hans Reviewed-by: Patrice Chotard Thanks Patrice From devnull+jian.hu.amlogic.com at kernel.org Mon May 11 05:47:22 2026 From: devnull+jian.hu.amlogic.com at kernel.org (Jian Hu via B4 Relay) Date: Mon, 11 May 2026 20:47:22 +0800 Subject: [PATCH 00/10] Add support for A9 family clock controller Message-ID: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> There are 4 clock controllers in A9 SoC: - SCMI clock controller: these clocks are managed by the Trusted Firmware-A(TF-A) and handled through SCMI. - PLL clock controller. - peripheral clock controller. - AO clock controller. There are reserved register regions placed between individual PLLs, so a separate driver is implemented for each PLL, similar to T7. Compared to previous SoCs PLLs, the A9 PLL controller introduces 4 new features: 1.PLL l_detect signal supports active-high configuration. Previous A7 and T7 l_detect signals are active-low. 2.PLL reset signal supports active-low configuration. Previous reset signals are active-high. 3.Support POWER_OF_TWO for the PLL pre-divider N; the N pre-divider follows the same calculation rule as OD. 4.The PLL input path includes an inherent divide-by-2 divider. Implement the first three features in clk-pll.c (verified on A9 and T7), with no impact to PLL logic on existing SoCs. Add a fixed divide-by-2 to A9 PLL driver for the fourth feature. A9 PLL is composed as follows: PLL +---------------------------------+ | | | +--+ | in/2 >>---[ /2^N ]-->| | +-----+ | | | |------| DCO |----->> out | +--------->| | +--v--+ | | | +--+ | | | | | | | +--[ *(M + (F/Fmax) ]<--+ | | | +---------------------------------+ out = in / 2 * (m + frac / frac_max) / 2^n Signed-off-by: Jian Hu --- Jian Hu (10): dt-bindings: clock: Add Amlogic A9 SCMI clock controller dt-bindings: clock: Add Amlogic A9 PLL clock controller dt-bindings: clock: Add Amlogic A9 peripherals clock controller dt-bindings: clock: Add Amlogic A9 AO clock controller clk: amlogic: PLL l_detect signal supports active-high configuration clk: amlogic: PLL reset signal supports active-low configuration clk: amlogic: Support POWER_OF_TWO for PLL pre-divider clk: amlogic: Add A9 PLL clock controller driver clk: amlogic: Add A9 peripherals clock controller driver clk: amlogic: Add A9 AO clock controller driver .../bindings/clock/amlogic,a9-aoclkc.yaml | 76 + .../clock/amlogic,a9-peripherals-clkc.yaml | 150 ++ .../bindings/clock/amlogic,a9-pll-clkc.yaml | 110 + drivers/clk/meson/Kconfig | 28 + drivers/clk/meson/Makefile | 2 + drivers/clk/meson/a9-aoclk.c | 494 +++++ drivers/clk/meson/a9-peripherals.c | 2317 ++++++++++++++++++++ drivers/clk/meson/a9-pll.c | 831 +++++++ drivers/clk/meson/clk-pll.c | 79 +- drivers/clk/meson/clk-pll.h | 6 + include/dt-bindings/clock/amlogic,a9-aoclkc.h | 76 + .../clock/amlogic,a9-peripherals-clkc.h | 352 +++ include/dt-bindings/clock/amlogic,a9-pll-clkc.h | 55 + include/dt-bindings/clock/amlogic,a9-scmi-clkc.h | 51 + 14 files changed, 4609 insertions(+), 18 deletions(-) --- base-commit: ca89c88bcf69daca829044c638a8163d5ce47af0 change-id: 20260511-b4-a9_clk-67652c1ae56e Best regards, -- Jian Hu From devnull+jian.hu.amlogic.com at kernel.org Mon May 11 05:47:25 2026 From: devnull+jian.hu.amlogic.com at kernel.org (Jian Hu via B4 Relay) Date: Mon, 11 May 2026 20:47:25 +0800 Subject: [PATCH 03/10] dt-bindings: clock: Add Amlogic A9 peripherals clock controller In-Reply-To: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> Message-ID: <20260511-b4-a9_clk-v1-3-41cb4071b7c9@amlogic.com> From: Jian Hu Add the peripherals clock controller dt-bindings for the Amlogic A9 SoC family. Signed-off-by: Jian Hu --- .../clock/amlogic,a9-peripherals-clkc.yaml | 150 +++++++++ .../clock/amlogic,a9-peripherals-clkc.h | 352 +++++++++++++++++++++ 2 files changed, 502 insertions(+) diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml new file mode 100644 index 000000000000..97e2c44d8630 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml @@ -0,0 +1,150 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2026 Amlogic, Inc. All rights reserved +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/amlogic,a9-peripherals-clkc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic A9 Series Peripherals Clock Controller + +maintainers: + - Neil Armstrong + - Jerome Brunet + - Jian Hu + - Xianwei Zhao + +properties: + compatible: + const: amlogic,a9-peripherals-clkc + + reg: + maxItems: 1 + + '#clock-cells': + const: 1 + + clocks: + minItems: 20 + items: + - description: input oscillator + - description: input fclk div 2 + - description: input fclk div 3 + - description: input fclk div 4 + - description: input fclk div 5 + - description: input fclk div 7 + - description: input fclk div 2p5 + - description: input sys clk + - description: input gp1 pll + - description: input gp2 pll + - description: input sys pll div 16 + - description: input cpu clk div 16 + - description: input a78 clk div 16 + - description: input dsu clk div 16 + - description: input rtc clk + - description: input gp0 pll + - description: input hifi0 pll + - description: input hifi1 pll + - description: input mclk0 pll + - description: input mclk1 pll + - description: input video1 pll (optional) + - description: input video2 pll (optional) + - description: input hdmi out2 clk (optional) + - description: input hdmi pixel clk (optional) + - description: input pixel0 pll (optional) + - description: input pixel1 pll (optional) + - description: input usb2 drd clk (optional) + - description: external input rmii oscillator (optional) + + clock-names: + minItems: 20 + items: + - const: xtal + - const: fdiv2 + - const: fdiv3 + - const: fdiv4 + - const: fdiv5 + - const: fdiv7 + - const: fdiv2p5 + - const: sys + - const: gp1 + - const: gp2 + - const: sysplldiv16 + - const: cpudiv16 + - const: a78div16 + - const: dsudiv16 + - const: rtc + - const: gp0 + - const: hifi0 + - const: hifi1 + - const: mclk0 + - const: mclk1 + - const: vid1 + - const: vid2 + - const: hdmiout2 + - const: hdmipix + - const: pix0 + - const: pix1 + - const: u2drd + - const: ext_rmii + +required: + - compatible + - reg + - '#clock-cells' + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + apb4 { + #address-cells = <2>; + #size-cells = <2>; + + clock-controller at 200 { + compatible = "amlogic,a9-peripherals-clkc"; + reg = <0x0 0x200 0x0 0x2f8>; + #clock-cells = <1>; + clocks = <&xtal>, + <&scmi_clk 10>, + <&scmi_clk 12>, + <&scmi_clk 14>, + <&scmi_clk 16>, + <&scmi_clk 18>, + <&scmi_clk 20>, + <&scmi_clk 21>, + <&scmi_clk 33>, + <&scmi_clk 34>, + <&scmi_clk 35>, + <&scmi_clk 36>, + <&scmi_clk 37>, + <&scmi_clk 38>, + <&scmi_clk 40>, + <&gp0 3>, + <&hifi0 3>, + <&hifi1 3>, + <&mclk0 3>, + <&mclk1 3>; + clock-names = "xtal", + "fdiv2", + "fdiv3", + "fdiv4", + "fdiv5", + "fdiv7", + "fdiv2p5", + "sys", + "gp1", + "gp2", + "sysplldiv16", + "cpudiv16", + "a78div16", + "dsudiv16", + "rtc", + "gp0", + "hifi0", + "hifi1", + "mclk0", + "mclk1"; + }; + }; diff --git a/include/dt-bindings/clock/amlogic,a9-peripherals-clkc.h b/include/dt-bindings/clock/amlogic,a9-peripherals-clkc.h new file mode 100644 index 000000000000..bca69771d728 --- /dev/null +++ b/include/dt-bindings/clock/amlogic,a9-peripherals-clkc.h @@ -0,0 +1,352 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (C) 2026 Amlogic, Inc. All rights reserved. + */ + +#ifndef __AMLOGIC_A9_PERIPHERALS_CLKC_H +#define __AMLOGIC_A9_PERIPHERALS_CLKC_H + +#define CLKID_SYS_AM_AXI 0 +#define CLKID_SYS_DOS 1 +#define CLKID_SYS_MIPI_DSI 2 +#define CLKID_SYS_ETH_PHY 3 +#define CLKID_SYS_AMFC 4 +#define CLKID_SYS_MALI 5 +#define CLKID_SYS_NNA 6 +#define CLKID_SYS_ETH_AXI 7 +#define CLKID_SYS_DP_APB 8 +#define CLKID_SYS_EDPTX_APB 9 +#define CLKID_SYS_U3HSG 10 +#define CLKID_SYS_AUCPU 11 +#define CLKID_SYS_GLB 12 +#define CLKID_SYS_COMBO_DPHY_APB 13 +#define CLKID_SYS_HDMIRX_APB 14 +#define CLKID_SYS_HDMIRX_PCLK 15 +#define CLKID_SYS_MIPI_DSI_PHY 16 +#define CLKID_SYS_CAN0 17 +#define CLKID_SYS_CAN1 18 +#define CLKID_SYS_SD_EMMC_A 19 +#define CLKID_SYS_SD_EMMC_B 20 +#define CLKID_SYS_SD_EMMC_C 21 +#define CLKID_SYS_SC 22 +#define CLKID_SYS_ACODEC 23 +#define CLKID_SYS_MIPI_ISP 24 +#define CLKID_SYS_MSR 25 +#define CLKID_SYS_AUDIO 26 +#define CLKID_SYS_MIPI_DSI_B 27 +#define CLKID_SYS_MIPI_DSI1_PHY 28 +#define CLKID_SYS_ETH 29 +#define CLKID_SYS_ETH_1G_MAC 30 +#define CLKID_SYS_UART_A 31 +#define CLKID_SYS_UART_F 32 +#define CLKID_SYS_TS_A55 33 +#define CLKID_SYS_ETH_1G_AXI 34 +#define CLKID_SYS_TS_DOS 35 +#define CLKID_SYS_U3DRD_B 36 +#define CLKID_SYS_TS_CORE 37 +#define CLKID_SYS_TS_PLL 38 +#define CLKID_SYS_CSI_DIG_CLKIN 39 +#define CLKID_SYS_CVE 40 +#define CLKID_SYS_GE2D 41 +#define CLKID_SYS_SPISG 42 +#define CLKID_SYS_U3DRD_1 43 +#define CLKID_SYS_U2H 44 +#define CLKID_SYS_PCIE_MAC_A 45 +#define CLKID_SYS_U3DRD_A 46 +#define CLKID_SYS_U2DRD 47 +#define CLKID_SYS_PCIE_PHY 48 +#define CLKID_SYS_PCIE_MAC_B 49 +#define CLKID_SYS_PERIPH 50 +#define CLKID_SYS_PIO 51 +#define CLKID_SYS_I3C 52 +#define CLKID_SYS_I2C_M_E 53 +#define CLKID_SYS_I2C_M_F 54 +#define CLKID_SYS_HDMITX_APB 55 +#define CLKID_SYS_I2C_M_I 56 +#define CLKID_SYS_I2C_M_G 57 +#define CLKID_SYS_I2C_M_H 58 +#define CLKID_SYS_HDMI20_AES 59 +#define CLKID_SYS_CSI2_HOST 60 +#define CLKID_SYS_CSI2_ADAPT 61 +#define CLKID_SYS_DSPA 62 +#define CLKID_SYS_PP_DMA 63 +#define CLKID_SYS_PP_WRAPPER 64 +#define CLKID_SYS_VPU_INTR 65 +#define CLKID_SYS_CSI2_PHY 66 +#define CLKID_SYS_SARADC 67 +#define CLKID_SYS_PWM_J 68 +#define CLKID_SYS_PWM_I 69 +#define CLKID_SYS_PWM_H 70 +#define CLKID_SYS_PWM_N 71 +#define CLKID_SYS_PWM_M 72 +#define CLKID_SYS_PWM_L 73 +#define CLKID_SYS_PWM_K 74 +#define CLKID_SD_EMMC_A_SEL 75 +#define CLKID_SD_EMMC_A_DIV 76 +#define CLKID_SD_EMMC_A 77 +#define CLKID_SD_EMMC_B_SEL 78 +#define CLKID_SD_EMMC_B_DIV 79 +#define CLKID_SD_EMMC_B 80 +#define CLKID_SD_EMMC_C_SEL 81 +#define CLKID_SD_EMMC_C_DIV 82 +#define CLKID_SD_EMMC_C 83 +#define CLKID_PWM_H_SEL 84 +#define CLKID_PWM_H_DIV 85 +#define CLKID_PWM_H 86 +#define CLKID_PWM_I_SEL 87 +#define CLKID_PWM_I_DIV 88 +#define CLKID_PWM_I 89 +#define CLKID_PWM_J_SEL 90 +#define CLKID_PWM_J_DIV 91 +#define CLKID_PWM_J 92 +#define CLKID_PWM_K_SEL 93 +#define CLKID_PWM_K_DIV 94 +#define CLKID_PWM_K 95 +#define CLKID_PWM_L_SEL 96 +#define CLKID_PWM_L_DIV 97 +#define CLKID_PWM_L 98 +#define CLKID_PWM_M_SEL 99 +#define CLKID_PWM_M_DIV 100 +#define CLKID_PWM_M 101 +#define CLKID_PWM_N_SEL 102 +#define CLKID_PWM_N_DIV 103 +#define CLKID_PWM_N 104 +#define CLKID_SPISG_SEL 105 +#define CLKID_SPISG_DIV 106 +#define CLKID_SPISG 107 +#define CLKID_SPISG1_SEL 108 +#define CLKID_SPISG1_DIV 109 +#define CLKID_SPISG1 110 +#define CLKID_SPISG2_SEL 111 +#define CLKID_SPISG2_DIV 112 +#define CLKID_SPISG2 113 +#define CLKID_SARADC_SEL 114 +#define CLKID_SARADC_DIV 115 +#define CLKID_SARADC 116 +#define CLKID_AMFC_SEL 117 +#define CLKID_AMFC_DIV 118 +#define CLKID_AMFC 119 +#define CLKID_NNA_SEL 120 +#define CLKID_NNA_DIV 121 +#define CLKID_NNA 122 +#define CLKID_USB_250M_SEL 123 +#define CLKID_USB_250M_DIV 124 +#define CLKID_USB_250M 125 +#define CLKID_USB_48M_PRE_SEL 126 +#define CLKID_USB_48M_PRE_DIV 127 +#define CLKID_USB_48M_PRE 128 +#define CLKID_PCIE_TL_SEL 129 +#define CLKID_PCIE_TL_DIV 130 +#define CLKID_PCIE_TL 131 +#define CLKID_PCIE1_TL_SEL 132 +#define CLKID_PCIE1_TL_DIV 133 +#define CLKID_PCIE1_TL 134 +#define CLKID_CMPR_SEL 135 +#define CLKID_CMPR_DIV 136 +#define CLKID_CMPR 137 +#define CLKID_DEWARPA_SEL 138 +#define CLKID_DEWARPA_DIV 139 +#define CLKID_DEWARPA 140 +#define CLKID_SC_PRE_SEL 141 +#define CLKID_SC_PRE_DIV 142 +#define CLKID_SC_PRE 143 +#define CLKID_SC 144 +#define CLKID_DPTX_APB2_SEL 145 +#define CLKID_DPTX_APB2_DIV 146 +#define CLKID_DPTX_APB2 147 +#define CLKID_DPTX_AUD_SEL 148 +#define CLKID_DPTX_AUD_DIV 149 +#define CLKID_DPTX_AUD 150 +#define CLKID_ISP_SEL 151 +#define CLKID_ISP_DIV 152 +#define CLKID_ISP 153 +#define CLKID_CVE_SEL 154 +#define CLKID_CVE_DIV 155 +#define CLKID_CVE 156 +#define CLKID_VGE_SEL 157 +#define CLKID_VGE_DIV 158 +#define CLKID_VGE 159 +#define CLKID_PP_SEL 160 +#define CLKID_PP_DIV 161 +#define CLKID_PP 162 +#define CLKID_GLB_SEL 163 +#define CLKID_GLB_DIV 164 +#define CLKID_GLB 165 +#define CLKID_USB_48M_DUALDIV_IN 166 +#define CLKID_USB_48M_DUALDIV_DIV 167 +#define CLKID_USB_48M_DUALDIV_SEL 168 +#define CLKID_USB_48M_DUALDIV 169 +#define CLKID_USB_48M 170 +#define CLKID_CAN_PE_SEL 171 +#define CLKID_CAN_PE_DIV 172 +#define CLKID_CAN_PE 173 +#define CLKID_CAN1_PE_SEL 174 +#define CLKID_CAN1_PE_DIV 175 +#define CLKID_CAN1_PE 176 +#define CLKID_CAN_FILTER_SEL 177 +#define CLKID_CAN_FILTER_DIV 178 +#define CLKID_CAN_FILTER 179 +#define CLKID_CAN1_FILTER_SEL 180 +#define CLKID_CAN1_FILTER_DIV 181 +#define CLKID_CAN1_FILTER 182 +#define CLKID_I3C_SEL 183 +#define CLKID_I3C_DIV 184 +#define CLKID_I3C 185 +#define CLKID_TS_DIV 186 +#define CLKID_TS 187 +#define CLKID_ETH_125M_DIV 188 +#define CLKID_ETH_125M 189 +#define CLKID_ETH_RMII_SEL 190 +#define CLKID_ETH_RMII_DIV 191 +#define CLKID_ETH_RMII 192 +#define CLKID_GEN_SEL 193 +#define CLKID_GEN_DIV 194 +#define CLKID_GEN 195 +#define CLKID_CLK24M_IN 196 +#define CLKID_CLK12_24M 197 +#define CLKID_MALI_0_SEL 198 +#define CLKID_MALI_0_DIV 199 +#define CLKID_MALI_0 200 +#define CLKID_MALI_1_SEL 201 +#define CLKID_MALI_1_DIV 202 +#define CLKID_MALI_1 203 +#define CLKID_MALI 204 +#define CLKID_MALI_STACK_0_SEL 205 +#define CLKID_MALI_STACK_0_DIV 206 +#define CLKID_MALI_STACK_0 207 +#define CLKID_MALI_STACK_1_SEL 208 +#define CLKID_MALI_STACK_1_DIV 209 +#define CLKID_MALI_STACK_1 210 +#define CLKID_MALI_STACK 211 +#define CLKID_DSPA_0_SEL 212 +#define CLKID_DSPA_0_DIV 213 +#define CLKID_DSPA_0 214 +#define CLKID_DSPA_1_SEL 215 +#define CLKID_DSPA_1_DIV 216 +#define CLKID_DSPA_1 217 +#define CLKID_DSPA 218 +#define CLKID_HEVCF_0_SEL 219 +#define CLKID_HEVCF_0_DIV 220 +#define CLKID_HEVCF_0 221 +#define CLKID_HEVCF_1_SEL 222 +#define CLKID_HEVCF_1_DIV 223 +#define CLKID_HEVCF_1 224 +#define CLKID_HEVCF 225 +#define CLKID_HCODEC_0_SEL 226 +#define CLKID_HCODEC_0_DIV 227 +#define CLKID_HCODEC_0 228 +#define CLKID_HCODEC_1_SEL 229 +#define CLKID_HCODEC_1_DIV 230 +#define CLKID_HCODEC_1 231 +#define CLKID_HCODEC 232 +#define CLKID_VPU_0_SEL 233 +#define CLKID_VPU_0_DIV 234 +#define CLKID_VPU_0 235 +#define CLKID_VPU_1_SEL 236 +#define CLKID_VPU_1_DIV 237 +#define CLKID_VPU_1 238 +#define CLKID_VPU 239 +#define CLKID_VAPB_0_SEL 240 +#define CLKID_VAPB_0_DIV 241 +#define CLKID_VAPB_0 242 +#define CLKID_VAPB_1_SEL 243 +#define CLKID_VAPB_1_DIV 244 +#define CLKID_VAPB_1 245 +#define CLKID_VAPB 246 +#define CLKID_GE2D 247 +#define CLKID_VPU_CLKB_TMP_SEL 248 +#define CLKID_VPU_CLKB_TMP_DIV 249 +#define CLKID_VPU_CLKB_TMP 250 +#define CLKID_VPU_CLKB_DIV 251 +#define CLKID_VPU_CLKB 252 +#define CLKID_HDMITX_SYS_SEL 253 +#define CLKID_HDMITX_SYS_DIV 254 +#define CLKID_HDMITX_SYS 255 +#define CLKID_HDMITX_PRIF_SEL 256 +#define CLKID_HDMITX_PRIF_DIV 257 +#define CLKID_HDMITX_PRIF 258 +#define CLKID_HDMITX_200M_SEL 259 +#define CLKID_HDMITX_200M_DIV 260 +#define CLKID_HDMITX_200M 261 +#define CLKID_HDMITX_AUD_SEL 262 +#define CLKID_HDMITX_AUD_DIV 263 +#define CLKID_HDMITX_AUD 264 +#define CLKID_HDMIRX_5M_SEL 265 +#define CLKID_HDMIRX_5M_DIV 266 +#define CLKID_HDMIRX_5M 267 +#define CLKID_HDMIRX_2M_SEL 268 +#define CLKID_HDMIRX_2M_DIV 269 +#define CLKID_HDMIRX_2M 270 +#define CLKID_HDMIRX_CFG_SEL 271 +#define CLKID_HDMIRX_CFG_DIV 272 +#define CLKID_HDMIRX_CFG 273 +#define CLKID_HDMIRX_HDCP2X_SEL 274 +#define CLKID_HDMIRX_HDCP2X_DIV 275 +#define CLKID_HDMIRX_HDCP2X 276 +#define CLKID_HDMIRX_ACR_REF_SEL 277 +#define CLKID_HDMIRX_ACR_REF_DIV 278 +#define CLKID_HDMIRX_ACR_REF 279 +#define CLKID_HDMIRX_METER_SEL 280 +#define CLKID_HDMIRX_METER_DIV 281 +#define CLKID_HDMIRX_METER 282 +#define CLKID_VID_LOCK_SEL 283 +#define CLKID_VID_LOCK_DIV 284 +#define CLKID_VID_LOCK 285 +#define CLKID_VDIN_MEAS_SEL 286 +#define CLKID_VDIN_MEAS_DIV 287 +#define CLKID_VDIN_MEAS 288 +#define CLKID_VID_PLL_DIV 289 +#define CLKID_VID_PLL_SEL 290 +#define CLKID_VID_PLL 291 +#define CLKID_VID_PLL_VCLK 292 +#define CLKID_VCLK_SEL 293 +#define CLKID_VCLK_IN 294 +#define CLKID_VCLK_DIV 295 +#define CLKID_VCLK 296 +#define CLKID_VCLK_DIV1_EN 297 +#define CLKID_VCLK_DIV2_EN 298 +#define CLKID_VCLK_DIV2 299 +#define CLKID_VCLK_DIV4_EN 300 +#define CLKID_VCLK_DIV4 301 +#define CLKID_VCLK_DIV6_EN 302 +#define CLKID_VCLK_DIV6 303 +#define CLKID_VCLK_DIV12_EN 304 +#define CLKID_VCLK_DIV12 305 +#define CLKID_VCLK2_SEL 306 +#define CLKID_VCLK2_IN 307 +#define CLKID_VCLK2_DIV 308 +#define CLKID_VCLK2 309 +#define CLKID_VCLK2_DIV1_EN 310 +#define CLKID_VCLK2_DIV2_EN 311 +#define CLKID_VCLK2_DIV2 312 +#define CLKID_VCLK2_DIV4_EN 313 +#define CLKID_VCLK2_DIV4 314 +#define CLKID_VCLK2_DIV6_EN 315 +#define CLKID_VCLK2_DIV6 316 +#define CLKID_VCLK2_DIV12_EN 317 +#define CLKID_VCLK2_DIV12 318 +#define CLKID_VDAC_SEL 319 +#define CLKID_VDAC 320 +#define CLKID_ENC_SEL 321 +#define CLKID_ENC 322 +#define CLKID_ENC1_SEL 323 +#define CLKID_ENC1 324 +#define CLKID_HDMITX_PIXEL_SEL 325 +#define CLKID_HDMITX_PIXEL 326 +#define CLKID_HDMITX_FE_SEL 327 +#define CLKID_HDMITX_FE 328 +#define CLKID_HDMITX1_PIXEL_SEL 329 +#define CLKID_HDMITX1_PIXEL 330 +#define CLKID_HDMITX1_FE_SEL 331 +#define CLKID_HDMITX1_FE 332 +#define CLKID_CSI_PHY_SEL 333 +#define CLKID_CSI_PHY_DIV 334 +#define CLKID_CSI_PHY 335 +#define CLKID_DSI_MEAS_SEL 336 +#define CLKID_DSI_MEAS_DIV 337 +#define CLKID_DSI_MEAS 338 +#define CLKID_DSI_B_MEAS_SEL 339 +#define CLKID_DSI_B_MEAS_DIV 340 +#define CLKID_DSI_B_MEAS 341 + +#endif /* __AMLOGIC_A9_PERIPHERALS_CLKC_H */ -- 2.47.1 From devnull+jian.hu.amlogic.com at kernel.org Mon May 11 05:47:32 2026 From: devnull+jian.hu.amlogic.com at kernel.org (Jian Hu via B4 Relay) Date: Mon, 11 May 2026 20:47:32 +0800 Subject: [PATCH 10/10] clk: amlogic: Add A9 AO clock controller driver In-Reply-To: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> Message-ID: <20260511-b4-a9_clk-v1-10-41cb4071b7c9@amlogic.com> From: Jian Hu Add the Always-on clock controller driver for the Amlogic A9 SoC family. Signed-off-by: Jian Hu --- drivers/clk/meson/Makefile | 2 +- drivers/clk/meson/a9-aoclk.c | 494 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 495 insertions(+), 1 deletion(-) diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile index 2b5b67b14efc..91af609ce815 100644 --- a/drivers/clk/meson/Makefile +++ b/drivers/clk/meson/Makefile @@ -20,7 +20,7 @@ obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o obj-$(CONFIG_COMMON_CLK_A9_PLL) += a9-pll.o -obj-$(CONFIG_COMMON_CLK_A9_PERIPHERALS) += a9-peripherals.o +obj-$(CONFIG_COMMON_CLK_A9_PERIPHERALS) += a9-peripherals.o a9-aoclk.o obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o diff --git a/drivers/clk/meson/a9-aoclk.c b/drivers/clk/meson/a9-aoclk.c new file mode 100644 index 000000000000..3c42eaf585d2 --- /dev/null +++ b/drivers/clk/meson/a9-aoclk.c @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2026 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include "clk-regmap.h" +#include "clk-dualdiv.h" +#include "meson-clkc-utils.h" + +#define AO_OSCIN_CTRL 0x00 +#define AO_SYS_CLK0 0x04 +#define AO_PWM_CLK_A_CTRL 0x1c +#define AO_PWM_CLK_B_CTRL 0x20 +#define AO_PWM_CLK_C_CTRL 0x24 +#define AO_PWM_CLK_D_CTRL 0x28 +#define AO_PWM_CLK_E_CTRL 0x2c +#define AO_PWM_CLK_F_CTRL 0x30 +#define AO_PWM_CLK_G_CTRL 0x34 +#define AO_CEC_CTRL0 0x38 +#define AO_CEC_CTRL1 0x3c +#define AO_RTC_BY_OSCIN_CTRL0 0x50 +#define AO_RTC_BY_OSCIN_CTRL1 0x54 + +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata) \ + MESON_COMP_SEL(a9_, _name, _reg, _shift, _mask, _pdata, NULL, 0, 0) + +#define A9_COMP_DIV(_name, _reg, _shift, _width) \ + MESON_COMP_DIV(a9_, _name, _reg, _shift, _width, 0, CLK_SET_RATE_PARENT) + +#define A9_COMP_GATE(_name, _reg, _bit) \ + MESON_COMP_GATE(a9_, _name, _reg, _bit, CLK_SET_RATE_PARENT) + +static struct clk_regmap a9_ao_xtal_in = { + .data = &(struct clk_regmap_gate_data){ + .offset = AO_OSCIN_CTRL, + .bit_idx = 3, + }, + .hw.init = &(struct clk_init_data) { + .name = "ao_xtal_in", + .ops = &clk_regmap_gate_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "xtal", + }, + .num_parents = 1, + /* + * It may be ao_sys's parent clock, its child clocks mark + * CLK_IS_CRITICAL, So mark CLK_IS_CRITICAL for it. + */ + .flags = CLK_IS_CRITICAL, + }, +}; + +static struct clk_regmap a9_ao_xtal = { + .data = &(struct clk_regmap_mux_data) { + .offset = AO_OSCIN_CTRL, + .mask = 0x1, + .shift = 0, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_xtal", + .ops = &clk_regmap_mux_ops, + /* ext_32k is from external PAD, do not automatically reparent */ + .parent_data = (const struct clk_parent_data []) { + { .hw = &a9_ao_xtal_in.hw }, + { .fw_name = "ext_32k", }, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_NO_REPARENT, + }, +}; + +static struct clk_regmap a9_ao_sys = { + .data = &(struct clk_regmap_mux_data) { + .offset = AO_OSCIN_CTRL, + .mask = 0x1, + .shift = 1, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_sys", + .ops = &clk_regmap_mux_ops, + .parent_data = (const struct clk_parent_data []) { + { .hw = &a9_ao_xtal.hw }, + { .fw_name = "sys", }, + }, + .num_parents = 2, + .flags = CLK_SET_PARENT_GATE, + }, +}; + +static const struct clk_parent_data a9_ao_pclk_parents = { .hw = &a9_ao_sys.hw }; + +#define A9_AO_PCLK(_name, _bit, _flags) \ + MESON_PCLK(a9_ao_sys_##_name, AO_SYS_CLK0, _bit, \ + &a9_ao_pclk_parents, _flags) + +/* + * A9 integrates a low-power microprocessor (Always-on CPU: AOCPU). Some AO sys + * clocks control the AOCPU modules. Mark the AOCPU-related clocks with + * CLK_IS_CRITICAL to avoid them being disabled and impacting AOCPU functionality. + * AOCPU-related clocks list: + * - clktree + * - rst_ctrl + * - pad + * - irq + * - pwrctrl + * - aocpu + * - sram + */ +static A9_AO_PCLK(i2c3, 0, 0); +static A9_AO_PCLK(rtc_reg, 1, 0); +static A9_AO_PCLK(clktree, 2, CLK_IS_CRITICAL); +static A9_AO_PCLK(rst_ctrl, 3, CLK_IS_CRITICAL); +static A9_AO_PCLK(pad, 4, CLK_IS_CRITICAL); +static A9_AO_PCLK(rtc_dig, 5, 0); +static A9_AO_PCLK(irq, 6, CLK_IS_CRITICAL); +static A9_AO_PCLK(pwrctrl, 7, CLK_IS_CRITICAL); +static A9_AO_PCLK(pwm_a, 8, 0); +static A9_AO_PCLK(pwm_b, 9, 0); +static A9_AO_PCLK(pwm_c, 10, 0); +static A9_AO_PCLK(pwm_d, 11, 0); +static A9_AO_PCLK(pwm_e, 12, 0); +static A9_AO_PCLK(pwm_f, 13, 0); +static A9_AO_PCLK(pwm_g, 14, 0); +static A9_AO_PCLK(i2c_a, 15, 0); +static A9_AO_PCLK(i2c_b, 16, 0); +static A9_AO_PCLK(i2c_c, 17, 0); +static A9_AO_PCLK(i2c_d, 18, 0); +static A9_AO_PCLK(sed, 19, 0); +static A9_AO_PCLK(ir_ctrl, 20, 0); +static A9_AO_PCLK(uart_b, 21, 0); +static A9_AO_PCLK(uart_c, 22, 0); +static A9_AO_PCLK(uart_d, 23, 0); +static A9_AO_PCLK(uart_e, 24, 0); +static A9_AO_PCLK(spisg_0, 25, 0); +static A9_AO_PCLK(rtc_secure, 26, 0); +static A9_AO_PCLK(cec, 27, 0); +static A9_AO_PCLK(aocpu, 28, CLK_IS_CRITICAL); +static A9_AO_PCLK(sram, 29, CLK_IS_CRITICAL); +static A9_AO_PCLK(spisg_1, 30, 0); +static A9_AO_PCLK(spisg_2, 31, 0); + +static const struct clk_parent_data a9_ao_pwm_parents[] = { + { .hw = &a9_ao_xtal.hw }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", } +}; + +static A9_COMP_SEL(ao_pwm_a, AO_PWM_CLK_A_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(ao_pwm_a, AO_PWM_CLK_A_CTRL, 0, 8); +static A9_COMP_GATE(ao_pwm_a, AO_PWM_CLK_A_CTRL, 8); + +static A9_COMP_SEL(ao_pwm_b, AO_PWM_CLK_B_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(ao_pwm_b, AO_PWM_CLK_B_CTRL, 0, 8); +static A9_COMP_GATE(ao_pwm_b, AO_PWM_CLK_A_CTRL, 8); + +static A9_COMP_SEL(ao_pwm_c, AO_PWM_CLK_C_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(ao_pwm_c, AO_PWM_CLK_C_CTRL, 0, 8); +static A9_COMP_GATE(ao_pwm_c, AO_PWM_CLK_C_CTRL, 8); + +static A9_COMP_SEL(ao_pwm_d, AO_PWM_CLK_D_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(ao_pwm_d, AO_PWM_CLK_D_CTRL, 0, 8); +static A9_COMP_GATE(ao_pwm_d, AO_PWM_CLK_D_CTRL, 8); + +static A9_COMP_SEL(ao_pwm_e, AO_PWM_CLK_E_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(ao_pwm_e, AO_PWM_CLK_E_CTRL, 0, 8); +static A9_COMP_GATE(ao_pwm_e, AO_PWM_CLK_E_CTRL, 8); + +static A9_COMP_SEL(ao_pwm_f, AO_PWM_CLK_F_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(ao_pwm_f, AO_PWM_CLK_F_CTRL, 0, 8); +static A9_COMP_GATE(ao_pwm_f, AO_PWM_CLK_F_CTRL, 8); + +static A9_COMP_SEL(ao_pwm_g, AO_PWM_CLK_G_CTRL, 9, 0x7, a9_ao_pwm_parents); +static A9_COMP_DIV(ao_pwm_g, AO_PWM_CLK_G_CTRL, 0, 8); +static A9_COMP_GATE(ao_pwm_g, AO_PWM_CLK_G_CTRL, 8); + +static struct clk_regmap a9_ao_rtc_dualdiv_in = { + .data = &(struct clk_regmap_gate_data){ + .offset = AO_RTC_BY_OSCIN_CTRL0, + .bit_idx = 31, + }, + .hw.init = &(struct clk_init_data) { + .name = "ao_rtc_duandiv_in", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_xtal.hw + }, + .num_parents = 1, + }, +}; + +static const struct meson_clk_dualdiv_param a9_ao_dualdiv_table[] = { + { 733, 732, 8, 11, 1 }, + { /* sentinel */ } +}; + +static struct clk_regmap a9_ao_rtc_dualdiv_div = { + .data = &(struct meson_clk_dualdiv_data){ + .n1 = { + .reg_off = AO_RTC_BY_OSCIN_CTRL0, + .shift = 0, + .width = 12, + }, + .n2 = { + .reg_off = AO_RTC_BY_OSCIN_CTRL0, + .shift = 12, + .width = 12, + }, + .m1 = { + .reg_off = AO_RTC_BY_OSCIN_CTRL1, + .shift = 0, + .width = 12, + }, + .m2 = { + .reg_off = AO_RTC_BY_OSCIN_CTRL1, + .shift = 12, + .width = 12, + }, + .dual = { + .reg_off = AO_RTC_BY_OSCIN_CTRL0, + .shift = 28, + .width = 1, + }, + .table = a9_ao_dualdiv_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "a9_ao_rtc_dualdiv_div", + .ops = &meson_clk_dualdiv_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_rtc_dualdiv_in.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_ao_rtc_dualdiv_sel = { + .data = &(struct clk_regmap_mux_data) { + .offset = AO_RTC_BY_OSCIN_CTRL1, + .mask = 0x1, + .shift = 24, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_rtc_dualdiv_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_rtc_dualdiv_div.hw, + &a9_ao_rtc_dualdiv_in.hw, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_ao_rtc_dualdiv = { + .data = &(struct clk_regmap_gate_data){ + .offset = AO_RTC_BY_OSCIN_CTRL0, + .bit_idx = 30, + }, + .hw.init = &(struct clk_init_data) { + .name = "ao_rtc_dualdiv", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_rtc_dualdiv_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_ao_rtc = { + .data = &(struct clk_regmap_mux_data) { + .offset = AO_RTC_BY_OSCIN_CTRL1, + .mask = 0x1, + .shift = 30, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_rtc", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_xtal.hw, + &a9_ao_rtc_dualdiv.hw, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_ao_cec_dualdiv_in = { + .data = &(struct clk_regmap_gate_data){ + .offset = AO_CEC_CTRL0, + .bit_idx = 31, + }, + .hw.init = &(struct clk_init_data) { + .name = "ao_cec_dualdiv_in", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_xtal.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_ao_cec_dualdiv_div = { + .data = &(struct meson_clk_dualdiv_data){ + .n1 = { + .reg_off = AO_CEC_CTRL0, + .shift = 0, + .width = 12, + }, + .n2 = { + .reg_off = AO_CEC_CTRL0, + .shift = 12, + .width = 12, + }, + .m1 = { + .reg_off = AO_CEC_CTRL1, + .shift = 0, + .width = 12, + }, + .m2 = { + .reg_off = AO_CEC_CTRL1, + .shift = 12, + .width = 12, + }, + .dual = { + .reg_off = AO_CEC_CTRL0, + .shift = 28, + .width = 1, + }, + .table = a9_ao_dualdiv_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_cec_dualdiv_div", + .ops = &meson_clk_dualdiv_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_cec_dualdiv_in.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_ao_cec_dualdiv_sel = { + .data = &(struct clk_regmap_mux_data) { + .offset = AO_CEC_CTRL1, + .mask = 0x1, + .shift = 24, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_cec_dualdiv_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_cec_dualdiv_div.hw, + &a9_ao_cec_dualdiv_in.hw, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_ao_cec_dualdiv = { + .data = &(struct clk_regmap_gate_data){ + .offset = AO_CEC_CTRL0, + .bit_idx = 30, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_cec_dualdiv", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_cec_dualdiv_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_ao_cec = { + .data = &(struct clk_regmap_mux_data) { + .offset = AO_CEC_CTRL1, + .mask = 0x1, + .shift = 30, + }, + .hw.init = &(struct clk_init_data){ + .name = "ao_cec", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ao_cec_dualdiv.hw, + &a9_ao_rtc.hw, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_hw *a9_ao_hw_clks[] = { + [CLKID_AO_XTAL_IN] = &a9_ao_xtal_in.hw, + [CLKID_AO_XTAL] = &a9_ao_xtal.hw, + [CLKID_AO_SYS] = &a9_ao_sys.hw, + [CLKID_AO_SYS_I3C] = &a9_ao_sys_i2c3.hw, + [CLKID_AO_SYS_RTC_REG] = &a9_ao_sys_rtc_reg.hw, + [CLKID_AO_SYS_CLKTREE] = &a9_ao_sys_clktree.hw, + [CLKID_AO_SYS_RST_CTRL] = &a9_ao_sys_rst_ctrl.hw, + [CLKID_AO_SYS_PAD] = &a9_ao_sys_pad.hw, + [CLKID_AO_SYS_RTC_DIG] = &a9_ao_sys_rtc_dig.hw, + [CLKID_AO_SYS_IRQ] = &a9_ao_sys_irq.hw, + [CLKID_AO_SYS_PWRCTRL] = &a9_ao_sys_pwrctrl.hw, + [CLKID_AO_SYS_PWM_A] = &a9_ao_sys_pwm_a.hw, + [CLKID_AO_SYS_PWM_B] = &a9_ao_sys_pwm_b.hw, + [CLKID_AO_SYS_PWM_C] = &a9_ao_sys_pwm_c.hw, + [CLKID_AO_SYS_PWM_D] = &a9_ao_sys_pwm_d.hw, + [CLKID_AO_SYS_PWM_E] = &a9_ao_sys_pwm_e.hw, + [CLKID_AO_SYS_PWM_F] = &a9_ao_sys_pwm_f.hw, + [CLKID_AO_SYS_PWM_G] = &a9_ao_sys_pwm_g.hw, + [CLKID_AO_SYS_I2C_A] = &a9_ao_sys_i2c_a.hw, + [CLKID_AO_SYS_I2C_B] = &a9_ao_sys_i2c_b.hw, + [CLKID_AO_SYS_I2C_C] = &a9_ao_sys_i2c_c.hw, + [CLKID_AO_SYS_I2C_D] = &a9_ao_sys_i2c_d.hw, + [CLKID_AO_SYS_SED] = &a9_ao_sys_sed.hw, + [CLKID_AO_SYS_IR_CTRL] = &a9_ao_sys_ir_ctrl.hw, + [CLKID_AO_SYS_UART_B] = &a9_ao_sys_uart_b.hw, + [CLKID_AO_SYS_UART_C] = &a9_ao_sys_uart_c.hw, + [CLKID_AO_SYS_UART_D] = &a9_ao_sys_uart_d.hw, + [CLKID_AO_SYS_UART_E] = &a9_ao_sys_uart_e.hw, + [CLKID_AO_SYS_SPISG_0] = &a9_ao_sys_spisg_0.hw, + [CLKID_AO_SYS_RTC_SECURE] = &a9_ao_sys_rtc_secure.hw, + [CLKID_AO_SYS_CEC] = &a9_ao_sys_cec.hw, + [CLKID_AO_SYS_AOCPU] = &a9_ao_sys_aocpu.hw, + [CLKID_AO_SYS_SRAM] = &a9_ao_sys_sram.hw, + [CLKID_AO_SYS_SPISG_1] = &a9_ao_sys_spisg_1.hw, + [CLKID_AO_SYS_SPISG_2] = &a9_ao_sys_spisg_2.hw, + [CLKID_AO_PWM_A_SEL] = &a9_ao_pwm_a_sel.hw, + [CLKID_AO_PWM_A_DIV] = &a9_ao_pwm_a_div.hw, + [CLKID_AO_PWM_A] = &a9_ao_pwm_a.hw, + [CLKID_AO_PWM_B_SEL] = &a9_ao_pwm_b_sel.hw, + [CLKID_AO_PWM_B_DIV] = &a9_ao_pwm_b_div.hw, + [CLKID_AO_PWM_B] = &a9_ao_pwm_b.hw, + [CLKID_AO_PWM_C_SEL] = &a9_ao_pwm_c_sel.hw, + [CLKID_AO_PWM_C_DIV] = &a9_ao_pwm_c_div.hw, + [CLKID_AO_PWM_C] = &a9_ao_pwm_c.hw, + [CLKID_AO_PWM_D_SEL] = &a9_ao_pwm_d_sel.hw, + [CLKID_AO_PWM_D_DIV] = &a9_ao_pwm_d_div.hw, + [CLKID_AO_PWM_D] = &a9_ao_pwm_d.hw, + [CLKID_AO_PWM_E_SEL] = &a9_ao_pwm_e_sel.hw, + [CLKID_AO_PWM_E_DIV] = &a9_ao_pwm_e_div.hw, + [CLKID_AO_PWM_E] = &a9_ao_pwm_e.hw, + [CLKID_AO_PWM_F_SEL] = &a9_ao_pwm_f_sel.hw, + [CLKID_AO_PWM_F_DIV] = &a9_ao_pwm_f_div.hw, + [CLKID_AO_PWM_F] = &a9_ao_pwm_f.hw, + [CLKID_AO_PWM_G_SEL] = &a9_ao_pwm_g_sel.hw, + [CLKID_AO_PWM_G_DIV] = &a9_ao_pwm_g_div.hw, + [CLKID_AO_PWM_G] = &a9_ao_pwm_g.hw, + [CLKID_AO_RTC_DUALDIV_IN] = &a9_ao_rtc_dualdiv_in.hw, + [CLKID_AO_RTC_DUALDIV_DIV] = &a9_ao_rtc_dualdiv_div.hw, + [CLKID_AO_RTC_DUALDIV_SEL] = &a9_ao_rtc_dualdiv_sel.hw, + [CLKID_AO_RTC_DUALDIV] = &a9_ao_rtc_dualdiv.hw, + [CLKID_AO_RTC] = &a9_ao_rtc.hw, + [CLKID_AO_CEC_DUALDIV_IN] = &a9_ao_cec_dualdiv_in.hw, + [CLKID_AO_CEC_DUALDIV_DIV] = &a9_ao_cec_dualdiv_div.hw, + [CLKID_AO_CEC_DUALDIV_SEL] = &a9_ao_cec_dualdiv_sel.hw, + [CLKID_AO_CEC_DUALDIV] = &a9_ao_cec_dualdiv.hw, + [CLKID_AO_CEC] = &a9_ao_cec.hw, +}; + +static const struct meson_clkc_data a9_ao_clkc_data = { + .hw_clks = { + .hws = a9_ao_hw_clks, + .num = ARRAY_SIZE(a9_ao_hw_clks), + }, +}; + +static const struct of_device_id a9_ao_clkc_match_table[] = { + { + .compatible = "amlogic,a9-aoclkc", + .data = &a9_ao_clkc_data, + }, + { } +}; +MODULE_DEVICE_TABLE(of, a9_ao_clkc_match_table); + +static struct platform_driver a9_ao_clkc_driver = { + .probe = meson_clkc_mmio_probe, + .driver = { + .name = "a9-aoclkc", + .of_match_table = a9_ao_clkc_match_table, + }, +}; +module_platform_driver(a9_ao_clkc_driver); + +MODULE_DESCRIPTION("Amlogic A9 Always-ON Clock Controller driver"); +MODULE_AUTHOR("Jian Hu "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_MESON"); -- 2.47.1 From devnull+jian.hu.amlogic.com at kernel.org Mon May 11 05:47:28 2026 From: devnull+jian.hu.amlogic.com at kernel.org (Jian Hu via B4 Relay) Date: Mon, 11 May 2026 20:47:28 +0800 Subject: [PATCH 06/10] clk: amlogic: PLL reset signal supports active-low configuration In-Reply-To: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> Message-ID: <20260511-b4-a9_clk-v1-6-41cb4071b7c9@amlogic.com> From: Jian Hu In the A9 design, the PLL reset signal is configured as active-low. Add the flag 'CLK_MESON_PLL_RST_N' to indicate that the PLL reset signal is active-low. Signed-off-by: Jian Hu --- drivers/clk/meson/clk-pll.c | 42 +++++++++++++++++++++++++++++++----------- drivers/clk/meson/clk-pll.h | 2 ++ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c index 5a0bd75f85a9..8568ad6ba7b6 100644 --- a/drivers/clk/meson/clk-pll.c +++ b/drivers/clk/meson/clk-pll.c @@ -295,10 +295,14 @@ static int meson_clk_pll_is_enabled(struct clk_hw *hw) { struct clk_regmap *clk = to_clk_regmap(hw); struct meson_clk_pll_data *pll = meson_clk_pll_data(clk); + unsigned int rst; - if (MESON_PARM_APPLICABLE(&pll->rst) && - meson_parm_read(clk->map, &pll->rst)) - return 0; + if (MESON_PARM_APPLICABLE(&pll->rst)) { + rst = meson_parm_read(clk->map, &pll->rst); + if ((rst && !(pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW)) || + (!rst && (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW))) + return 0; + } if (!meson_parm_read(clk->map, &pll->en) || !meson_parm_read(clk->map, &pll->l)) @@ -326,14 +330,22 @@ static int meson_clk_pll_init(struct clk_hw *hw) return 0; if (pll->init_count) { - if (MESON_PARM_APPLICABLE(&pll->rst)) - meson_parm_write(clk->map, &pll->rst, 1); + if (MESON_PARM_APPLICABLE(&pll->rst)) { + if (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW) + meson_parm_write(clk->map, &pll->rst, 0); + else + meson_parm_write(clk->map, &pll->rst, 1); + } regmap_multi_reg_write(clk->map, pll->init_regs, pll->init_count); - if (MESON_PARM_APPLICABLE(&pll->rst)) - meson_parm_write(clk->map, &pll->rst, 0); + if (MESON_PARM_APPLICABLE(&pll->rst)) { + if (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW) + meson_parm_write(clk->map, &pll->rst, 1); + else + meson_parm_write(clk->map, &pll->rst, 0); + } } return 0; @@ -363,15 +375,23 @@ static int meson_clk_pll_enable(struct clk_hw *hw) return 0; /* Make sure the pll is in reset */ - if (MESON_PARM_APPLICABLE(&pll->rst)) - meson_parm_write(clk->map, &pll->rst, 1); + if (MESON_PARM_APPLICABLE(&pll->rst)) { + if (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW) + meson_parm_write(clk->map, &pll->rst, 0); + else + meson_parm_write(clk->map, &pll->rst, 1); + } /* Enable the pll */ meson_parm_write(clk->map, &pll->en, 1); /* Take the pll out reset */ - if (MESON_PARM_APPLICABLE(&pll->rst)) - meson_parm_write(clk->map, &pll->rst, 0); + if (MESON_PARM_APPLICABLE(&pll->rst)) { + if (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW) + meson_parm_write(clk->map, &pll->rst, 1); + else + meson_parm_write(clk->map, &pll->rst, 0); + } /* * Compared with the previous SoCs, self-adaption current module diff --git a/drivers/clk/meson/clk-pll.h b/drivers/clk/meson/clk-pll.h index 97b7c70376a3..1be7e6e77631 100644 --- a/drivers/clk/meson/clk-pll.h +++ b/drivers/clk/meson/clk-pll.h @@ -31,6 +31,8 @@ struct pll_mult_range { #define CLK_MESON_PLL_NOINIT_ENABLED BIT(1) /* l_detect signal is active-high */ #define CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH BIT(2) +/* rst signal is active-low (Power-on reset) */ +#define CLK_MESON_PLL_RST_ACTIVE_LOW BIT(3) struct meson_clk_pll_data { struct parm en; -- 2.47.1 From devnull+jian.hu.amlogic.com at kernel.org Mon May 11 05:47:24 2026 From: devnull+jian.hu.amlogic.com at kernel.org (Jian Hu via B4 Relay) Date: Mon, 11 May 2026 20:47:24 +0800 Subject: [PATCH 02/10] dt-bindings: clock: Add Amlogic A9 PLL clock controller In-Reply-To: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> Message-ID: <20260511-b4-a9_clk-v1-2-41cb4071b7c9@amlogic.com> From: Jian Hu Add the PLL clock controller dt-bindings for the Amlogic A9 SoC family. Signed-off-by: Jian Hu --- .../bindings/clock/amlogic,a9-pll-clkc.yaml | 110 +++++++++++++++++++++ include/dt-bindings/clock/amlogic,a9-pll-clkc.h | 55 +++++++++++ 2 files changed, 165 insertions(+) diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml new file mode 100644 index 000000000000..4ee6013ba1a1 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml @@ -0,0 +1,110 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2026 Amlogic, Inc. All rights reserved +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/amlogic,a9-pll-clkc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic A9 Series PLL Clock Controller + +maintainers: + - Neil Armstrong + - Jerome Brunet + - Jian Hu + - Xianwei Zhao + +properties: + compatible: + enum: + - amlogic,a9-gp0-pll + - amlogic,a9-hifi0-pll + - amlogic,a9-hifi1-pll + - amlogic,a9-mclk0-pll + - amlogic,a9-mclk1-pll + + reg: + maxItems: 1 + + '#clock-cells': + const: 1 + + clocks: + items: + - description: pll input oscillator gate + - description: fixed input clock source for mclk_sel_0 + - description: u3p2pll input clock source for mclk_sel_0 (optional) + minItems: 1 + + clock-names: + items: + - const: in0 + - const: in1 + - const: in2 + minItems: 1 + +required: + - compatible + - '#clock-cells' + - reg + - clocks + - clock-names + +allOf: + - if: + properties: + compatible: + contains: + enum: + - amlogic,a9-mclk0-pll + - amlogic,a9-mclk1-pll + + then: + properties: + clocks: + maxItems: 3 + + clock-names: + maxItems: 3 + + - if: + properties: + compatible: + contains: + enum: + - amlogic,a9-gp0-pll + - amlogic,a9-hifi0-pll + - amlogic,a9-hifi1-pll + + then: + properties: + clocks: + maxItems: 1 + + clock-names: + maxItems: 1 + +additionalProperties: false + +examples: + - | + apb4 { + #address-cells = <2>; + #size-cells = <2>; + + clock-controller at 8200 { + compatible = "amlogic,a9-gp0-pll"; + reg = <0x0 0x8200 0x0 0x20>; + #clock-cells = <1>; + clocks = <&scmi_clk 0>; + clock-names = "in0"; + }; + + clock-controller at 8330 { + compatible = "amlogic,a9-mclk0-pll"; + reg = <0x0 0x8330 0x0 0x14>; + #clock-cells = <1>; + clocks = <&scmi_clk 4>, + <&scmi_clk 8>; + clock-names = "in0", "in1"; + }; + }; diff --git a/include/dt-bindings/clock/amlogic,a9-pll-clkc.h b/include/dt-bindings/clock/amlogic,a9-pll-clkc.h new file mode 100644 index 000000000000..31edb0bc95e7 --- /dev/null +++ b/include/dt-bindings/clock/amlogic,a9-pll-clkc.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (C) 2026 Amlogic, Inc. All rights reserved. + */ + +#ifndef __AMLOGIC_A9_PLL_CLKC_H +#define __AMLOGIC_A9_PLL_CLKC_H + +/* GP0 */ +#define CLKID_GP0_IN_DIV2_DIV 0 +#define CLKID_GP0_IN_DIV2 1 +#define CLKID_GP0_PLL_DCO 2 +#define CLKID_GP0_PLL 3 + +/* HIFI0 */ +#define CLKID_HIFI0_IN_DIV2_DIV 0 +#define CLKID_HIFI0_IN_DIV2 1 +#define CLKID_HIFI0_PLL_DCO 2 +#define CLKID_HIFI0_PLL 3 + +/* HIFI1 */ +#define CLKID_HIFI1_IN_DIV2_DIV 0 +#define CLKID_HIFI1_IN_DIV2 1 +#define CLKID_HIFI1_PLL_DCO 2 +#define CLKID_HIFI1_PLL 3 + +/* MCLK0 */ +#define CLKID_MCLK0_IN_DIV2 0 +#define CLKID_MCLK0_PLL_DCO 1 +#define CLKID_MCLK0_0_PLL 2 +#define CLKID_MCLK0_0_PRE 3 +#define CLKID_MCLK0_0_SEL 4 +#define CLKID_MCLK0_0_DIV 5 +#define CLKID_MCLK0_0 6 +#define CLKID_MCLK0_1_PLL 7 +#define CLKID_MCLK0_1_PRE 8 +#define CLKID_MCLK0_1_SEL 9 +#define CLKID_MCLK0_1_DIV 10 +#define CLKID_MCLK0_1 11 + +/* MCLK1 */ +#define CLKID_MCLK1_IN_DIV2 0 +#define CLKID_MCLK1_PLL_DCO 1 +#define CLKID_MCLK1_0_PLL 2 +#define CLKID_MCLK1_0_PRE 3 +#define CLKID_MCLK1_0_SEL 4 +#define CLKID_MCLK1_0_DIV 5 +#define CLKID_MCLK1_0 6 +#define CLKID_MCLK1_1_PLL 7 +#define CLKID_MCLK1_1_PRE 8 +#define CLKID_MCLK1_1_SEL 9 +#define CLKID_MCLK1_1_DIV 10 +#define CLKID_MCLK1_1 11 + +#endif /* __AMLOGIC_A9_PLL_CLKC_H */ -- 2.47.1 From devnull+jian.hu.amlogic.com at kernel.org Mon May 11 05:47:23 2026 From: devnull+jian.hu.amlogic.com at kernel.org (Jian Hu via B4 Relay) Date: Mon, 11 May 2026 20:47:23 +0800 Subject: [PATCH 01/10] dt-bindings: clock: Add Amlogic A9 SCMI clock controller In-Reply-To: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> Message-ID: <20260511-b4-a9_clk-v1-1-41cb4071b7c9@amlogic.com> From: Jian Hu Add the SCMI clock controller dt-bindings for the Amlogic A9 SoC family. Signed-off-by: Jian Hu --- include/dt-bindings/clock/amlogic,a9-scmi-clkc.h | 51 ++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/include/dt-bindings/clock/amlogic,a9-scmi-clkc.h b/include/dt-bindings/clock/amlogic,a9-scmi-clkc.h new file mode 100644 index 000000000000..d543db9fe035 --- /dev/null +++ b/include/dt-bindings/clock/amlogic,a9-scmi-clkc.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (C) 2026 Amlogic, Inc. All rights reserved. + */ + +#ifndef __AMLOGIC_A9_SCMI_CLKC_H +#define __AMLOGIC_A9_SCMI_CLKC_H + +#define CLKID_GP0_PLL_OSC 0 +#define CLKID_GP1_PLL_OSC 1 +#define CLKID_HIFI_PLL_OSC 2 +#define CLKID_GP2_PLL_OSC 3 +#define CLKID_MCLK_PLL_OSC 4 +#define CLKID_FIXED_PLL 5 +#define CLKID_FCLK_50M_PREDIV 6 +#define CLKID_FCLK_50M_DIV 7 +#define CLKID_FCLK_50M 8 +#define CLKID_FCLK_DIV2_DIV 9 +#define CLKID_FCLK_DIV2 10 +#define CLKID_FCLK_DIV2P5_DIV 11 +#define CLKID_FCLK_DIV2P5 12 +#define CLKID_FCLK_DIV3_DIV 13 +#define CLKID_FCLK_DIV3 14 +#define CLKID_FCLK_DIV4_DIV 15 +#define CLKID_FCLK_DIV4 16 +#define CLKID_FCLK_DIV5_DIV 17 +#define CLKID_FCLK_DIV5 18 +#define CLKID_FCLK_DIV7_DIV 19 +#define CLKID_FCLK_DIV7 20 +#define CLKID_SYS_CLK 21 +#define CLKID_SYS_AO_SYS 22 +#define CLKID_SYS_MMC_APB 23 +#define CLKID_SYS_CPU_APB 24 +#define CLKID_SYS_GIC 25 +#define CLKID_AXI_CLK 26 +#define CLKID_AXI_SYS_NIC 27 +#define CLKID_AXI_RAMA 28 +#define CLKID_CPU_CLK 29 +#define CLKID_A78_CLK 30 +#define CLKID_DSU_CLK 31 +#define CLKID_ACLKM 32 +#define CLKID_GP1_PLL 33 +#define CLKID_GP2_PLL 34 +#define CLKID_SYS_PLL_DIV16 35 +#define CLKID_CPU_CLK_DIV16 36 +#define CLKID_A78_CLK_DIV16 37 +#define CLKID_DSU_CLK_DIV16 38 +#define CLKID_GIC_CLK 39 +#define CLKID_RTC 40 + +#endif /* __AMLOGIC_A9_SCMI_CLKC_H */ -- 2.47.1 From devnull+jian.hu.amlogic.com at kernel.org Mon May 11 05:47:27 2026 From: devnull+jian.hu.amlogic.com at kernel.org (Jian Hu via B4 Relay) Date: Mon, 11 May 2026 20:47:27 +0800 Subject: [PATCH 05/10] clk: amlogic: PLL l_detect signal supports active-high configuration In-Reply-To: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> Message-ID: <20260511-b4-a9_clk-v1-5-41cb4071b7c9@amlogic.com> From: Jian Hu l_detect controls the enable/disable of the PLL lock-detect module. For A9, the l_detect signal is active-high: 0 -> Disable lock-detect module; 1 -> Enable lock-detect module. Here, a flag CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH is added to handle cases like A9, where the signal is active-high. Signed-off-by: Jian Hu --- drivers/clk/meson/clk-pll.c | 9 +++++++-- drivers/clk/meson/clk-pll.h | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c index 1ea6579a760f..5a0bd75f85a9 100644 --- a/drivers/clk/meson/clk-pll.c +++ b/drivers/clk/meson/clk-pll.c @@ -388,8 +388,13 @@ static int meson_clk_pll_enable(struct clk_hw *hw) } if (MESON_PARM_APPLICABLE(&pll->l_detect)) { - meson_parm_write(clk->map, &pll->l_detect, 1); - meson_parm_write(clk->map, &pll->l_detect, 0); + if (pll->flags & CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH) { + meson_parm_write(clk->map, &pll->l_detect, 0); + meson_parm_write(clk->map, &pll->l_detect, 1); + } else { + meson_parm_write(clk->map, &pll->l_detect, 1); + meson_parm_write(clk->map, &pll->l_detect, 0); + } } if (meson_clk_pll_wait_lock(hw)) diff --git a/drivers/clk/meson/clk-pll.h b/drivers/clk/meson/clk-pll.h index 949157fb7bf5..97b7c70376a3 100644 --- a/drivers/clk/meson/clk-pll.h +++ b/drivers/clk/meson/clk-pll.h @@ -29,6 +29,8 @@ struct pll_mult_range { #define CLK_MESON_PLL_ROUND_CLOSEST BIT(0) #define CLK_MESON_PLL_NOINIT_ENABLED BIT(1) +/* l_detect signal is active-high */ +#define CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH BIT(2) struct meson_clk_pll_data { struct parm en; -- 2.47.1 From devnull+jian.hu.amlogic.com at kernel.org Mon May 11 05:47:26 2026 From: devnull+jian.hu.amlogic.com at kernel.org (Jian Hu via B4 Relay) Date: Mon, 11 May 2026 20:47:26 +0800 Subject: [PATCH 04/10] dt-bindings: clock: Add Amlogic A9 AO clock controller In-Reply-To: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> Message-ID: <20260511-b4-a9_clk-v1-4-41cb4071b7c9@amlogic.com> From: Jian Hu Add the Always-On clock controller dt-bindings for the Amlogic A9 SoC family. Signed-off-by: Jian Hu --- .../bindings/clock/amlogic,a9-aoclkc.yaml | 76 ++++++++++++++++++++++ include/dt-bindings/clock/amlogic,a9-aoclkc.h | 76 ++++++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-aoclkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-aoclkc.yaml new file mode 100644 index 000000000000..973cac3c6988 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-aoclkc.yaml @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2026 Amlogic, Inc. All rights reserved +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/amlogic,a9-aoclkc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic A9 Series Always-On Clock Controller + +maintainers: + - Neil Armstrong + - Jerome Brunet + - Jian Hu + - Xianwei Zhao + +properties: + compatible: + const: amlogic,a9-aoclkc + + reg: + maxItems: 1 + + '#clock-cells': + const: 1 + + clocks: + minItems: 5 + items: + - description: input oscillator + - description: input fclk div 3 + - description: input fclk div 4 + - description: input fclk div 5 + - description: input sys clk + - description: external fixed 32k (optional) + + clock-names: + minItems: 5 + items: + - const: xtal + - const: fdiv3 + - const: fdiv4 + - const: fdiv5 + - const: sys + - const: ext_32k + +required: + - compatible + - reg + - '#clock-cells' + - clocks + - clock-names + +additionalProperties: false + +examples: + - | + aobus { + #address-cells = <2>; + #size-cells = <2>; + + clock-controller at 0 { + compatible = "amlogic,a9-aoclkc"; + reg = <0x0 0x0 0x0 0x58>; + #clock-cells = <1>; + clocks = <&xtal>, + <&scmi_clk 14>, + <&scmi_clk 16>, + <&scmi_clk 18>, + <&scmi_clk 21>; + clock-names = "xtal", + "fdiv3", + "fdiv4", + "fdiv5", + "sys"; + }; + }; diff --git a/include/dt-bindings/clock/amlogic,a9-aoclkc.h b/include/dt-bindings/clock/amlogic,a9-aoclkc.h new file mode 100644 index 000000000000..a7d704d4b58e --- /dev/null +++ b/include/dt-bindings/clock/amlogic,a9-aoclkc.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ +/* + * Copyright (C) 2026 Amlogic, Inc. All rights reserved. + */ + +#ifndef __AMLOGIC_A9_AO_CLKC_H +#define __AMLOGIC_A9_AO_CLKC_H + +#define CLKID_AO_XTAL_IN 0 +#define CLKID_AO_XTAL 1 +#define CLKID_AO_SYS 2 +#define CLKID_AO_SYS_I3C 3 +#define CLKID_AO_SYS_RTC_REG 4 +#define CLKID_AO_SYS_CLKTREE 5 +#define CLKID_AO_SYS_RST_CTRL 6 +#define CLKID_AO_SYS_PAD 7 +#define CLKID_AO_SYS_RTC_DIG 8 +#define CLKID_AO_SYS_IRQ 9 +#define CLKID_AO_SYS_PWRCTRL 10 +#define CLKID_AO_SYS_PWM_A 11 +#define CLKID_AO_SYS_PWM_B 12 +#define CLKID_AO_SYS_PWM_C 13 +#define CLKID_AO_SYS_PWM_D 14 +#define CLKID_AO_SYS_PWM_E 15 +#define CLKID_AO_SYS_PWM_F 16 +#define CLKID_AO_SYS_PWM_G 17 +#define CLKID_AO_SYS_I2C_A 18 +#define CLKID_AO_SYS_I2C_B 19 +#define CLKID_AO_SYS_I2C_C 20 +#define CLKID_AO_SYS_I2C_D 21 +#define CLKID_AO_SYS_SED 22 +#define CLKID_AO_SYS_IR_CTRL 23 +#define CLKID_AO_SYS_UART_B 24 +#define CLKID_AO_SYS_UART_C 25 +#define CLKID_AO_SYS_UART_D 26 +#define CLKID_AO_SYS_UART_E 27 +#define CLKID_AO_SYS_SPISG_0 28 +#define CLKID_AO_SYS_RTC_SECURE 29 +#define CLKID_AO_SYS_CEC 30 +#define CLKID_AO_SYS_AOCPU 31 +#define CLKID_AO_SYS_SRAM 32 +#define CLKID_AO_SYS_SPISG_1 33 +#define CLKID_AO_SYS_SPISG_2 34 +#define CLKID_AO_PWM_A_SEL 35 +#define CLKID_AO_PWM_A_DIV 36 +#define CLKID_AO_PWM_A 37 +#define CLKID_AO_PWM_B_SEL 38 +#define CLKID_AO_PWM_B_DIV 39 +#define CLKID_AO_PWM_B 40 +#define CLKID_AO_PWM_C_SEL 41 +#define CLKID_AO_PWM_C_DIV 42 +#define CLKID_AO_PWM_C 43 +#define CLKID_AO_PWM_D_SEL 44 +#define CLKID_AO_PWM_D_DIV 45 +#define CLKID_AO_PWM_D 46 +#define CLKID_AO_PWM_E_SEL 47 +#define CLKID_AO_PWM_E_DIV 48 +#define CLKID_AO_PWM_E 49 +#define CLKID_AO_PWM_F_SEL 50 +#define CLKID_AO_PWM_F_DIV 51 +#define CLKID_AO_PWM_F 52 +#define CLKID_AO_PWM_G_SEL 53 +#define CLKID_AO_PWM_G_DIV 54 +#define CLKID_AO_PWM_G 55 +#define CLKID_AO_RTC_DUALDIV_IN 56 +#define CLKID_AO_RTC_DUALDIV_DIV 57 +#define CLKID_AO_RTC_DUALDIV_SEL 58 +#define CLKID_AO_RTC_DUALDIV 59 +#define CLKID_AO_RTC 60 +#define CLKID_AO_CEC_DUALDIV_IN 61 +#define CLKID_AO_CEC_DUALDIV_DIV 62 +#define CLKID_AO_CEC_DUALDIV_SEL 63 +#define CLKID_AO_CEC_DUALDIV 64 +#define CLKID_AO_CEC 65 + +#endif /* __AMLOGIC_A9_AO_CLKC_H */ -- 2.47.1 From devnull+jian.hu.amlogic.com at kernel.org Mon May 11 05:47:31 2026 From: devnull+jian.hu.amlogic.com at kernel.org (Jian Hu via B4 Relay) Date: Mon, 11 May 2026 20:47:31 +0800 Subject: [PATCH 09/10] clk: amlogic: Add A9 peripherals clock controller driver In-Reply-To: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> Message-ID: <20260511-b4-a9_clk-v1-9-41cb4071b7c9@amlogic.com> From: Jian Hu Add the peripherals clock controller driver for the Amlogic A9 SoC family. Signed-off-by: Jian Hu --- drivers/clk/meson/Kconfig | 15 + drivers/clk/meson/Makefile | 1 + drivers/clk/meson/a9-peripherals.c | 2317 ++++++++++++++++++++++++++++++++++++ 3 files changed, 2333 insertions(+) diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig index 3549e67d6988..48a15a5e1323 100644 --- a/drivers/clk/meson/Kconfig +++ b/drivers/clk/meson/Kconfig @@ -145,6 +145,21 @@ config COMMON_CLK_A9_PLL device, AKA A9. PLLs are required by most peripheral to operate. Say Y if you want A9 PLL clock controller to work. +config COMMON_CLK_A9_PERIPHERALS + tristate "Amlogic A9 SoC peripherals clock controller support" + depends on ARM64 + default ARCH_MESON + select COMMON_CLK_MESON_REGMAP + select COMMON_CLK_MESON_CLKC_UTILS + select COMMON_CLK_MESON_DUALDIV + select COMMON_CLK_MESON_VID_PLL_DIV + imply COMMON_CLK_SCMI + imply COMMON_CLK_A9_PLL + help + Support for the peripherals clock controller on Amlogic A311Y3 based + device, AKA A9. Peripherals are required by most peripheral to operate. + Say Y if you want A9 peripherals clock controller to work. + config COMMON_CLK_C3_PLL tristate "Amlogic C3 PLL clock controller" depends on ARM64 diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile index 77636033061f..2b5b67b14efc 100644 --- a/drivers/clk/meson/Makefile +++ b/drivers/clk/meson/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o obj-$(CONFIG_COMMON_CLK_A9_PLL) += a9-pll.o +obj-$(CONFIG_COMMON_CLK_A9_PERIPHERALS) += a9-peripherals.o obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o diff --git a/drivers/clk/meson/a9-peripherals.c b/drivers/clk/meson/a9-peripherals.c new file mode 100644 index 000000000000..338a91c473ea --- /dev/null +++ b/drivers/clk/meson/a9-peripherals.c @@ -0,0 +1,2317 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2026 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include "clk-regmap.h" +#include "clk-dualdiv.h" +#include "vid-pll-div.h" +#include "meson-clkc-utils.h" + +#define SYS_CLK_EN0_REG0 0x30 +#define SYS_CLK_EN0_REG1 0x34 +#define SYS_CLK_EN0_REG2 0x38 +#define SYS_CLK_EN0_REG3 0x3c +#define SD_EMMC_CLK_CTRL0 0x90 +#define SD_EMMC_CLK_CTRL1 0x94 +#define PWM_CLK_H_CTRL 0xbc +#define PWM_CLK_I_CTRL 0xc0 +#define PWM_CLK_J_CTRL 0xc4 +#define PWM_CLK_K_CTRL 0xc8 +#define PWM_CLK_L_CTRL 0xcc +#define PWM_CLK_M_CTRL 0xd0 +#define PWM_CLK_N_CTRL 0xd4 +#define SPISG_CLK_CTRL 0x100 +#define SPISG_CLK_CTRL1 0x104 +#define SAR_CLK_CTRL 0x150 +#define AMFC_CLK_CTRL 0x154 +#define NNA_CLK_CTRL 0x15c +#define USB_CLK_CTRL 0x160 +#define PCIE_TL_CLK_CTRL 0x164 +#define CMPR_CLK_CTRL 0x168 +#define DEWARP_CLK_CTRL 0x16c +#define SC_CLK_CTRL 0x170 +#define DPTX_CLK_CTRL 0x178 +#define ISP_CLK_CTRL 0x17c +#define CVE_CLK_CTRL 0x180 +#define PP_CLK_CTRL 0x184 +#define GLB_CLK_CTRL 0x188 +#define USB_CLK_CTRL0 0x18c +#define USB_CLK_CTRL1 0x190 +#define CAN_CLK_CTRL 0x194 +#define CAN_CLK_CTRL1 0x198 +#define I3C_CLK_CTRL 0x19c +#define TS_CLK_CTRL 0x1a0 +#define ETH_CLK_CTRL 0x1a4 +#define GEN_CLK_CTRL 0x1a8 +#define CLK12_24_CTRL 0x1ac +#define MALI_CLK_CTRL 0x200 +#define MALI_STACK_CLK_CTRL 0x204 +#define DSPA_CLK_CTRL 0x220 +#define HEVCF_CLK_CTRL 0x240 +#define HCODEC_CLK_CTRL 0x244 +#define VPU_CLK_CTRL 0x260 +#define VAPB_CLK_CTRL 0x268 +#define VPU_CLKB_CTRL 0x280 +#define HDMI_CLK_CTRL 0x284 +#define HTX_CLK_CTRL 0x28c +#define HTX_CLK_CTRL1 0x290 +#define HRX_CLK_CTRL 0x294 +#define HRX_CLK_CTRL1 0x298 +#define HRX_CLK_CTRL2 0x29c +#define HRX_CLK_CTRL3 0x2a0 +#define VID_LOCK_CLK_CTRL 0x2a4 +#define VDIN_MEAS_CLK_CTRL 0x2a8 +#define VID_PLL_CLK_DIV 0x2b0 +#define VID_CLK_CTRL 0x2c0 +#define VID_CLK_CTRL2 0x2c4 +#define VID_CLK_DIV 0x2c8 +#define VIID_CLK_DIV 0x2cc +#define VIID_CLK_CTRL 0x2d0 +#define MIPI_CSI_PHY_CLK_CTRL 0x2e0 +#define DSI_MEAS_CLK_CTRL 0x2f4 + +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata, _table) \ + MESON_COMP_SEL(a9_, _name, _reg, _shift, _mask, _pdata, _table, 0, 0) + +#define A9_COMP_DIV(_name, _reg, _shift, _width) \ + MESON_COMP_DIV(a9_, _name, _reg, _shift, _width, 0, CLK_SET_RATE_PARENT) + +#define A9_COMP_GATE(_name, _reg, _bit, _iflags) \ + MESON_COMP_GATE(a9_, _name, _reg, _bit, CLK_SET_RATE_PARENT | (_iflags)) + +static const struct clk_parent_data a9_sys_pclk_parents = { .fw_name = "sys" }; + +#define A9_SYS_PCLK(_name, _reg, _bit) \ + MESON_PCLK(a9_##_name, _reg, _bit, &a9_sys_pclk_parents, 0) + +static A9_SYS_PCLK(sys_am_axi, SYS_CLK_EN0_REG0, 0); +static A9_SYS_PCLK(sys_dos, SYS_CLK_EN0_REG0, 1); +static A9_SYS_PCLK(sys_mipi_dsi, SYS_CLK_EN0_REG0, 3); +static A9_SYS_PCLK(sys_eth_phy, SYS_CLK_EN0_REG0, 4); +static A9_SYS_PCLK(sys_amfc, SYS_CLK_EN0_REG0, 5); +static A9_SYS_PCLK(sys_mali, SYS_CLK_EN0_REG0, 6); +static A9_SYS_PCLK(sys_nna, SYS_CLK_EN0_REG0, 7); +static A9_SYS_PCLK(sys_eth_axi, SYS_CLK_EN0_REG0, 8); +static A9_SYS_PCLK(sys_dp_apb, SYS_CLK_EN0_REG0, 9); +static A9_SYS_PCLK(sys_edptx_apb, SYS_CLK_EN0_REG0, 10); +static A9_SYS_PCLK(sys_u3hsg, SYS_CLK_EN0_REG0, 11); +static A9_SYS_PCLK(sys_aucpu, SYS_CLK_EN0_REG0, 14); +static A9_SYS_PCLK(sys_glb, SYS_CLK_EN0_REG0, 15); +static A9_SYS_PCLK(sys_combo_dphy_apb, SYS_CLK_EN0_REG0, 17); +static A9_SYS_PCLK(sys_hdmirx_apb, SYS_CLK_EN0_REG0, 18); +static A9_SYS_PCLK(sys_hdmirx_pclk, SYS_CLK_EN0_REG0, 19); +static A9_SYS_PCLK(sys_mipi_dsi_phy, SYS_CLK_EN0_REG0, 20); +static A9_SYS_PCLK(sys_can0, SYS_CLK_EN0_REG0, 21); +static A9_SYS_PCLK(sys_can1, SYS_CLK_EN0_REG0, 22); +static A9_SYS_PCLK(sys_sd_emmc_a, SYS_CLK_EN0_REG0, 24); +static A9_SYS_PCLK(sys_sd_emmc_b, SYS_CLK_EN0_REG0, 25); +static A9_SYS_PCLK(sys_sd_emmc_c, SYS_CLK_EN0_REG0, 26); +static A9_SYS_PCLK(sys_sc, SYS_CLK_EN0_REG0, 27); +static A9_SYS_PCLK(sys_acodec, SYS_CLK_EN0_REG0, 28); +static A9_SYS_PCLK(sys_mipi_isp, SYS_CLK_EN0_REG0, 29); +static A9_SYS_PCLK(sys_msr, SYS_CLK_EN0_REG0, 30); +static A9_SYS_PCLK(sys_audio, SYS_CLK_EN0_REG1, 0); +static A9_SYS_PCLK(sys_mipi_dsi_b, SYS_CLK_EN0_REG1, 1); +static A9_SYS_PCLK(sys_mipi_dsi1_phy, SYS_CLK_EN0_REG1, 2); +static A9_SYS_PCLK(sys_eth, SYS_CLK_EN0_REG1, 3); +static A9_SYS_PCLK(sys_eth_1g_mac, SYS_CLK_EN0_REG1, 4); +static A9_SYS_PCLK(sys_uart_a, SYS_CLK_EN0_REG1, 5); +static A9_SYS_PCLK(sys_uart_f, SYS_CLK_EN0_REG1, 10); +static A9_SYS_PCLK(sys_ts_a55, SYS_CLK_EN0_REG1, 11); +static A9_SYS_PCLK(sys_eth_1g_axi, SYS_CLK_EN0_REG1, 12); +static A9_SYS_PCLK(sys_ts_dos, SYS_CLK_EN0_REG1, 13); +static A9_SYS_PCLK(sys_u3drd_b, SYS_CLK_EN0_REG1, 14); +static A9_SYS_PCLK(sys_ts_core, SYS_CLK_EN0_REG1, 15); +static A9_SYS_PCLK(sys_ts_pll, SYS_CLK_EN0_REG1, 16); +static A9_SYS_PCLK(sys_csi_dig_clkin, SYS_CLK_EN0_REG1, 18); +static A9_SYS_PCLK(sys_cve, SYS_CLK_EN0_REG1, 19); +static A9_SYS_PCLK(sys_ge2d, SYS_CLK_EN0_REG1, 20); +static A9_SYS_PCLK(sys_spisg, SYS_CLK_EN0_REG1, 21); +static A9_SYS_PCLK(sys_u3drd_1, SYS_CLK_EN0_REG1, 22); +static A9_SYS_PCLK(sys_u2h, SYS_CLK_EN0_REG1, 23); +static A9_SYS_PCLK(sys_pcie_mac_a, SYS_CLK_EN0_REG1, 24); +static A9_SYS_PCLK(sys_u3drd_a, SYS_CLK_EN0_REG1, 25); +static A9_SYS_PCLK(sys_u2drd, SYS_CLK_EN0_REG1, 26); +static A9_SYS_PCLK(sys_pcie_phy, SYS_CLK_EN0_REG1, 27); +static A9_SYS_PCLK(sys_pcie_mac_b, SYS_CLK_EN0_REG1, 28); +static A9_SYS_PCLK(sys_periph, SYS_CLK_EN0_REG1, 29); +static A9_SYS_PCLK(sys_pio, SYS_CLK_EN0_REG2, 0); +static A9_SYS_PCLK(sys_i3c, SYS_CLK_EN0_REG2, 1); +static A9_SYS_PCLK(sys_i2c_m_e, SYS_CLK_EN0_REG2, 2); +static A9_SYS_PCLK(sys_i2c_m_f, SYS_CLK_EN0_REG2, 3); +static A9_SYS_PCLK(sys_hdmitx_apb, SYS_CLK_EN0_REG2, 4); +static A9_SYS_PCLK(sys_i2c_m_i, SYS_CLK_EN0_REG2, 5); +static A9_SYS_PCLK(sys_i2c_m_g, SYS_CLK_EN0_REG2, 6); +static A9_SYS_PCLK(sys_i2c_m_h, SYS_CLK_EN0_REG2, 7); +static A9_SYS_PCLK(sys_hdmi20_aes, SYS_CLK_EN0_REG2, 9); +static A9_SYS_PCLK(sys_csi2_host, SYS_CLK_EN0_REG2, 16); +static A9_SYS_PCLK(sys_csi2_adapt, SYS_CLK_EN0_REG2, 17); +static A9_SYS_PCLK(sys_dspa, SYS_CLK_EN0_REG2, 21); +static A9_SYS_PCLK(sys_pp_dma, SYS_CLK_EN0_REG2, 22); +static A9_SYS_PCLK(sys_pp_wrapper, SYS_CLK_EN0_REG2, 23); +static A9_SYS_PCLK(sys_vpu_intr, SYS_CLK_EN0_REG2, 25); +static A9_SYS_PCLK(sys_csi2_phy, SYS_CLK_EN0_REG2, 27); +static A9_SYS_PCLK(sys_saradc, SYS_CLK_EN0_REG2, 28); +static A9_SYS_PCLK(sys_pwm_j, SYS_CLK_EN0_REG2, 30); +static A9_SYS_PCLK(sys_pwm_i, SYS_CLK_EN0_REG2, 31); +static A9_SYS_PCLK(sys_pwm_h, SYS_CLK_EN0_REG3, 0); +static A9_SYS_PCLK(sys_pwm_n, SYS_CLK_EN0_REG3, 8); +static A9_SYS_PCLK(sys_pwm_m, SYS_CLK_EN0_REG3, 9); +static A9_SYS_PCLK(sys_pwm_l, SYS_CLK_EN0_REG3, 10); +static A9_SYS_PCLK(sys_pwm_k, SYS_CLK_EN0_REG3, 11); + +/* Channel 5 is unconnected. */ +static u32 a9_sd_emmc_parents_val_table[] = { 0, 1, 2, 3, 4, 6, 7 }; +static const struct clk_parent_data a9_sd_emmc_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv3", }, + { .fw_name = "hifi0", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "gp1", }, + { .fw_name = "gp0", } +}; + +static A9_COMP_SEL(sd_emmc_a, SD_EMMC_CLK_CTRL0, 9, 0x7, a9_sd_emmc_parents, + a9_sd_emmc_parents_val_table); +static A9_COMP_DIV(sd_emmc_a, SD_EMMC_CLK_CTRL0, 0, 7); +static A9_COMP_GATE(sd_emmc_a, SD_EMMC_CLK_CTRL0, 8, 0); + +static A9_COMP_SEL(sd_emmc_b, SD_EMMC_CLK_CTRL0, 25, 0x7, a9_sd_emmc_parents, + a9_sd_emmc_parents_val_table); +static A9_COMP_DIV(sd_emmc_b, SD_EMMC_CLK_CTRL0, 16, 7); +static A9_COMP_GATE(sd_emmc_b, SD_EMMC_CLK_CTRL0, 24, 0); + +static A9_COMP_SEL(sd_emmc_c, SD_EMMC_CLK_CTRL1, 9, 0x7, a9_sd_emmc_parents, + a9_sd_emmc_parents_val_table); +static A9_COMP_DIV(sd_emmc_c, SD_EMMC_CLK_CTRL1, 0, 7); +static A9_COMP_GATE(sd_emmc_c, SD_EMMC_CLK_CTRL1, 8, 0); + +static const struct clk_parent_data a9_pwm_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", } +}; + +static A9_COMP_SEL(pwm_h, PWM_CLK_H_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_h, PWM_CLK_H_CTRL, 0, 8); +static A9_COMP_GATE(pwm_h, PWM_CLK_H_CTRL, 8, 0); + +static A9_COMP_SEL(pwm_i, PWM_CLK_I_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_i, PWM_CLK_I_CTRL, 0, 8); +static A9_COMP_GATE(pwm_i, PWM_CLK_I_CTRL, 8, 0); + +static A9_COMP_SEL(pwm_j, PWM_CLK_J_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_j, PWM_CLK_J_CTRL, 0, 8); +static A9_COMP_GATE(pwm_j, PWM_CLK_J_CTRL, 8, 0); + +static A9_COMP_SEL(pwm_k, PWM_CLK_K_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_k, PWM_CLK_K_CTRL, 0, 8); +static A9_COMP_GATE(pwm_k, PWM_CLK_K_CTRL, 8, 0); + +static A9_COMP_SEL(pwm_l, PWM_CLK_L_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_l, PWM_CLK_L_CTRL, 0, 8); +static A9_COMP_GATE(pwm_l, PWM_CLK_L_CTRL, 8, 0); + +static A9_COMP_SEL(pwm_m, PWM_CLK_M_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_m, PWM_CLK_M_CTRL, 0, 8); +static A9_COMP_GATE(pwm_m, PWM_CLK_M_CTRL, 8, 0); + +static A9_COMP_SEL(pwm_n, PWM_CLK_N_CTRL, 9, 0x7, a9_pwm_parents, NULL); +static A9_COMP_DIV(pwm_n, PWM_CLK_N_CTRL, 0, 8); +static A9_COMP_GATE(pwm_n, PWM_CLK_N_CTRL, 8, 0); + +static const struct clk_parent_data a9_spisg_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "sys", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", }, + { .fw_name = "gp0", } +}; + +static A9_COMP_SEL(spisg, SPISG_CLK_CTRL, 9, 0x7, a9_spisg_parents, NULL); +static A9_COMP_DIV(spisg, SPISG_CLK_CTRL, 0, 6); +static A9_COMP_GATE(spisg, SPISG_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(spisg1, SPISG_CLK_CTRL, 25, 0x7, a9_spisg_parents, NULL); +static A9_COMP_DIV(spisg1, SPISG_CLK_CTRL, 16, 6); +static A9_COMP_GATE(spisg1, SPISG_CLK_CTRL, 24, 0); + +static A9_COMP_SEL(spisg2, SPISG_CLK_CTRL1, 9, 0x7, a9_spisg_parents, NULL); +static A9_COMP_DIV(spisg2, SPISG_CLK_CTRL1, 0, 6); +static A9_COMP_GATE(spisg2, SPISG_CLK_CTRL1, 8, 0); + +static const struct clk_parent_data a9_saradc_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "sys", } +}; + +static A9_COMP_SEL(saradc, SAR_CLK_CTRL, 9, 0x7, a9_saradc_parents, NULL); +static A9_COMP_DIV(saradc, SAR_CLK_CTRL, 0, 8); +static A9_COMP_GATE(saradc, SAR_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_amfc_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "sys", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", } +}; + +static A9_COMP_SEL(amfc, AMFC_CLK_CTRL, 9, 0x7, a9_amfc_parents, NULL); +static A9_COMP_DIV(amfc, AMFC_CLK_CTRL, 0, 6); +static A9_COMP_GATE(amfc, AMFC_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_nna_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv2", }, + { .fw_name = "gp2", }, + { .fw_name = "hifi", } +}; + +static A9_COMP_SEL(nna, NNA_CLK_CTRL, 9, 0x7, a9_nna_parents, NULL); +static A9_COMP_DIV(nna, NNA_CLK_CTRL, 0, 7); +static A9_COMP_GATE(nna, NNA_CLK_CTRL, 8, 0); + +/* Channel 5 and 6 are unconnected. */ +static u32 a9_usb_250m_parents_val_table[] = { 0, 1, 2, 3, 4, 7 }; +static const struct clk_parent_data a9_usb_250m_parents[] = { + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv7", }, + { .fw_name = "fdiv2p5", } +}; + +static A9_COMP_SEL(usb_250m, USB_CLK_CTRL, 9, 0x7, a9_usb_250m_parents, + a9_usb_250m_parents_val_table); +static A9_COMP_DIV(usb_250m, USB_CLK_CTRL, 0, 7); +static A9_COMP_GATE(usb_250m, USB_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_usb_48m_pre_parents[] = { + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv7", }, + { .fw_name = "fdiv2p5", } +}; + +static A9_COMP_SEL(usb_48m_pre, USB_CLK_CTRL, 25, 0x7, a9_usb_48m_pre_parents, + NULL); +static A9_COMP_DIV(usb_48m_pre, USB_CLK_CTRL, 16, 7); +static A9_COMP_GATE(usb_48m_pre, USB_CLK_CTRL, 24, 0); + +static const struct clk_parent_data a9_pcie_tl_parents[] = { + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "gp0", }, + { .fw_name = "sys", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(pcie_tl, PCIE_TL_CLK_CTRL, 9, 0x7, a9_pcie_tl_parents, + NULL); +static A9_COMP_DIV(pcie_tl, PCIE_TL_CLK_CTRL, 0, 7); +static A9_COMP_GATE(pcie_tl, PCIE_TL_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(pcie1_tl, PCIE_TL_CLK_CTRL, 25, 0x7, a9_pcie_tl_parents, + NULL); +static A9_COMP_DIV(pcie1_tl, PCIE_TL_CLK_CTRL, 16, 7); +static A9_COMP_GATE(pcie1_tl, PCIE_TL_CLK_CTRL, 24, 0); + +static const struct clk_parent_data a9_cmpr_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", }, + { .fw_name = "hifi0", }, + { .fw_name = "gp1", } +}; + +static A9_COMP_SEL(cmpr, CMPR_CLK_CTRL, 25, 0x7, a9_cmpr_parents, NULL); +static A9_COMP_DIV(cmpr, CMPR_CLK_CTRL, 16, 7); +static A9_COMP_GATE(cmpr, CMPR_CLK_CTRL, 24, 0); + +static const struct clk_parent_data a9_dewarpa_parents[] = { + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", }, + { .fw_name = "gp0", }, + { .fw_name = "hifi0", }, + { .fw_name = "gp1", } +}; + +static A9_COMP_SEL(dewarpa, DEWARP_CLK_CTRL, 9, 0x7, a9_dewarpa_parents, NULL); +static A9_COMP_DIV(dewarpa, DEWARP_CLK_CTRL, 0, 7); +static A9_COMP_GATE(dewarpa, DEWARP_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_sc_parents[] = { + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(sc_pre, SC_CLK_CTRL, 9, 0x7, a9_sc_parents, NULL); +static A9_COMP_DIV(sc_pre, SC_CLK_CTRL, 0, 8); +static A9_COMP_GATE(sc_pre, SC_CLK_CTRL, 8, 0); + +static struct clk_regmap a9_sc = { + .data = &(struct clk_regmap_div_data) { + .offset = SC_CLK_CTRL, + .shift = 16, + .width = 4, + }, + .hw.init = &(struct clk_init_data) { + .name = "sc", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_sc_pre.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_dptx_apb2_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "sys", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", } +}; + +static A9_COMP_SEL(dptx_apb2, DPTX_CLK_CTRL, 9, 0x7, a9_dptx_apb2_parents, NULL); +static A9_COMP_DIV(dptx_apb2, DPTX_CLK_CTRL, 0, 7); +static A9_COMP_GATE(dptx_apb2, DPTX_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_dptx_aud_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "sys", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", } +}; + +static A9_COMP_SEL(dptx_aud, DPTX_CLK_CTRL, 25, 0x7, a9_dptx_aud_parents, NULL); +static A9_COMP_DIV(dptx_aud, DPTX_CLK_CTRL, 16, 7); +static A9_COMP_GATE(dptx_aud, DPTX_CLK_CTRL, 24, 0); + +static const struct clk_parent_data a9_isp_parents[] = { + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "gp0", }, + { .fw_name = "hifi0", }, + { .fw_name = "fdiv2", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(isp, ISP_CLK_CTRL, 9, 0x7, a9_isp_parents, NULL); +static A9_COMP_DIV(isp, ISP_CLK_CTRL, 0, 7); +static A9_COMP_GATE(isp, ISP_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_cve_vge_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "hifi0", }, + { .fw_name = "fdiv5", }, + { .fw_name = "gp0", }, + { .fw_name = "rtc", } +}; + +static A9_COMP_SEL(cve, CVE_CLK_CTRL, 9, 0x7, a9_cve_vge_parents, NULL); +static A9_COMP_DIV(cve, CVE_CLK_CTRL, 0, 7); +static A9_COMP_GATE(cve, CVE_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(vge, CVE_CLK_CTRL, 25, 0x7, a9_cve_vge_parents, NULL); +static A9_COMP_DIV(vge, CVE_CLK_CTRL, 16, 7); +static A9_COMP_GATE(vge, CVE_CLK_CTRL, 24, 0); + +static const struct clk_parent_data a9_pp_parents[] = { + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "gp0", }, + { .fw_name = "sys", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(pp, PP_CLK_CTRL, 9, 0x7, a9_pp_parents, NULL); +static A9_COMP_DIV(pp, PP_CLK_CTRL, 0, 6); +static A9_COMP_GATE(pp, PP_CLK_CTRL, 8, 0); + +/* Channel 6 is unconnected. */ +static u32 a9_glb_parents_val_table[] = { 0, 1, 2, 3, 4, 5, 7 }; +static struct clk_regmap a9_dspa; + +static const struct clk_parent_data a9_glb_parents[] = { + { .fw_name = "xtal", }, + { .hw = &a9_dspa.hw }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .hw = &a9_isp.hw }, + { .fw_name = "rtc", } +}; + +static A9_COMP_SEL(glb, GLB_CLK_CTRL, 9, 0x7, a9_glb_parents, + a9_glb_parents_val_table); +static A9_COMP_DIV(glb, GLB_CLK_CTRL, 0, 7); +static A9_COMP_GATE(glb, GLB_CLK_CTRL, 8, 0); + +static struct clk_regmap a9_usb_48m_dualdiv_in = { + .data = &(struct clk_regmap_gate_data) { + .offset = USB_CLK_CTRL, + .bit_idx = 31, + }, + .hw.init = &(struct clk_init_data) { + .name = "usb_48m_dualdiv_in", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_usb_48m_pre.hw + }, + .num_parents = 1, + }, +}; + +static const struct meson_clk_dualdiv_param a9_usb_48m_dualdiv_div_table[] = { + { 733, 732, 8, 11, 1 }, + { /* sentinel */ } +}; + +static struct clk_regmap a9_usb_48m_dualdiv_div = { + .data = &(struct meson_clk_dualdiv_data) { + .n1 = { + .reg_off = USB_CLK_CTRL0, + .shift = 0, + .width = 12, + }, + .n2 = { + .reg_off = USB_CLK_CTRL0, + .shift = 12, + .width = 12, + }, + .m1 = { + .reg_off = USB_CLK_CTRL1, + .shift = 0, + .width = 12, + }, + .m2 = { + .reg_off = USB_CLK_CTRL1, + .shift = 12, + .width = 12, + }, + .dual = { + .reg_off = USB_CLK_CTRL0, + .shift = 28, + .width = 1, + }, + .table = a9_usb_48m_dualdiv_div_table, + }, + .hw.init = &(struct clk_init_data) { + .name = "usb_48m_dualdiv_div", + .ops = &meson_clk_dualdiv_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_usb_48m_dualdiv_in.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_usb_48m_dualdiv_sel = { + .data = &(struct clk_regmap_mux_data) { + .offset = USB_CLK_CTRL1, + .mask = 0x1, + .shift = 24, + }, + .hw.init = &(struct clk_init_data) { + .name = "usb_48m_dualdiv_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_usb_48m_dualdiv_in.hw, + &a9_usb_48m_dualdiv_div.hw, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_usb_48m_dualdiv = { + .data = &(struct clk_regmap_gate_data) { + .offset = USB_CLK_CTRL0, + .bit_idx = 30, + }, + .hw.init = &(struct clk_init_data) { + .name = "usb_48m_dualdiv", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_usb_48m_dualdiv_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_usb_48m = { + .data = &(struct clk_regmap_mux_data) { + .offset = USB_CLK_CTRL1, + .mask = 0x3, + .shift = 30, + }, + .hw.init = &(struct clk_init_data) { + .name = "usb_48m", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_usb_48m_pre.hw, + &a9_usb_48m_dualdiv.hw, + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_can_pe_parents[] = { + { .fw_name = "sys", }, + { .fw_name = "xtal", }, + { .fw_name = "usb2drd", }, + { .fw_name = "fdiv5", } +}; + +static A9_COMP_SEL(can_pe, CAN_CLK_CTRL, 9, 0x7, a9_can_pe_parents, NULL); +static A9_COMP_DIV(can_pe, CAN_CLK_CTRL, 0, 7); +static A9_COMP_GATE(can_pe, CAN_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(can1_pe, CAN_CLK_CTRL, 25, 0x7, a9_can_pe_parents, NULL); +static A9_COMP_DIV(can1_pe, CAN_CLK_CTRL, 16, 7); +static A9_COMP_GATE(can1_pe, CAN_CLK_CTRL, 24, 0); + +static const struct clk_parent_data a9_can_filter_parents[] = { + { .fw_name = "sys", }, + { .fw_name = "xtal", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", } +}; + +static A9_COMP_SEL(can_filter, CAN_CLK_CTRL1, 9, 0x7, a9_can_filter_parents, + NULL); +static A9_COMP_DIV(can_filter, CAN_CLK_CTRL1, 0, 7); +static A9_COMP_GATE(can_filter, CAN_CLK_CTRL1, 8, 0); + +static A9_COMP_SEL(can1_filter, CAN_CLK_CTRL1, 25, 0x7, a9_can_filter_parents, + NULL); +static A9_COMP_DIV(can1_filter, CAN_CLK_CTRL1, 16, 7); +static A9_COMP_GATE(can1_filter, CAN_CLK_CTRL1, 24, 0); + +static const struct clk_parent_data a9_i3c_parents[] = { + { .fw_name = "sys", }, + { .fw_name = "xtal", }, + { .fw_name = "fdiv5", } +}; + +static A9_COMP_SEL(i3c, I3C_CLK_CTRL, 9, 0x7, a9_i3c_parents, NULL); +static A9_COMP_DIV(i3c, I3C_CLK_CTRL, 0, 8); +static A9_COMP_GATE(i3c, I3C_CLK_CTRL, 8, 0); + +static struct clk_regmap a9_ts_div = { + .data = &(struct clk_regmap_div_data) { + .offset = TS_CLK_CTRL, + .shift = 0, + .width = 8, + }, + .hw.init = &(struct clk_init_data) { + .name = "ts_div", + .ops = &clk_regmap_divider_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "xtal", + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_ts = { + .data = &(struct clk_regmap_gate_data) { + .offset = TS_CLK_CTRL, + .bit_idx = 8, + }, + .hw.init = &(struct clk_init_data) { + .name = "ts", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_ts_div.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_fixed_factor a9_eth_125m_div = { + .mult = 1, + .div = 8, + .hw.init = &(struct clk_init_data) { + .name = "eth_125m_div", + .ops = &clk_fixed_factor_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "fdiv2", + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_eth_125m = { + .data = &(struct clk_regmap_gate_data) { + .offset = ETH_CLK_CTRL, + .bit_idx = 7, + }, + .hw.init = &(struct clk_init_data) { + .name = "eth_125m", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_eth_125m_div.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +/* + * Channel 1, 2, 3, 4, 5 and 6 are unconnected, + * ext_rmii connects external PAD. + */ +static u32 a9_eth_rmii_parents_val_table[] = { 0, 7 }; +static const struct clk_parent_data a9_eth_rmii_parents[] = { + { .fw_name = "fdiv2", }, + { .fw_name = "ext_rmii", } +}; + +static struct clk_regmap a9_eth_rmii_sel = { + .data = &(struct clk_regmap_mux_data) { + .offset = ETH_CLK_CTRL, + .mask = 0x7, + .shift = 9, + .table = a9_eth_rmii_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "eth_rmii_sel", + .ops = &clk_regmap_mux_ops, + .parent_data = a9_eth_rmii_parents, + .num_parents = ARRAY_SIZE(a9_eth_rmii_parents), + .flags = CLK_SET_RATE_NO_REPARENT, + }, +}; + +static struct clk_regmap a9_eth_rmii_div = { + .data = &(struct clk_regmap_div_data) { + .offset = ETH_CLK_CTRL, + .shift = 0, + .width = 7, + }, + .hw.init = &(struct clk_init_data){ + .name = "eth_rmii_div", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_eth_rmii_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_eth_rmii = { + .data = &(struct clk_regmap_gate_data) { + .offset = ETH_CLK_CTRL, + .bit_idx = 8, + }, + .hw.init = &(struct clk_init_data){ + .name = "eth_rmii", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_eth_rmii_div.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +/* + * Channel 3(ddr_dpll_pt_clk) is manged by the DDR module; + * channel 12(msr_clk) is manged by clock measures module. + * channel 16(audio_dac1_clk) is manged by audio module. + * Channel 10, 11, 13, 14 are not connected. + */ +static u32 a9_gen_parents_val_table[] = { 0, 1, 2, 4, 5, 6, 7, 8, 9, 15, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26}; +static struct clk_regmap a9_vid_pll; + +static const struct clk_parent_data a9_gen_parents[] = { + { .fw_name = "xtal" }, + { .fw_name = "rtc" }, + { .fw_name = "sysplldiv16" }, + { .hw = &a9_vid_pll.hw }, + { .fw_name = "gp0" }, + { .fw_name = "hifi1" }, + { .fw_name = "hifi0" }, + { .fw_name = "gp1" }, + { .fw_name = "gp2" }, + { .fw_name = "dsudiv16" }, + { .fw_name = "cpudiv16" }, + { .fw_name = "a78div16" }, + { .fw_name = "fdiv2" }, + { .fw_name = "fdiv2p5" }, + { .fw_name = "fdiv3" }, + { .fw_name = "fdiv4" }, + { .fw_name = "fdiv5" }, + { .fw_name = "fdiv7" }, + { .fw_name = "mclk0" }, + { .fw_name = "mclk1" } +}; + +static A9_COMP_SEL(gen, GEN_CLK_CTRL, 12, 0x1f, a9_gen_parents, + a9_gen_parents_val_table); +static A9_COMP_DIV(gen, GEN_CLK_CTRL, 0, 12); +static A9_COMP_GATE(gen, GEN_CLK_CTRL, 11, 0); + +static struct clk_regmap a9_24m_in = { + .data = &(struct clk_regmap_gate_data) { + .offset = CLK12_24_CTRL, + .bit_idx = 11, + }, + .hw.init = &(struct clk_init_data) { + .name = "24m_in", + .ops = &clk_regmap_gate_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "xtal", + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_12_24m = { + .data = &(struct clk_regmap_div_data) { + .offset = CLK12_24_CTRL, + .shift = 10, + .width = 1, + }, + .hw.init = &(struct clk_init_data) { + .name = "12_24m", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_24m_in.hw + }, + .num_parents = 1, + }, +}; + +static const struct clk_parent_data a9_mali_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "gp1", }, + { .fw_name = "fdiv2", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", } +}; + +static A9_COMP_SEL(mali_0, MALI_CLK_CTRL, 9, 0x7, a9_mali_parents, NULL); +static A9_COMP_DIV(mali_0, MALI_CLK_CTRL, 0, 7); +static A9_COMP_GATE(mali_0, MALI_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(mali_1, MALI_CLK_CTRL, 25, 0x7, a9_mali_parents, NULL); +static A9_COMP_DIV(mali_1, MALI_CLK_CTRL, 16, 7); +static A9_COMP_GATE(mali_1, MALI_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_mali = { + .data = &(struct clk_regmap_mux_data){ + .offset = MALI_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "mali", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mali_0.hw, + &a9_mali_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static A9_COMP_SEL(mali_stack_0, MALI_STACK_CLK_CTRL, 9, 0x7, a9_mali_parents, + NULL); +static A9_COMP_DIV(mali_stack_0, MALI_STACK_CLK_CTRL, 0, 7); +static A9_COMP_GATE(mali_stack_0, MALI_STACK_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(mali_stack_1, MALI_STACK_CLK_CTRL, 25, 0x7, a9_mali_parents, + NULL); +static A9_COMP_DIV(mali_stack_1, MALI_STACK_CLK_CTRL, 16, 7); +static A9_COMP_GATE(mali_stack_1, MALI_STACK_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_mali_stack = { + .data = &(struct clk_regmap_mux_data){ + .offset = MALI_STACK_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "mali_stack", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mali_stack_0.hw, + &a9_mali_stack_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_dspa_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "gp2", }, + { .fw_name = "fdiv4", }, + { .fw_name = "hifi0", }, + { .fw_name = "rtc", } +}; + +static A9_COMP_SEL(dspa_0, DSPA_CLK_CTRL, 9, 0x7, a9_dspa_parents, NULL); +static A9_COMP_DIV(dspa_0, DSPA_CLK_CTRL, 0, 7); +static A9_COMP_GATE(dspa_0, DSPA_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(dspa_1, DSPA_CLK_CTRL, 25, 0x7, a9_dspa_parents, NULL); +static A9_COMP_DIV(dspa_1, DSPA_CLK_CTRL, 16, 7); +static A9_COMP_GATE(dspa_1, DSPA_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_dspa = { + .data = &(struct clk_regmap_mux_data){ + .offset = DSPA_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "dspa", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_dspa_0.hw, + &a9_dspa_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_hevcf_parents[] = { + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", }, + { .fw_name = "hifi0", }, + { .fw_name = "gp1", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(hevcf_0, HEVCF_CLK_CTRL, 9, 0x7, a9_hevcf_parents, NULL); +static A9_COMP_DIV(hevcf_0, HEVCF_CLK_CTRL, 0, 7); +static A9_COMP_GATE(hevcf_0, HEVCF_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(hevcf_1, HEVCF_CLK_CTRL, 25, 0x7, a9_hevcf_parents, NULL); +static A9_COMP_DIV(hevcf_1, HEVCF_CLK_CTRL, 16, 7); +static A9_COMP_GATE(hevcf_1, HEVCF_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_hevcf = { + .data = &(struct clk_regmap_mux_data){ + .offset = HEVCF_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "hevcf", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hevcf_0.hw, + &a9_hevcf_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_hcodec_parents[] = { + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", }, + { .fw_name = "hifi0", }, + { .fw_name = "gp0", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(hcodec_0, HCODEC_CLK_CTRL, 9, 0x7, a9_hcodec_parents, NULL); +static A9_COMP_DIV(hcodec_0, HCODEC_CLK_CTRL, 0, 7); +static A9_COMP_GATE(hcodec_0, HCODEC_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(hcodec_1, HCODEC_CLK_CTRL, 25, 0x7, a9_hcodec_parents, NULL); +static A9_COMP_DIV(hcodec_1, HCODEC_CLK_CTRL, 16, 7); +static A9_COMP_GATE(hcodec_1, HCODEC_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_hcodec = { + .data = &(struct clk_regmap_mux_data){ + .offset = HCODEC_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "hcodec", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hcodec_0.hw, + &a9_hcodec_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_vpu_parents[] = { + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "vid1", }, + { .fw_name = "fdiv2", }, + { .hw = &a9_vid_pll.hw }, + { .fw_name = "vid2", }, + { .fw_name = "gp1", } +}; + +static A9_COMP_SEL(vpu_0, VPU_CLK_CTRL, 9, 0x7, a9_vpu_parents, NULL); +static A9_COMP_DIV(vpu_0, VPU_CLK_CTRL, 0, 7); +static A9_COMP_GATE(vpu_0, VPU_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(vpu_1, VPU_CLK_CTRL, 25, 0x7, a9_vpu_parents, NULL); +static A9_COMP_DIV(vpu_1, VPU_CLK_CTRL, 16, 7); +static A9_COMP_GATE(vpu_1, VPU_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_vpu = { + .data = &(struct clk_regmap_mux_data){ + .offset = VPU_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "vpu", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vpu_0.hw, + &a9_vpu_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_vapb_parents[] = { + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", }, + { .fw_name = "fdiv2", }, + { .hw = &a9_vid_pll.hw }, + { .fw_name = "hifi0", }, + { .fw_name = "fdiv2p5", } +}; + +static A9_COMP_SEL(vapb_0, VAPB_CLK_CTRL, 9, 0x7, a9_vapb_parents, NULL); +static A9_COMP_DIV(vapb_0, VAPB_CLK_CTRL, 0, 7); +static A9_COMP_GATE(vapb_0, VAPB_CLK_CTRL, 8, CLK_SET_RATE_GATE); + +static A9_COMP_SEL(vapb_1, VAPB_CLK_CTRL, 25, 0x7, a9_vapb_parents, NULL); +static A9_COMP_DIV(vapb_1, VAPB_CLK_CTRL, 16, 7); +static A9_COMP_GATE(vapb_1, VAPB_CLK_CTRL, 24, CLK_SET_RATE_GATE); + +static struct clk_regmap a9_vapb = { + .data = &(struct clk_regmap_mux_data){ + .offset = VAPB_CLK_CTRL, + .mask = 0x1, + .shift = 31, + }, + .hw.init = &(struct clk_init_data){ + .name = "vapb", + .ops = &clk_regmap_mux_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vapb_0.hw, + &a9_vapb_1.hw + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_ge2d = { + .data = &(struct clk_regmap_gate_data) { + .offset = VAPB_CLK_CTRL, + .bit_idx = 30, + }, + .hw.init = &(struct clk_init_data) { + .name = "ge2d", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vapb.hw, + }, + .num_parents = 1, + }, +}; + +static const struct clk_parent_data a9_vpu_clkb_tmp_parents[] = { + { .hw = &a9_vpu.hw }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "fdiv7", } +}; + +static A9_COMP_SEL(vpu_clkb_tmp, VPU_CLKB_CTRL, 25, 0x7, a9_vpu_clkb_tmp_parents, + NULL); +static A9_COMP_DIV(vpu_clkb_tmp, VPU_CLKB_CTRL, 16, 4); +static A9_COMP_GATE(vpu_clkb_tmp, VPU_CLKB_CTRL, 24, 0); + +static struct clk_regmap a9_vpu_clkb_div = { + .data = &(struct clk_regmap_div_data) { + .offset = VPU_CLKB_CTRL, + .shift = 0, + .width = 8, + }, + .hw.init = &(struct clk_init_data) { + .name = "vpu_clkb_div", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vpu_clkb_tmp.hw, + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_vpu_clkb = { + .data = &(struct clk_regmap_gate_data) { + .offset = VPU_CLKB_CTRL, + .bit_idx = 8, + }, + .hw.init = &(struct clk_init_data) { + .name = "vpu_clkb", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vpu_clkb_div.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_hdmi_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", } +}; + +static A9_COMP_SEL(hdmitx_sys, HDMI_CLK_CTRL, 9, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmitx_sys, HDMI_CLK_CTRL, 0, 7); +static A9_COMP_GATE(hdmitx_sys, HDMI_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(hdmitx_prif, HTX_CLK_CTRL, 9, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmitx_prif, HTX_CLK_CTRL, 0, 7); +static A9_COMP_GATE(hdmitx_prif, HTX_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(hdmitx_200m, HTX_CLK_CTRL, 25, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmitx_200m, HTX_CLK_CTRL, 16, 7); +static A9_COMP_GATE(hdmitx_200m, HTX_CLK_CTRL, 24, 0); + +static A9_COMP_SEL(hdmitx_aud, HTX_CLK_CTRL1, 9, 0x7, a9_hdmi_parents, NULL); +static A9_COMP_DIV(hdmitx_aud, HTX_CLK_CTRL1, 0, 7); +static A9_COMP_GATE(hdmitx_aud, HTX_CLK_CTRL1, 8, 0); + +static A9_COMP_SEL(hdmirx_5m, HRX_CLK_CTRL, 9, 0x7, a9_hdmi_parents, + NULL); +static A9_COMP_DIV(hdmirx_5m, HRX_CLK_CTRL, 0, 7); +static A9_COMP_GATE(hdmirx_5m, HRX_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(hdmirx_2m, HRX_CLK_CTRL, 25, 0x7, a9_hdmi_parents, + NULL); +static A9_COMP_DIV(hdmirx_2m, HRX_CLK_CTRL, 16, 7); +static A9_COMP_GATE(hdmirx_2m, HRX_CLK_CTRL, 24, 0); + +static A9_COMP_SEL(hdmirx_cfg, HRX_CLK_CTRL1, 9, 0x7, a9_hdmi_parents, + NULL); +static A9_COMP_DIV(hdmirx_cfg, HRX_CLK_CTRL1, 0, 7); +static A9_COMP_GATE(hdmirx_cfg, HRX_CLK_CTRL1, 8, 0); + +static A9_COMP_SEL(hdmirx_hdcp2x, HRX_CLK_CTRL1, 25, 0x7, a9_hdmi_parents, + NULL); +static A9_COMP_DIV(hdmirx_hdcp2x, HRX_CLK_CTRL1, 16, 7); +static A9_COMP_GATE(hdmirx_hdcp2x, HRX_CLK_CTRL1, 24, 0); + +static A9_COMP_SEL(hdmirx_acr_ref, HRX_CLK_CTRL2, 25, 0x7, a9_hdmi_parents, + NULL); +static A9_COMP_DIV(hdmirx_acr_ref, HRX_CLK_CTRL2, 16, 7); +static A9_COMP_GATE(hdmirx_acr_ref, HRX_CLK_CTRL2, 24, 0); + +static A9_COMP_SEL(hdmirx_meter, HRX_CLK_CTRL3, 9, 0x7, a9_hdmi_parents, + NULL); +static A9_COMP_DIV(hdmirx_meter, HRX_CLK_CTRL3, 0, 7); +static A9_COMP_GATE(hdmirx_meter, HRX_CLK_CTRL3, 8, 0); + +static struct clk_regmap a9_enc, a9_enc1; + +static const struct clk_parent_data a9_vid_lock_parents[] = { + { .fw_name = "xtal", }, + { .hw = &a9_enc.hw }, + { .hw = &a9_enc1.hw } +}; + +static A9_COMP_SEL(vid_lock, VID_LOCK_CLK_CTRL, 9, 0x7, a9_vid_lock_parents, + NULL); +static A9_COMP_DIV(vid_lock, VID_LOCK_CLK_CTRL, 0, 7); +static A9_COMP_GATE(vid_lock, VID_LOCK_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_vdin_meas_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", } +}; + +static A9_COMP_SEL(vdin_meas, VDIN_MEAS_CLK_CTRL, 9, 0x7, a9_vdin_meas_parents, + NULL); +static A9_COMP_DIV(vdin_meas, VDIN_MEAS_CLK_CTRL, 0, 7); +static A9_COMP_GATE(vdin_meas, VDIN_MEAS_CLK_CTRL, 8, 0); + +static struct clk_regmap a9_vid_pll_div = { + .data = &(struct meson_vid_pll_div_data){ + .val = { + .reg_off = VID_PLL_CLK_DIV, + .shift = 0, + .width = 15, + }, + .sel = { + .reg_off = VID_PLL_CLK_DIV, + .shift = 16, + .width = 2, + }, + }, + .hw.init = &(struct clk_init_data) { + .name = "vid_pll_div", + .ops = &meson_vid_pll_div_ro_ops, + .parent_data = (const struct clk_parent_data []) { + { .fw_name = "hdmiout2", } + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_vid_pll_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = VID_PLL_CLK_DIV, + .mask = 0x1, + .shift = 18, + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_sel", + .ops = &clk_regmap_mux_ops, + .parent_data = (const struct clk_parent_data []) { + { .hw = &a9_vid_pll_div.hw }, + { .fw_name = "hdmiout2", } + }, + .num_parents = 2, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vid_pll = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_PLL_CLK_DIV, + .bit_idx = 19, + }, + .hw.init = &(struct clk_init_data) { + .name = "vid_pll", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vid_pll_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vid_pll_vclk = { + .data = &(struct clk_regmap_mux_data){ + .offset = HDMI_CLK_CTRL, + .mask = 0x1, + .shift = 15, + }, + .hw.init = &(struct clk_init_data){ + .name = "vid_pll_vclk", + .ops = &clk_regmap_mux_ops, + .parent_data = (const struct clk_parent_data []) { + { .hw = &a9_vid_pll.hw }, + { .fw_name = "hdmipix", } + }, + .num_parents = 2, + }, +}; + +static const struct clk_parent_data a9_vclk_parents[] = { + { .hw = &a9_vid_pll_vclk.hw }, + { .fw_name = "pix0", }, + { .fw_name = "vid1", }, + { .fw_name = "pix1", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "vid2", } +}; + +static struct clk_regmap a9_vclk_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = VID_CLK_CTRL, + .mask = 0x7, + .shift = 16, + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_sel", + .ops = &clk_regmap_mux_ops, + .parent_data = a9_vclk_parents, + .num_parents = ARRAY_SIZE(a9_vclk_parents), + }, +}; + +static struct clk_regmap a9_vclk_in = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_DIV, + .bit_idx = 16, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk_in", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk_sel.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk_div = { + .data = &(struct clk_regmap_div_data){ + .offset = VID_CLK_DIV, + .shift = 0, + .width = 8, + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vclk_in.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL, + .bit_idx = 19, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk_div.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk_div1_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL, + .bit_idx = 0, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk_div1_en", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk_div2_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL, + .bit_idx = 1, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk_div2_en", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_fixed_factor a9_vclk_div2 = { + .mult = 1, + .div = 2, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div2", + .ops = &clk_fixed_factor_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vclk_div2_en.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk_div4_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL, + .bit_idx = 2, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk_div4_en", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_fixed_factor a9_vclk_div4 = { + .mult = 1, + .div = 4, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div4", + .ops = &clk_fixed_factor_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vclk_div4_en.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk_div6_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL, + .bit_idx = 3, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk_div6_en", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_fixed_factor a9_vclk_div6 = { + .mult = 1, + .div = 6, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div6", + .ops = &clk_fixed_factor_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vclk_div6_en.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk_div12_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL, + .bit_idx = 4, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk_div12_en", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_fixed_factor a9_vclk_div12 = { + .mult = 1, + .div = 12, + .hw.init = &(struct clk_init_data){ + .name = "vclk_div12", + .ops = &clk_fixed_factor_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vclk_div12_en.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk2_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = VIID_CLK_CTRL, + .mask = 0x7, + .shift = 16, + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_sel", + .ops = &clk_regmap_mux_ops, + .parent_data = a9_vclk_parents, + .num_parents = ARRAY_SIZE(a9_vclk_parents), + }, +}; + +static struct clk_regmap a9_vclk2_in = { + .data = &(struct clk_regmap_gate_data){ + .offset = VIID_CLK_DIV, + .bit_idx = 16, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk2_in", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk2_sel.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk2_div = { + .data = &(struct clk_regmap_div_data){ + .offset = VIID_CLK_DIV, + .shift = 0, + .width = 8, + }, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vclk2_in.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk2 = { + .data = &(struct clk_regmap_gate_data){ + .offset = VIID_CLK_CTRL, + .bit_idx = 19, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk2", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk2_div.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk2_div1_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = VIID_CLK_CTRL, + .bit_idx = 0, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk2_div1_en", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk2.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk2_div2_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = VIID_CLK_CTRL, + .bit_idx = 1, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk2_div2_en", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk2.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_fixed_factor a9_vclk2_div2 = { + .mult = 1, + .div = 2, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div2", + .ops = &clk_fixed_factor_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vclk2_div2_en.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk2_div4_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = VIID_CLK_CTRL, + .bit_idx = 2, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk2_div4_en", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk2.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_fixed_factor a9_vclk2_div4 = { + .mult = 1, + .div = 4, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div4", + .ops = &clk_fixed_factor_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vclk2_div4_en.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk2_div6_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = VIID_CLK_CTRL, + .bit_idx = 3, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk2_div6_en", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk2.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_fixed_factor a9_vclk2_div6 = { + .mult = 1, + .div = 6, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div6", + .ops = &clk_fixed_factor_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vclk2_div6_en.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_vclk2_div12_en = { + .data = &(struct clk_regmap_gate_data){ + .offset = VIID_CLK_CTRL, + .bit_idx = 4, + }, + .hw.init = &(struct clk_init_data) { + .name = "vclk2_div12_en", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { &a9_vclk2.hw }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_fixed_factor a9_vclk2_div12 = { + .mult = 1, + .div = 12, + .hw.init = &(struct clk_init_data){ + .name = "vclk2_div12", + .ops = &clk_fixed_factor_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vclk2_div12_en.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +/* Channel 5, 6 and 7 are unconnected */ +static u32 a9_vid_parents_val_table[] = { 0, 1, 2, 3, 4, 8, 9, 10, 11, 12 }; +static const struct clk_hw *a9_vid_parents[] = { + &a9_vclk_div1_en.hw, + &a9_vclk_div2.hw, + &a9_vclk_div4.hw, + &a9_vclk_div6.hw, + &a9_vclk_div12.hw, + &a9_vclk2_div1_en.hw, + &a9_vclk2_div2.hw, + &a9_vclk2_div4.hw, + &a9_vclk2_div6.hw, + &a9_vclk2_div12.hw +}; + +static struct clk_regmap a9_vdac_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = VIID_CLK_DIV, + .mask = 0xf, + .shift = 28, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "vdac_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_vdac = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 4, + }, + .hw.init = &(struct clk_init_data) { + .name = "vdac", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_vdac_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_enc_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = VIID_CLK_DIV, + .mask = 0xf, + .shift = 12, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "enc_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_enc = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 10, + }, + .hw.init = &(struct clk_init_data) { + .name = "enc", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_enc_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_enc1_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = VIID_CLK_DIV, + .mask = 0xf, + .shift = 8, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "enc1_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_enc1 = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 11, + }, + .hw.init = &(struct clk_init_data) { + .name = "enc1", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_enc1_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_hdmitx_pixel_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HDMI_CLK_CTRL, + .mask = 0xf, + .shift = 16, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmitx_pixel_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_hdmitx_pixel = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 5, + }, + .hw.init = &(struct clk_init_data) { + .name = "hdmitx_pixel", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hdmitx_pixel_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_hdmitx_fe_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HDMI_CLK_CTRL, + .mask = 0xf, + .shift = 20, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmitx_fe_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_hdmitx_fe = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 9, + }, + .hw.init = &(struct clk_init_data) { + .name = "hdmitx_fe", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hdmitx_fe_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_hdmitx1_pixel_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HDMI_CLK_CTRL, + .mask = 0xf, + .shift = 24, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmitx1_pixel_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_hdmitx1_pixel = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 12, + }, + .hw.init = &(struct clk_init_data) { + .name = "hdmitx1_pixel", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hdmitx_pixel_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_regmap a9_hdmitx1_fe_sel = { + .data = &(struct clk_regmap_mux_data){ + .offset = HDMI_CLK_CTRL, + .mask = 0xf, + .shift = 28, + .table = a9_vid_parents_val_table, + }, + .hw.init = &(struct clk_init_data){ + .name = "hdmitx1_fe_sel", + .ops = &clk_regmap_mux_ops, + .parent_hws = a9_vid_parents, + .num_parents = ARRAY_SIZE(a9_vid_parents), + }, +}; + +static struct clk_regmap a9_hdmitx1_fe = { + .data = &(struct clk_regmap_gate_data){ + .offset = VID_CLK_CTRL2, + .bit_idx = 13, + }, + .hw.init = &(struct clk_init_data) { + .name = "hdmitx1_fe", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hdmitx1_fe_sel.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_csi_phy_parents[] = { + { .fw_name = "fdiv2p5", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv5", }, + { .fw_name = "gp0", }, + { .fw_name = "hifi0", }, + { .fw_name = "fdiv2", }, + { .fw_name = "xtal", } +}; + +static A9_COMP_SEL(csi_phy, MIPI_CSI_PHY_CLK_CTRL, 9, 0x7, + a9_csi_phy_parents, NULL); +static A9_COMP_DIV(csi_phy, MIPI_CSI_PHY_CLK_CTRL, 0, 7); +static A9_COMP_GATE(csi_phy, MIPI_CSI_PHY_CLK_CTRL, 8, 0); + +static const struct clk_parent_data a9_dsi_meas_parents[] = { + { .fw_name = "xtal", }, + { .fw_name = "fdiv4", }, + { .fw_name = "fdiv3", }, + { .fw_name = "fdiv5", }, + { .hw = &a9_vid_pll.hw }, + { .fw_name = "gp0", }, + { .fw_name = "vid1", }, + { .fw_name = "vid2", } +}; + +static A9_COMP_SEL(dsi_meas, DSI_MEAS_CLK_CTRL, 9, 0x7, + a9_dsi_meas_parents, NULL); +static A9_COMP_DIV(dsi_meas, DSI_MEAS_CLK_CTRL, 0, 7); +static A9_COMP_GATE(dsi_meas, DSI_MEAS_CLK_CTRL, 8, 0); + +static A9_COMP_SEL(dsi_b_meas, DSI_MEAS_CLK_CTRL, 25, 0x7, + a9_dsi_meas_parents, NULL); +static A9_COMP_DIV(dsi_b_meas, DSI_MEAS_CLK_CTRL, 16, 7); +static A9_COMP_GATE(dsi_b_meas, DSI_MEAS_CLK_CTRL, 24, 0); + +static struct clk_hw *a9_peripherals_hw_clks[] = { + [CLKID_SYS_AM_AXI] = &a9_sys_am_axi.hw, + [CLKID_SYS_DOS] = &a9_sys_dos.hw, + [CLKID_SYS_MIPI_DSI] = &a9_sys_mipi_dsi.hw, + [CLKID_SYS_ETH_PHY] = &a9_sys_eth_phy.hw, + [CLKID_SYS_AMFC] = &a9_sys_amfc.hw, + [CLKID_SYS_MALI] = &a9_sys_mali.hw, + [CLKID_SYS_NNA] = &a9_sys_nna.hw, + [CLKID_SYS_ETH_AXI] = &a9_sys_eth_axi.hw, + [CLKID_SYS_DP_APB] = &a9_sys_dp_apb.hw, + [CLKID_SYS_EDPTX_APB] = &a9_sys_edptx_apb.hw, + [CLKID_SYS_U3HSG] = &a9_sys_u3hsg.hw, + [CLKID_SYS_AUCPU] = &a9_sys_aucpu.hw, + [CLKID_SYS_GLB] = &a9_sys_glb.hw, + [CLKID_SYS_COMBO_DPHY_APB] = &a9_sys_combo_dphy_apb.hw, + [CLKID_SYS_HDMIRX_APB] = &a9_sys_hdmirx_apb.hw, + [CLKID_SYS_HDMIRX_PCLK] = &a9_sys_hdmirx_pclk.hw, + [CLKID_SYS_MIPI_DSI_PHY] = &a9_sys_mipi_dsi_phy.hw, + [CLKID_SYS_CAN0] = &a9_sys_can0.hw, + [CLKID_SYS_CAN1] = &a9_sys_can1.hw, + [CLKID_SYS_SD_EMMC_A] = &a9_sys_sd_emmc_a.hw, + [CLKID_SYS_SD_EMMC_B] = &a9_sys_sd_emmc_b.hw, + [CLKID_SYS_SD_EMMC_C] = &a9_sys_sd_emmc_c.hw, + [CLKID_SYS_SC] = &a9_sys_sc.hw, + [CLKID_SYS_ACODEC] = &a9_sys_acodec.hw, + [CLKID_SYS_MIPI_ISP] = &a9_sys_mipi_isp.hw, + [CLKID_SYS_MSR] = &a9_sys_msr.hw, + [CLKID_SYS_AUDIO] = &a9_sys_audio.hw, + [CLKID_SYS_MIPI_DSI_B] = &a9_sys_mipi_dsi_b.hw, + [CLKID_SYS_MIPI_DSI1_PHY] = &a9_sys_mipi_dsi1_phy.hw, + [CLKID_SYS_ETH] = &a9_sys_eth.hw, + [CLKID_SYS_ETH_1G_MAC] = &a9_sys_eth_1g_mac.hw, + [CLKID_SYS_UART_A] = &a9_sys_uart_a.hw, + [CLKID_SYS_UART_F] = &a9_sys_uart_f.hw, + [CLKID_SYS_TS_A55] = &a9_sys_ts_a55.hw, + [CLKID_SYS_ETH_1G_AXI] = &a9_sys_eth_1g_axi.hw, + [CLKID_SYS_TS_DOS] = &a9_sys_ts_dos.hw, + [CLKID_SYS_U3DRD_B] = &a9_sys_u3drd_b.hw, + [CLKID_SYS_TS_CORE] = &a9_sys_ts_core.hw, + [CLKID_SYS_TS_PLL] = &a9_sys_ts_pll.hw, + [CLKID_SYS_CSI_DIG_CLKIN] = &a9_sys_csi_dig_clkin.hw, + [CLKID_SYS_CVE] = &a9_sys_cve.hw, + [CLKID_SYS_GE2D] = &a9_sys_ge2d.hw, + [CLKID_SYS_SPISG] = &a9_sys_spisg.hw, + [CLKID_SYS_U3DRD_1] = &a9_sys_u3drd_1.hw, + [CLKID_SYS_U2H] = &a9_sys_u2h.hw, + [CLKID_SYS_PCIE_MAC_A] = &a9_sys_pcie_mac_a.hw, + [CLKID_SYS_U3DRD_A] = &a9_sys_u3drd_a.hw, + [CLKID_SYS_U2DRD] = &a9_sys_u2drd.hw, + [CLKID_SYS_PCIE_PHY] = &a9_sys_pcie_phy.hw, + [CLKID_SYS_PCIE_MAC_B] = &a9_sys_pcie_mac_b.hw, + [CLKID_SYS_PERIPH] = &a9_sys_periph.hw, + [CLKID_SYS_PIO] = &a9_sys_pio.hw, + [CLKID_SYS_I3C] = &a9_sys_i3c.hw, + [CLKID_SYS_I2C_M_E] = &a9_sys_i2c_m_e.hw, + [CLKID_SYS_I2C_M_F] = &a9_sys_i2c_m_f.hw, + [CLKID_SYS_HDMITX_APB] = &a9_sys_hdmitx_apb.hw, + [CLKID_SYS_I2C_M_I] = &a9_sys_i2c_m_i.hw, + [CLKID_SYS_I2C_M_G] = &a9_sys_i2c_m_g.hw, + [CLKID_SYS_I2C_M_H] = &a9_sys_i2c_m_h.hw, + [CLKID_SYS_HDMI20_AES] = &a9_sys_hdmi20_aes.hw, + [CLKID_SYS_CSI2_HOST] = &a9_sys_csi2_host.hw, + [CLKID_SYS_CSI2_ADAPT] = &a9_sys_csi2_adapt.hw, + [CLKID_SYS_DSPA] = &a9_sys_dspa.hw, + [CLKID_SYS_PP_DMA] = &a9_sys_pp_dma.hw, + [CLKID_SYS_PP_WRAPPER] = &a9_sys_pp_wrapper.hw, + [CLKID_SYS_VPU_INTR] = &a9_sys_vpu_intr.hw, + [CLKID_SYS_CSI2_PHY] = &a9_sys_csi2_phy.hw, + [CLKID_SYS_SARADC] = &a9_sys_saradc.hw, + [CLKID_SYS_PWM_J] = &a9_sys_pwm_j.hw, + [CLKID_SYS_PWM_I] = &a9_sys_pwm_i.hw, + [CLKID_SYS_PWM_H] = &a9_sys_pwm_h.hw, + [CLKID_SYS_PWM_N] = &a9_sys_pwm_n.hw, + [CLKID_SYS_PWM_M] = &a9_sys_pwm_m.hw, + [CLKID_SYS_PWM_L] = &a9_sys_pwm_l.hw, + [CLKID_SYS_PWM_K] = &a9_sys_pwm_k.hw, + [CLKID_SD_EMMC_A_SEL] = &a9_sd_emmc_a_sel.hw, + [CLKID_SD_EMMC_A_DIV] = &a9_sd_emmc_a_div.hw, + [CLKID_SD_EMMC_A] = &a9_sd_emmc_a.hw, + [CLKID_SD_EMMC_B_SEL] = &a9_sd_emmc_b_sel.hw, + [CLKID_SD_EMMC_B_DIV] = &a9_sd_emmc_b_div.hw, + [CLKID_SD_EMMC_B] = &a9_sd_emmc_b.hw, + [CLKID_SD_EMMC_C_SEL] = &a9_sd_emmc_c_sel.hw, + [CLKID_SD_EMMC_C_DIV] = &a9_sd_emmc_c_div.hw, + [CLKID_SD_EMMC_C] = &a9_sd_emmc_c.hw, + [CLKID_PWM_H_SEL] = &a9_pwm_h_sel.hw, + [CLKID_PWM_H_DIV] = &a9_pwm_h_div.hw, + [CLKID_PWM_H] = &a9_pwm_h.hw, + [CLKID_PWM_I_SEL] = &a9_pwm_i_sel.hw, + [CLKID_PWM_I_DIV] = &a9_pwm_i_div.hw, + [CLKID_PWM_I] = &a9_pwm_i.hw, + [CLKID_PWM_J_SEL] = &a9_pwm_j_sel.hw, + [CLKID_PWM_J_DIV] = &a9_pwm_j_div.hw, + [CLKID_PWM_J] = &a9_pwm_j.hw, + [CLKID_PWM_K_SEL] = &a9_pwm_k_sel.hw, + [CLKID_PWM_K_DIV] = &a9_pwm_k_div.hw, + [CLKID_PWM_K] = &a9_pwm_k.hw, + [CLKID_PWM_L_SEL] = &a9_pwm_l_sel.hw, + [CLKID_PWM_L_DIV] = &a9_pwm_l_div.hw, + [CLKID_PWM_L] = &a9_pwm_l.hw, + [CLKID_PWM_M_SEL] = &a9_pwm_m_sel.hw, + [CLKID_PWM_M_DIV] = &a9_pwm_m_div.hw, + [CLKID_PWM_M] = &a9_pwm_m.hw, + [CLKID_PWM_N_SEL] = &a9_pwm_n_sel.hw, + [CLKID_PWM_N_DIV] = &a9_pwm_n_div.hw, + [CLKID_PWM_N] = &a9_pwm_n.hw, + [CLKID_SPISG_SEL] = &a9_spisg_sel.hw, + [CLKID_SPISG_DIV] = &a9_spisg_div.hw, + [CLKID_SPISG] = &a9_spisg.hw, + [CLKID_SPISG1_SEL] = &a9_spisg1_sel.hw, + [CLKID_SPISG1_DIV] = &a9_spisg1_div.hw, + [CLKID_SPISG1] = &a9_spisg1.hw, + [CLKID_SPISG2_SEL] = &a9_spisg2_sel.hw, + [CLKID_SPISG2_DIV] = &a9_spisg2_div.hw, + [CLKID_SPISG2] = &a9_spisg2.hw, + [CLKID_SARADC_SEL] = &a9_saradc_sel.hw, + [CLKID_SARADC_DIV] = &a9_saradc_div.hw, + [CLKID_SARADC] = &a9_saradc.hw, + [CLKID_AMFC_SEL] = &a9_amfc_sel.hw, + [CLKID_AMFC_DIV] = &a9_amfc_div.hw, + [CLKID_AMFC] = &a9_amfc.hw, + [CLKID_NNA_SEL] = &a9_nna_sel.hw, + [CLKID_NNA_DIV] = &a9_nna_div.hw, + [CLKID_NNA] = &a9_nna.hw, + [CLKID_USB_250M_SEL] = &a9_usb_250m_sel.hw, + [CLKID_USB_250M_DIV] = &a9_usb_250m_div.hw, + [CLKID_USB_250M] = &a9_usb_250m.hw, + [CLKID_USB_48M_PRE_SEL] = &a9_usb_48m_pre_sel.hw, + [CLKID_USB_48M_PRE_DIV] = &a9_usb_48m_pre_div.hw, + [CLKID_USB_48M_PRE] = &a9_usb_48m_pre.hw, + [CLKID_PCIE_TL_SEL] = &a9_pcie_tl_sel.hw, + [CLKID_PCIE_TL_DIV] = &a9_pcie_tl_div.hw, + [CLKID_PCIE_TL] = &a9_pcie_tl.hw, + [CLKID_PCIE1_TL_SEL] = &a9_pcie1_tl_sel.hw, + [CLKID_PCIE1_TL_DIV] = &a9_pcie1_tl_div.hw, + [CLKID_PCIE1_TL] = &a9_pcie1_tl.hw, + [CLKID_CMPR_SEL] = &a9_cmpr_sel.hw, + [CLKID_CMPR_DIV] = &a9_cmpr_div.hw, + [CLKID_CMPR] = &a9_cmpr.hw, + [CLKID_DEWARPA_SEL] = &a9_dewarpa_sel.hw, + [CLKID_DEWARPA_DIV] = &a9_dewarpa_div.hw, + [CLKID_DEWARPA] = &a9_dewarpa.hw, + [CLKID_SC_PRE_SEL] = &a9_sc_pre_sel.hw, + [CLKID_SC_PRE_DIV] = &a9_sc_pre_div.hw, + [CLKID_SC_PRE] = &a9_sc_pre.hw, + [CLKID_SC] = &a9_sc.hw, + [CLKID_DPTX_APB2_SEL] = &a9_dptx_apb2_sel.hw, + [CLKID_DPTX_APB2_DIV] = &a9_dptx_apb2_div.hw, + [CLKID_DPTX_APB2] = &a9_dptx_apb2.hw, + [CLKID_DPTX_AUD_SEL] = &a9_dptx_aud_sel.hw, + [CLKID_DPTX_AUD_DIV] = &a9_dptx_aud_div.hw, + [CLKID_DPTX_AUD] = &a9_dptx_aud.hw, + [CLKID_ISP_SEL] = &a9_isp_sel.hw, + [CLKID_ISP_DIV] = &a9_isp_div.hw, + [CLKID_ISP] = &a9_isp.hw, + [CLKID_CVE_SEL] = &a9_cve_sel.hw, + [CLKID_CVE_DIV] = &a9_cve_div.hw, + [CLKID_CVE] = &a9_cve.hw, + [CLKID_VGE_SEL] = &a9_vge_sel.hw, + [CLKID_VGE_DIV] = &a9_vge_div.hw, + [CLKID_VGE] = &a9_vge.hw, + [CLKID_PP_SEL] = &a9_pp_sel.hw, + [CLKID_PP_DIV] = &a9_pp_div.hw, + [CLKID_PP] = &a9_pp.hw, + [CLKID_GLB_SEL] = &a9_glb_sel.hw, + [CLKID_GLB_DIV] = &a9_glb_div.hw, + [CLKID_GLB] = &a9_glb.hw, + [CLKID_USB_48M_DUALDIV_IN] = &a9_usb_48m_dualdiv_in.hw, + [CLKID_USB_48M_DUALDIV_DIV] = &a9_usb_48m_dualdiv_div.hw, + [CLKID_USB_48M_DUALDIV_SEL] = &a9_usb_48m_dualdiv_sel.hw, + [CLKID_USB_48M_DUALDIV] = &a9_usb_48m_dualdiv.hw, + [CLKID_USB_48M] = &a9_usb_48m.hw, + [CLKID_CAN_PE_SEL] = &a9_can_pe_sel.hw, + [CLKID_CAN_PE_DIV] = &a9_can_pe_div.hw, + [CLKID_CAN_PE] = &a9_can_pe.hw, + [CLKID_CAN1_PE_SEL] = &a9_can1_pe_sel.hw, + [CLKID_CAN1_PE_DIV] = &a9_can1_pe_div.hw, + [CLKID_CAN1_PE] = &a9_can1_pe.hw, + [CLKID_CAN_FILTER_SEL] = &a9_can_filter_sel.hw, + [CLKID_CAN_FILTER_DIV] = &a9_can_filter_div.hw, + [CLKID_CAN_FILTER] = &a9_can_filter.hw, + [CLKID_CAN1_FILTER_SEL] = &a9_can1_filter_sel.hw, + [CLKID_CAN1_FILTER_DIV] = &a9_can1_filter_div.hw, + [CLKID_CAN1_FILTER] = &a9_can1_filter.hw, + [CLKID_I3C_SEL] = &a9_i3c_sel.hw, + [CLKID_I3C_DIV] = &a9_i3c_div.hw, + [CLKID_I3C] = &a9_i3c.hw, + [CLKID_TS_DIV] = &a9_ts_div.hw, + [CLKID_TS] = &a9_ts.hw, + [CLKID_ETH_125M_DIV] = &a9_eth_125m_div.hw, + [CLKID_ETH_125M] = &a9_eth_125m.hw, + [CLKID_ETH_RMII_SEL] = &a9_eth_rmii_sel.hw, + [CLKID_ETH_RMII_DIV] = &a9_eth_rmii_div.hw, + [CLKID_ETH_RMII] = &a9_eth_rmii.hw, + [CLKID_GEN_SEL] = &a9_gen_sel.hw, + [CLKID_GEN_DIV] = &a9_gen_div.hw, + [CLKID_GEN] = &a9_gen.hw, + [CLKID_CLK24M_IN] = &a9_24m_in.hw, + [CLKID_CLK12_24M] = &a9_12_24m.hw, + [CLKID_MALI_0_SEL] = &a9_mali_0_sel.hw, + [CLKID_MALI_0_DIV] = &a9_mali_0_div.hw, + [CLKID_MALI_0] = &a9_mali_0.hw, + [CLKID_MALI_1_SEL] = &a9_mali_1_sel.hw, + [CLKID_MALI_1_DIV] = &a9_mali_1_div.hw, + [CLKID_MALI_1] = &a9_mali_1.hw, + [CLKID_MALI] = &a9_mali.hw, + [CLKID_MALI_STACK_0_SEL] = &a9_mali_stack_0_sel.hw, + [CLKID_MALI_STACK_0_DIV] = &a9_mali_stack_0_div.hw, + [CLKID_MALI_STACK_0] = &a9_mali_stack_0.hw, + [CLKID_MALI_STACK_1_SEL] = &a9_mali_stack_1_sel.hw, + [CLKID_MALI_STACK_1_DIV] = &a9_mali_stack_1_div.hw, + [CLKID_MALI_STACK_1] = &a9_mali_stack_1.hw, + [CLKID_MALI_STACK] = &a9_mali_stack.hw, + [CLKID_DSPA_0_SEL] = &a9_dspa_0_sel.hw, + [CLKID_DSPA_0_DIV] = &a9_dspa_0_div.hw, + [CLKID_DSPA_0] = &a9_dspa_0.hw, + [CLKID_DSPA_1_SEL] = &a9_dspa_1_sel.hw, + [CLKID_DSPA_1_DIV] = &a9_dspa_1_div.hw, + [CLKID_DSPA_1] = &a9_dspa_1.hw, + [CLKID_DSPA] = &a9_dspa.hw, + [CLKID_HEVCF_0_SEL] = &a9_hevcf_0_sel.hw, + [CLKID_HEVCF_0_DIV] = &a9_hevcf_0_div.hw, + [CLKID_HEVCF_0] = &a9_hevcf_0.hw, + [CLKID_HEVCF_1_SEL] = &a9_hevcf_1_sel.hw, + [CLKID_HEVCF_1_DIV] = &a9_hevcf_1_div.hw, + [CLKID_HEVCF_1] = &a9_hevcf_1.hw, + [CLKID_HEVCF] = &a9_hevcf.hw, + [CLKID_HCODEC_0_SEL] = &a9_hcodec_0_sel.hw, + [CLKID_HCODEC_0_DIV] = &a9_hcodec_0_div.hw, + [CLKID_HCODEC_0] = &a9_hcodec_0.hw, + [CLKID_HCODEC_1_SEL] = &a9_hcodec_1_sel.hw, + [CLKID_HCODEC_1_DIV] = &a9_hcodec_1_div.hw, + [CLKID_HCODEC_1] = &a9_hcodec_1.hw, + [CLKID_HCODEC] = &a9_hcodec.hw, + [CLKID_VPU_0_SEL] = &a9_vpu_0_sel.hw, + [CLKID_VPU_0_DIV] = &a9_vpu_0_div.hw, + [CLKID_VPU_0] = &a9_vpu_0.hw, + [CLKID_VPU_1_SEL] = &a9_vpu_1_sel.hw, + [CLKID_VPU_1_DIV] = &a9_vpu_1_div.hw, + [CLKID_VPU_1] = &a9_vpu_1.hw, + [CLKID_VPU] = &a9_vpu.hw, + [CLKID_VAPB_0_SEL] = &a9_vapb_0_sel.hw, + [CLKID_VAPB_0_DIV] = &a9_vapb_0_div.hw, + [CLKID_VAPB_0] = &a9_vapb_0.hw, + [CLKID_VAPB_1_SEL] = &a9_vapb_1_sel.hw, + [CLKID_VAPB_1_DIV] = &a9_vapb_1_div.hw, + [CLKID_VAPB_1] = &a9_vapb_1.hw, + [CLKID_VAPB] = &a9_vapb.hw, + [CLKID_GE2D] = &a9_ge2d.hw, + [CLKID_VPU_CLKB_TMP_SEL] = &a9_vpu_clkb_tmp_sel.hw, + [CLKID_VPU_CLKB_TMP_DIV] = &a9_vpu_clkb_tmp_div.hw, + [CLKID_VPU_CLKB_TMP] = &a9_vpu_clkb_tmp.hw, + [CLKID_VPU_CLKB_DIV] = &a9_vpu_clkb_div.hw, + [CLKID_VPU_CLKB] = &a9_vpu_clkb.hw, + [CLKID_HDMITX_SYS_SEL] = &a9_hdmitx_sys_sel.hw, + [CLKID_HDMITX_SYS_DIV] = &a9_hdmitx_sys_div.hw, + [CLKID_HDMITX_SYS] = &a9_hdmitx_sys.hw, + [CLKID_HDMITX_PRIF_SEL] = &a9_hdmitx_prif_sel.hw, + [CLKID_HDMITX_PRIF_DIV] = &a9_hdmitx_prif_div.hw, + [CLKID_HDMITX_PRIF] = &a9_hdmitx_prif.hw, + [CLKID_HDMITX_200M_SEL] = &a9_hdmitx_200m_sel.hw, + [CLKID_HDMITX_200M_DIV] = &a9_hdmitx_200m_div.hw, + [CLKID_HDMITX_200M] = &a9_hdmitx_200m.hw, + [CLKID_HDMITX_AUD_SEL] = &a9_hdmitx_aud_sel.hw, + [CLKID_HDMITX_AUD_DIV] = &a9_hdmitx_aud_div.hw, + [CLKID_HDMITX_AUD] = &a9_hdmitx_aud.hw, + [CLKID_HDMIRX_5M_SEL] = &a9_hdmirx_5m_sel.hw, + [CLKID_HDMIRX_5M_DIV] = &a9_hdmirx_5m_div.hw, + [CLKID_HDMIRX_5M] = &a9_hdmirx_5m.hw, + [CLKID_HDMIRX_2M_SEL] = &a9_hdmirx_2m_sel.hw, + [CLKID_HDMIRX_2M_DIV] = &a9_hdmirx_2m_div.hw, + [CLKID_HDMIRX_2M] = &a9_hdmirx_2m.hw, + [CLKID_HDMIRX_CFG_SEL] = &a9_hdmirx_cfg_sel.hw, + [CLKID_HDMIRX_CFG_DIV] = &a9_hdmirx_cfg_div.hw, + [CLKID_HDMIRX_CFG] = &a9_hdmirx_cfg.hw, + [CLKID_HDMIRX_HDCP2X_SEL] = &a9_hdmirx_hdcp2x_sel.hw, + [CLKID_HDMIRX_HDCP2X_DIV] = &a9_hdmirx_hdcp2x_div.hw, + [CLKID_HDMIRX_HDCP2X] = &a9_hdmirx_hdcp2x.hw, + [CLKID_HDMIRX_ACR_REF_SEL] = &a9_hdmirx_acr_ref_sel.hw, + [CLKID_HDMIRX_ACR_REF_DIV] = &a9_hdmirx_acr_ref_div.hw, + [CLKID_HDMIRX_ACR_REF] = &a9_hdmirx_acr_ref.hw, + [CLKID_HDMIRX_METER_SEL] = &a9_hdmirx_meter_sel.hw, + [CLKID_HDMIRX_METER_DIV] = &a9_hdmirx_meter_div.hw, + [CLKID_HDMIRX_METER] = &a9_hdmirx_meter.hw, + [CLKID_VID_LOCK_SEL] = &a9_vid_lock_sel.hw, + [CLKID_VID_LOCK_DIV] = &a9_vid_lock_div.hw, + [CLKID_VID_LOCK] = &a9_vid_lock.hw, + [CLKID_VDIN_MEAS_SEL] = &a9_vdin_meas_sel.hw, + [CLKID_VDIN_MEAS_DIV] = &a9_vdin_meas_div.hw, + [CLKID_VDIN_MEAS] = &a9_vdin_meas.hw, + [CLKID_VID_PLL_DIV] = &a9_vid_pll_div.hw, + [CLKID_VID_PLL_SEL] = &a9_vid_pll_sel.hw, + [CLKID_VID_PLL] = &a9_vid_pll.hw, + [CLKID_VID_PLL_VCLK] = &a9_vid_pll_vclk.hw, + [CLKID_VCLK_SEL] = &a9_vclk_sel.hw, + [CLKID_VCLK_IN] = &a9_vclk_in.hw, + [CLKID_VCLK_DIV] = &a9_vclk_div.hw, + [CLKID_VCLK] = &a9_vclk.hw, + [CLKID_VCLK_DIV1_EN] = &a9_vclk_div1_en.hw, + [CLKID_VCLK_DIV2_EN] = &a9_vclk_div2_en.hw, + [CLKID_VCLK_DIV2] = &a9_vclk_div2.hw, + [CLKID_VCLK_DIV4_EN] = &a9_vclk_div4_en.hw, + [CLKID_VCLK_DIV4] = &a9_vclk_div4.hw, + [CLKID_VCLK_DIV6_EN] = &a9_vclk_div6_en.hw, + [CLKID_VCLK_DIV6] = &a9_vclk_div6.hw, + [CLKID_VCLK_DIV12_EN] = &a9_vclk_div12_en.hw, + [CLKID_VCLK_DIV12] = &a9_vclk_div12.hw, + [CLKID_VCLK2_SEL] = &a9_vclk2_sel.hw, + [CLKID_VCLK2_IN] = &a9_vclk2_in.hw, + [CLKID_VCLK2_DIV] = &a9_vclk2_div.hw, + [CLKID_VCLK2] = &a9_vclk2.hw, + [CLKID_VCLK2_DIV1_EN] = &a9_vclk2_div1_en.hw, + [CLKID_VCLK2_DIV2_EN] = &a9_vclk2_div2_en.hw, + [CLKID_VCLK2_DIV2] = &a9_vclk2_div2.hw, + [CLKID_VCLK2_DIV4_EN] = &a9_vclk2_div4_en.hw, + [CLKID_VCLK2_DIV4] = &a9_vclk2_div4.hw, + [CLKID_VCLK2_DIV6_EN] = &a9_vclk2_div6_en.hw, + [CLKID_VCLK2_DIV6] = &a9_vclk2_div6.hw, + [CLKID_VCLK2_DIV12_EN] = &a9_vclk2_div12_en.hw, + [CLKID_VCLK2_DIV12] = &a9_vclk2_div12.hw, + [CLKID_VDAC_SEL] = &a9_vdac_sel.hw, + [CLKID_VDAC] = &a9_vdac.hw, + [CLKID_ENC_SEL] = &a9_enc_sel.hw, + [CLKID_ENC] = &a9_enc.hw, + [CLKID_ENC1_SEL] = &a9_enc1_sel.hw, + [CLKID_ENC1] = &a9_enc1.hw, + [CLKID_HDMITX_PIXEL_SEL] = &a9_hdmitx_pixel_sel.hw, + [CLKID_HDMITX_PIXEL] = &a9_hdmitx_pixel.hw, + [CLKID_HDMITX_FE_SEL] = &a9_hdmitx_fe_sel.hw, + [CLKID_HDMITX_FE] = &a9_hdmitx_fe.hw, + [CLKID_HDMITX1_PIXEL_SEL] = &a9_hdmitx1_pixel_sel.hw, + [CLKID_HDMITX1_PIXEL] = &a9_hdmitx1_pixel.hw, + [CLKID_HDMITX1_FE_SEL] = &a9_hdmitx1_fe_sel.hw, + [CLKID_HDMITX1_FE] = &a9_hdmitx1_fe.hw, + [CLKID_CSI_PHY_SEL] = &a9_csi_phy_sel.hw, + [CLKID_CSI_PHY_DIV] = &a9_csi_phy_div.hw, + [CLKID_CSI_PHY] = &a9_csi_phy.hw, + [CLKID_DSI_MEAS_SEL] = &a9_dsi_meas_sel.hw, + [CLKID_DSI_MEAS_DIV] = &a9_dsi_meas_div.hw, + [CLKID_DSI_MEAS] = &a9_dsi_meas.hw, + [CLKID_DSI_B_MEAS_SEL] = &a9_dsi_b_meas_sel.hw, + [CLKID_DSI_B_MEAS_DIV] = &a9_dsi_b_meas_div.hw, + [CLKID_DSI_B_MEAS] = &a9_dsi_b_meas.hw, +}; + +static const struct meson_clkc_data a9_peripherals_clkc_data = { + .hw_clks = { + .hws = a9_peripherals_hw_clks, + .num = ARRAY_SIZE(a9_peripherals_hw_clks), + }, +}; + +static const struct of_device_id a9_peripherals_clkc_match_table[] = { + { + .compatible = "amlogic,a9-peripherals-clkc", + .data = &a9_peripherals_clkc_data, + }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, a9_peripherals_clkc_match_table); + +static struct platform_driver a9_peripherals_clkc_driver = { + .probe = meson_clkc_mmio_probe, + .driver = { + .name = "a9-peripherals-clkc", + .of_match_table = a9_peripherals_clkc_match_table, + }, +}; +module_platform_driver(a9_peripherals_clkc_driver); + +MODULE_DESCRIPTION("Amlogic A9 Peripherals Clock Controller driver"); +MODULE_AUTHOR("Jian Hu "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_MESON"); -- 2.47.1 From devnull+jian.hu.amlogic.com at kernel.org Mon May 11 05:47:29 2026 From: devnull+jian.hu.amlogic.com at kernel.org (Jian Hu via B4 Relay) Date: Mon, 11 May 2026 20:47:29 +0800 Subject: [PATCH 07/10] clk: amlogic: Support POWER_OF_TWO for PLL pre-divider In-Reply-To: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> Message-ID: <20260511-b4-a9_clk-v1-7-41cb4071b7c9@amlogic.com> From: Jian Hu The A9 PLL pre-divider uses a division factor of 2^n to ensure a clock duty cycle of 50% after predivision. Add flag 'CLK_MESON_PLL_N_POWER_OF_TWO' to indicate that the PLL pre-divider division factor is 2^n. Signed-off-by: Jian Hu --- drivers/clk/meson/clk-pll.c | 28 +++++++++++++++++++++++----- drivers/clk/meson/clk-pll.h | 2 ++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c index 8568ad6ba7b6..49483e431d44 100644 --- a/drivers/clk/meson/clk-pll.c +++ b/drivers/clk/meson/clk-pll.c @@ -66,6 +66,9 @@ static unsigned long __pll_params_to_rate(unsigned long parent_rate, rate += DIV_ROUND_UP_ULL(frac_rate, frac_max); } + if (pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO) + n = 1 << n; + return DIV_ROUND_UP_ULL(rate, n); } @@ -83,7 +86,7 @@ static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw, * it would result in a division by zero. The rate can't be * calculated in this case */ - if (n == 0) + if (n == 0 && !(pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO)) return 0; m = meson_parm_read(clk->map, &pll->m); @@ -103,7 +106,12 @@ static unsigned int __pll_params_with_frac(unsigned long rate, { unsigned int frac_max = pll->frac_max ? pll->frac_max : (1 << pll->frac.width); - u64 val = (u64)rate * n; + u64 val; + + if (pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO) + n = 1 << n; + + val = (u64)rate * n; /* Bail out if we are already over the requested rate */ if (rate < parent_rate * m / n) @@ -142,7 +150,8 @@ static int meson_clk_get_pll_table_index(unsigned int index, unsigned int *n, struct meson_clk_pll_data *pll) { - if (!pll->table[index].n) + if (!pll->table[index].n && + !(pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO)) return -EINVAL; *m = pll->table[index].m; @@ -156,7 +165,12 @@ static unsigned int meson_clk_get_pll_range_m(unsigned long rate, unsigned int n, struct meson_clk_pll_data *pll) { - u64 val = (u64)rate * n; + u64 val; + + if (pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO) + n = 1 << n; + + val = (u64)rate * n; if (__pll_round_closest_mult(pll)) return DIV_ROUND_CLOSEST_ULL(val, parent_rate); @@ -173,11 +187,15 @@ static int meson_clk_get_pll_range_index(unsigned long rate, { *n = index + 1; + if ((pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO)) + *n = index; + /* Check the predivider range */ if (*n >= (1 << pll->n.width)) return -EINVAL; - if (*n == 1) { + if ((*n == 1 && !(pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO)) || + (*n == 0 && (pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO))) { /* Get the boundaries out the way */ if (rate <= pll->range->min * parent_rate) { *m = pll->range->min; diff --git a/drivers/clk/meson/clk-pll.h b/drivers/clk/meson/clk-pll.h index 1be7e6e77631..60b2772a54c8 100644 --- a/drivers/clk/meson/clk-pll.h +++ b/drivers/clk/meson/clk-pll.h @@ -33,6 +33,8 @@ struct pll_mult_range { #define CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH BIT(2) /* rst signal is active-low (Power-on reset) */ #define CLK_MESON_PLL_RST_ACTIVE_LOW BIT(3) +/* The division factor of the PLL pre-divider is 2^n */ +#define CLK_MESON_PLL_N_POWER_OF_TWO BIT(4) struct meson_clk_pll_data { struct parm en; -- 2.47.1 From devnull+jian.hu.amlogic.com at kernel.org Mon May 11 05:47:30 2026 From: devnull+jian.hu.amlogic.com at kernel.org (Jian Hu via B4 Relay) Date: Mon, 11 May 2026 20:47:30 +0800 Subject: [PATCH 08/10] clk: amlogic: Add A9 PLL clock controller driver In-Reply-To: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> Message-ID: <20260511-b4-a9_clk-v1-8-41cb4071b7c9@amlogic.com> From: Jian Hu Add the PLL clock controller driver for the Amlogic A9 SoC family. Signed-off-by: Jian Hu --- drivers/clk/meson/Kconfig | 13 + drivers/clk/meson/Makefile | 1 + drivers/clk/meson/a9-pll.c | 831 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 845 insertions(+) diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig index cf8cf3f9e4ee..3549e67d6988 100644 --- a/drivers/clk/meson/Kconfig +++ b/drivers/clk/meson/Kconfig @@ -132,6 +132,19 @@ config COMMON_CLK_A1_PERIPHERALS device, A1 SoC Family. Say Y if you want A1 Peripherals clock controller to work. +config COMMON_CLK_A9_PLL + tristate "Amlogic A9 SoC PLL controller support" + depends on ARM64 + default ARCH_MESON + select COMMON_CLK_MESON_REGMAP + select COMMON_CLK_MESON_CLKC_UTILS + select COMMON_CLK_MESON_PLL + imply COMMON_CLK_SCMI + help + Support for the PLL clock controller on Amlogic A311Y3 based + device, AKA A9. PLLs are required by most peripheral to operate. + Say Y if you want A9 PLL clock controller to work. + config COMMON_CLK_C3_PLL tristate "Amlogic C3 PLL clock controller" depends on ARM64 diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile index c6719694a242..77636033061f 100644 --- a/drivers/clk/meson/Makefile +++ b/drivers/clk/meson/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_COMMON_CLK_AXG) += axg.o axg-aoclk.o obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o +obj-$(CONFIG_COMMON_CLK_A9_PLL) += a9-pll.o obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o diff --git a/drivers/clk/meson/a9-pll.c b/drivers/clk/meson/a9-pll.c new file mode 100644 index 000000000000..84b591c3afff --- /dev/null +++ b/drivers/clk/meson/a9-pll.c @@ -0,0 +1,831 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2026 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include "clk-regmap.h" +#include "clk-pll.h" +#include "meson-clkc-utils.h" + +#define GP0PLL_CTRL0 0x00 +#define GP0PLL_CTRL1 0x04 +#define GP0PLL_CTRL2 0x08 +#define GP0PLL_CTRL3 0x0c +#define GP0PLL_CTRL4 0x10 + +/* HIFI0 and HIFI1 share the same IP and register offset layout. */ +#define HIFIPLL_CTRL0 0x00 +#define HIFIPLL_CTRL1 0x04 +#define HIFIPLL_CTRL2 0x08 +#define HIFIPLL_CTRL3 0x0c +#define HIFIPLL_CTRL4 0x10 + +/* MCLK0 and MCLK1 share the same IP and register offset layout. */ +#define MCLKPLL_CTRL0 0x00 +#define MCLKPLL_CTRL1 0x04 +#define MCLKPLL_CTRL2 0x08 +#define MCLKPLL_CTRL3 0x0c +#define MCLKPLL_CTRL4 0x10 + +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata) \ + MESON_COMP_SEL(a9_, _name, _reg, _shift, _mask, _pdata, NULL, 0, 0) + +#define A9_COMP_DIV(_name, _reg, _shift, _width) \ + MESON_COMP_DIV(a9_, _name, _reg, _shift, _width, 0, CLK_SET_RATE_PARENT) + +#define A9_COMP_GATE(_name, _reg, _bit) \ + MESON_COMP_GATE(a9_, _name, _reg, _bit, CLK_SET_RATE_PARENT) + +/* + * Compared with previous SoC PLLs, the A9 PLL input path has an inherent + * 2-divider. The N pre-divider follows the same calculation rule as OD, + * where the pre-divider ratio equals 2^N. + * + * A9 PLL is composed as follows: + * + * PLL + * +---------------------------------+ + * | | + * | +--+ | + * in/2 >>---[ /2^N ]-->| | +-----+ | + * | | |------| DCO |----->> out + * | +--------->| | +--v--+ | + * | | +--+ | | + * | | | | + * | +--[ *(M + (F/Fmax) ]<--+ | + * | | + * +---------------------------------+ + * + * out = in / 2 * (m + frac / frac_max) / 2^n + */ + +static struct clk_fixed_factor a9_gp0_in_div2_div = { + .mult = 1, + .div = 2, + .hw.init = &(struct clk_init_data){ + .name = "gp0_in_div2_div", + .ops = &clk_fixed_factor_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "in0", + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_gp0_in_div2 = { + .data = &(struct clk_regmap_gate_data) { + .offset = GP0PLL_CTRL0, + .bit_idx = 27, + }, + .hw.init = &(struct clk_init_data) { + .name = "gp0_in_div2", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_gp0_in_div2_div.hw + }, + .num_parents = 1, + }, +}; + +/* The output frequency range of the A9 PLL_DCO is 1.4 GHz to 2.8 GHz. */ +static const struct pll_mult_range a9_pll_mult_range = { + .min = 117, + .max = 233, +}; + +static const struct reg_sequence a9_gp0_pll_init_regs[] = { + { .reg = GP0PLL_CTRL0, .def = 0x00010000 }, + { .reg = GP0PLL_CTRL1, .def = 0x11480000 }, + { .reg = GP0PLL_CTRL2, .def = 0x1219b010 }, + { .reg = GP0PLL_CTRL3, .def = 0x00008010 } +}; + +static struct clk_regmap a9_gp0_pll_dco = { + .data = &(struct meson_clk_pll_data) { + .en = { + .reg_off = GP0PLL_CTRL0, + .shift = 28, + .width = 1, + }, + .m = { + .reg_off = GP0PLL_CTRL0, + .shift = 0, + .width = 9, + }, + .n = { + .reg_off = GP0PLL_CTRL0, + .shift = 12, + .width = 3, + }, + .frac = { + .reg_off = GP0PLL_CTRL1, + .shift = 0, + .width = 17, + }, + .l = { + .reg_off = GP0PLL_CTRL0, + .shift = 31, + .width = 1, + }, + .rst = { + .reg_off = GP0PLL_CTRL0, + .shift = 29, + .width = 1, + }, + .l_detect = { + .reg_off = GP0PLL_CTRL0, + .shift = 30, + .width = 1, + }, + .range = &a9_pll_mult_range, + .init_regs = a9_gp0_pll_init_regs, + .init_count = ARRAY_SIZE(a9_gp0_pll_init_regs), + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | + CLK_MESON_PLL_N_POWER_OF_TWO | + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, + }, + .hw.init = &(struct clk_init_data) { + .name = "gp0_pll_dco", + .ops = &meson_clk_pll_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_gp0_in_div2.hw + }, + .num_parents = 1, + }, +}; + +/* For gp0, hifi and mclk pll, the maximum value of od is 4. */ +static const struct clk_div_table a9_pll_od_table[] = { + { 0, 1 }, + { 1, 2 }, + { 2, 4 }, + { 3, 8 }, + { 4, 16 }, + { /* sentinel */ } +}; + +static struct clk_regmap a9_gp0_pll = { + .data = &(struct clk_regmap_div_data) { + .offset = GP0PLL_CTRL0, + .shift = 20, + .width = 3, + .table = a9_pll_od_table, + }, + .hw.init = &(struct clk_init_data) { + .name = "gp0_pll", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_gp0_pll_dco.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_fixed_factor a9_hifi0_in_div2_div = { + .mult = 1, + .div = 2, + .hw.init = &(struct clk_init_data){ + .name = "hifi0_in_div2_div", + .ops = &clk_fixed_factor_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "in0", + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_hifi0_in_div2 = { + .data = &(struct clk_regmap_gate_data) { + .offset = HIFIPLL_CTRL0, + .bit_idx = 27, + }, + .hw.init = &(struct clk_init_data) { + .name = "hifi0_in_div2", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hifi0_in_div2_div.hw + }, + .num_parents = 1, + }, +}; + +static const struct reg_sequence a9_hifi0_pll_init_regs[] = { + { .reg = HIFIPLL_CTRL0, .def = 0x00010000 }, + { .reg = HIFIPLL_CTRL1, .def = 0x11480000 }, + { .reg = HIFIPLL_CTRL2, .def = 0x1219b010 }, + { .reg = HIFIPLL_CTRL3, .def = 0x00008010 } +}; + +static struct clk_regmap a9_hifi0_pll_dco = { + .data = &(struct meson_clk_pll_data) { + .en = { + .reg_off = HIFIPLL_CTRL0, + .shift = 28, + .width = 1, + }, + .m = { + .reg_off = HIFIPLL_CTRL0, + .shift = 0, + .width = 9, + }, + .n = { + .reg_off = HIFIPLL_CTRL0, + .shift = 12, + .width = 3, + }, + .frac = { + .reg_off = HIFIPLL_CTRL1, + .shift = 0, + .width = 17, + }, + .l = { + .reg_off = HIFIPLL_CTRL0, + .shift = 31, + .width = 1, + }, + .rst = { + .reg_off = HIFIPLL_CTRL0, + .shift = 29, + .width = 1, + }, + .l_detect = { + .reg_off = HIFIPLL_CTRL0, + .shift = 30, + .width = 1, + }, + .range = &a9_pll_mult_range, + .init_regs = a9_hifi0_pll_init_regs, + .init_count = ARRAY_SIZE(a9_hifi0_pll_init_regs), + .frac_max = 100000, + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | + CLK_MESON_PLL_N_POWER_OF_TWO | + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, + }, + .hw.init = &(struct clk_init_data) { + .name = "hifi0_pll_dco", + .ops = &meson_clk_pll_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hifi0_in_div2.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_hifi0_pll = { + .data = &(struct clk_regmap_div_data) { + .offset = HIFIPLL_CTRL0, + .shift = 20, + .width = 3, + .table = a9_pll_od_table, + }, + .hw.init = &(struct clk_init_data) { + .name = "hifi0_pll", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hifi0_pll_dco.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static struct clk_fixed_factor a9_hifi1_in_div2_div = { + .mult = 1, + .div = 2, + .hw.init = &(struct clk_init_data){ + .name = "hifi1_in_div2_div", + .ops = &clk_fixed_factor_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "in0", + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_hifi1_in_div2 = { + .data = &(struct clk_regmap_gate_data) { + .offset = HIFIPLL_CTRL0, + .bit_idx = 27, + }, + .hw.init = &(struct clk_init_data) { + .name = "hifi1_in_div2", + .ops = &clk_regmap_gate_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hifi1_in_div2_div.hw + }, + .num_parents = 1, + }, +}; + +static const struct reg_sequence a9_hifi1_pll_init_regs[] = { + { .reg = HIFIPLL_CTRL0, .def = 0x00010000 }, + { .reg = HIFIPLL_CTRL1, .def = 0x11480000 }, + { .reg = HIFIPLL_CTRL2, .def = 0x1219b011 }, + { .reg = HIFIPLL_CTRL3, .def = 0x00008010 } +}; + +static struct clk_regmap a9_hifi1_pll_dco = { + .data = &(struct meson_clk_pll_data) { + .en = { + .reg_off = HIFIPLL_CTRL0, + .shift = 28, + .width = 1, + }, + .m = { + .reg_off = HIFIPLL_CTRL0, + .shift = 0, + .width = 9, + }, + .n = { + .reg_off = HIFIPLL_CTRL0, + .shift = 12, + .width = 3, + }, + .frac = { + .reg_off = HIFIPLL_CTRL1, + .shift = 0, + .width = 17, + }, + .l = { + .reg_off = HIFIPLL_CTRL0, + .shift = 31, + .width = 1, + }, + .rst = { + .reg_off = HIFIPLL_CTRL0, + .shift = 29, + .width = 1, + }, + .l_detect = { + .reg_off = HIFIPLL_CTRL0, + .shift = 30, + .width = 1, + }, + .range = &a9_pll_mult_range, + .init_regs = a9_hifi1_pll_init_regs, + .init_count = ARRAY_SIZE(a9_hifi1_pll_init_regs), + .frac_max = 100000, + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | + CLK_MESON_PLL_N_POWER_OF_TWO | + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, + }, + .hw.init = &(struct clk_init_data) { + .name = "hifi1_pll_dco", + .ops = &meson_clk_pll_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hifi1_in_div2.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_hifi1_pll = { + .data = &(struct clk_regmap_div_data) { + .offset = HIFIPLL_CTRL0, + .shift = 20, + .width = 3, + .table = a9_pll_od_table, + }, + .hw.init = &(struct clk_init_data) { + .name = "hifi1_pll", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_hifi1_pll_dco.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +/* + * Unlike GP0 and HIFI PLLs, the input divider 2 of MCLK PLL is + * enabled by default and has no enable control bit. + */ +static struct clk_fixed_factor a9_mclk0_in_div2 = { + .mult = 1, + .div = 2, + .hw.init = &(struct clk_init_data){ + .name = "mclk0_in_div2_div", + .ops = &clk_fixed_factor_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "in0", + }, + .num_parents = 1, + }, +}; + +static const struct reg_sequence a9_mclk0_pll_init_regs[] = { + { .reg = MCLKPLL_CTRL1, .def = 0x00422000 }, + { .reg = MCLKPLL_CTRL2, .def = 0x60000100 }, + { .reg = MCLKPLL_CTRL3, .def = 0x02000200 }, + { .reg = MCLKPLL_CTRL4, .def = 0xd616d616 } +}; + +static struct clk_regmap a9_mclk0_pll_dco = { + .data = &(struct meson_clk_pll_data) { + .en = { + .reg_off = MCLKPLL_CTRL0, + .shift = 28, + .width = 1, + }, + .m = { + .reg_off = MCLKPLL_CTRL0, + .shift = 0, + .width = 9, + }, + .n = { + .reg_off = MCLKPLL_CTRL0, + .shift = 12, + .width = 3, + }, + .l = { + .reg_off = MCLKPLL_CTRL0, + .shift = 31, + .width = 1, + }, + .rst = { + .reg_off = MCLKPLL_CTRL0, + .shift = 29, + .width = 1, + }, + .l_detect = { + .reg_off = MCLKPLL_CTRL0, + .shift = 30, + .width = 1, + }, + .range = &a9_pll_mult_range, + .init_regs = a9_mclk0_pll_init_regs, + .init_count = ARRAY_SIZE(a9_mclk0_pll_init_regs), + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | + CLK_MESON_PLL_N_POWER_OF_TWO | + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, + }, + .hw.init = &(struct clk_init_data) { + .name = "mclk0_pll_dco", + .ops = &meson_clk_pll_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mclk0_in_div2.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_mclk0_0_pll = { + .data = &(struct clk_regmap_div_data) { + .offset = MCLKPLL_CTRL3, + .shift = 0, + .width = 3, + .table = a9_pll_od_table, + }, + .hw.init = &(struct clk_init_data) { + .name = "mclk0_0_pll", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mclk0_pll_dco.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_mclk0_0_pre = { + .data = &(struct clk_regmap_div_data) { + .offset = MCLKPLL_CTRL3, + .shift = 3, + .width = 5, + .flags = CLK_DIVIDER_MAX_AT_ZERO, + }, + .hw.init = &(struct clk_init_data) { + .name = "mclk0_0_pre", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mclk0_0_pll.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_mclk0_0_parents[] = { + { .hw = &a9_mclk0_0_pre.hw }, + { .fw_name = "in0" }, + { .fw_name = "in1" }, + { .fw_name = "in2" } +}; + +static A9_COMP_SEL(mclk0_0, MCLKPLL_CTRL3, 12, 0x3, a9_mclk0_0_parents); +static A9_COMP_DIV(mclk0_0, MCLKPLL_CTRL3, 10, 1); +static A9_COMP_GATE(mclk0_0, MCLKPLL_CTRL3, 8); + +static struct clk_regmap a9_mclk0_1_pll = { + .data = &(struct clk_regmap_div_data) { + .offset = MCLKPLL_CTRL3, + .shift = 16, + .width = 3, + .table = a9_pll_od_table, + }, + .hw.init = &(struct clk_init_data) { + .name = "mclk0_1_pll", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mclk0_pll_dco.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_mclk0_1_pre = { + .data = &(struct clk_regmap_div_data) { + .offset = MCLKPLL_CTRL3, + .shift = 19, + .width = 5, + .flags = CLK_DIVIDER_MAX_AT_ZERO, + }, + .hw.init = &(struct clk_init_data) { + .name = "mclk0_1_pre", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mclk0_1_pll.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_mclk0_1_parents[] = { + { .hw = &a9_mclk0_1_pre.hw }, + { .fw_name = "in0" }, + { .fw_name = "in1" }, + { .fw_name = "in2" } +}; + +static A9_COMP_SEL(mclk0_1, MCLKPLL_CTRL3, 28, 0x3, a9_mclk0_1_parents); +static A9_COMP_DIV(mclk0_1, MCLKPLL_CTRL3, 26, 1); +static A9_COMP_GATE(mclk0_1, MCLKPLL_CTRL3, 24); + +static struct clk_fixed_factor a9_mclk1_in_div2 = { + .mult = 1, + .div = 2, + .hw.init = &(struct clk_init_data){ + .name = "mclk1_in_div2", + .ops = &clk_fixed_factor_ops, + .parent_data = &(const struct clk_parent_data) { + .fw_name = "in0", + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_mclk1_pll_dco = { + .data = &(struct meson_clk_pll_data) { + .en = { + .reg_off = MCLKPLL_CTRL0, + .shift = 28, + .width = 1, + }, + .m = { + .reg_off = MCLKPLL_CTRL0, + .shift = 0, + .width = 9, + }, + .n = { + .reg_off = MCLKPLL_CTRL0, + .shift = 12, + .width = 3, + }, + .l = { + .reg_off = MCLKPLL_CTRL0, + .shift = 31, + .width = 1, + }, + .rst = { + .reg_off = MCLKPLL_CTRL0, + .shift = 29, + .width = 1, + }, + .l_detect = { + .reg_off = MCLKPLL_CTRL0, + .shift = 30, + .width = 1, + }, + .range = &a9_pll_mult_range, + .init_regs = a9_mclk0_pll_init_regs, + .init_count = ARRAY_SIZE(a9_mclk0_pll_init_regs), + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | + CLK_MESON_PLL_N_POWER_OF_TWO | + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, + }, + .hw.init = &(struct clk_init_data) { + .name = "mclk1_pll_dco", + .ops = &meson_clk_pll_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mclk1_in_div2.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_mclk1_0_pll = { + .data = &(struct clk_regmap_div_data) { + .offset = MCLKPLL_CTRL3, + .shift = 0, + .width = 3, + .table = a9_pll_od_table, + }, + .hw.init = &(struct clk_init_data) { + .name = "mclk1_0_pll", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mclk1_pll_dco.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_mclk1_0_pre = { + .data = &(struct clk_regmap_div_data) { + .offset = MCLKPLL_CTRL3, + .shift = 3, + .width = 5, + .flags = CLK_DIVIDER_MAX_AT_ZERO, + }, + .hw.init = &(struct clk_init_data) { + .name = "mclk1_0_pre", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mclk1_0_pll.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_mclk1_0_parents[] = { + { .hw = &a9_mclk1_0_pre.hw }, + { .fw_name = "in0" }, + { .fw_name = "in1" }, + { .fw_name = "in2" } +}; + +static A9_COMP_SEL(mclk1_0, MCLKPLL_CTRL3, 12, 0x3, a9_mclk1_0_parents); +static A9_COMP_DIV(mclk1_0, MCLKPLL_CTRL3, 10, 1); +static A9_COMP_GATE(mclk1_0, MCLKPLL_CTRL3, 8); + +static struct clk_regmap a9_mclk1_1_pll = { + .data = &(struct clk_regmap_div_data) { + .offset = MCLKPLL_CTRL3, + .shift = 16, + .width = 3, + .table = a9_pll_od_table, + }, + .hw.init = &(struct clk_init_data) { + .name = "mclk1_1_pll", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mclk1_pll_dco.hw + }, + .num_parents = 1, + }, +}; + +static struct clk_regmap a9_mclk1_1_pre = { + .data = &(struct clk_regmap_div_data) { + .offset = MCLKPLL_CTRL3, + .shift = 19, + .width = 5, + .flags = CLK_DIVIDER_MAX_AT_ZERO, + }, + .hw.init = &(struct clk_init_data) { + .name = "mclk1_1_pre", + .ops = &clk_regmap_divider_ops, + .parent_hws = (const struct clk_hw *[]) { + &a9_mclk1_1_pll.hw + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + }, +}; + +static const struct clk_parent_data a9_mclk1_1_parents[] = { + { .hw = &a9_mclk1_1_pre.hw }, + { .fw_name = "in0" }, + { .fw_name = "in1" }, + { .fw_name = "in2" } +}; + +static A9_COMP_SEL(mclk1_1, MCLKPLL_CTRL3, 28, 0x3, a9_mclk1_1_parents); +static A9_COMP_DIV(mclk1_1, MCLKPLL_CTRL3, 26, 1); +static A9_COMP_GATE(mclk1_1, MCLKPLL_CTRL3, 24); + +static struct clk_hw *a9_gp0_hw_clks[] = { + [CLKID_GP0_IN_DIV2_DIV] = &a9_gp0_in_div2_div.hw, + [CLKID_GP0_IN_DIV2] = &a9_gp0_in_div2.hw, + [CLKID_GP0_PLL_DCO] = &a9_gp0_pll_dco.hw, + [CLKID_GP0_PLL] = &a9_gp0_pll.hw, +}; + +static struct clk_hw *a9_hifi0_hw_clks[] = { + [CLKID_HIFI0_IN_DIV2_DIV] = &a9_hifi0_in_div2_div.hw, + [CLKID_HIFI0_IN_DIV2] = &a9_hifi0_in_div2.hw, + [CLKID_HIFI0_PLL_DCO] = &a9_hifi0_pll_dco.hw, + [CLKID_HIFI0_PLL] = &a9_hifi0_pll.hw, +}; + +static struct clk_hw *a9_hifi1_hw_clks[] = { + [CLKID_HIFI1_IN_DIV2_DIV] = &a9_hifi1_in_div2_div.hw, + [CLKID_HIFI1_IN_DIV2] = &a9_hifi1_in_div2.hw, + [CLKID_HIFI1_PLL_DCO] = &a9_hifi1_pll_dco.hw, + [CLKID_HIFI1_PLL] = &a9_hifi1_pll.hw, +}; + +static struct clk_hw *a9_mclk0_hw_clks[] = { + [CLKID_MCLK0_IN_DIV2] = &a9_mclk0_in_div2.hw, + [CLKID_MCLK0_PLL_DCO] = &a9_mclk0_pll_dco.hw, + [CLKID_MCLK0_0_PLL] = &a9_mclk0_0_pll.hw, + [CLKID_MCLK0_0_PRE] = &a9_mclk0_0_pre.hw, + [CLKID_MCLK0_0_SEL] = &a9_mclk0_0_sel.hw, + [CLKID_MCLK0_0_DIV] = &a9_mclk0_0_div.hw, + [CLKID_MCLK0_0] = &a9_mclk0_0.hw, + [CLKID_MCLK0_1_PLL] = &a9_mclk0_1_pll.hw, + [CLKID_MCLK0_1_PRE] = &a9_mclk0_1_pre.hw, + [CLKID_MCLK0_1_SEL] = &a9_mclk0_1_sel.hw, + [CLKID_MCLK0_1_DIV] = &a9_mclk0_1_div.hw, + [CLKID_MCLK0_1] = &a9_mclk0_1.hw, +}; + +static struct clk_hw *a9_mclk1_hw_clks[] = { + [CLKID_MCLK1_IN_DIV2] = &a9_mclk1_in_div2.hw, + [CLKID_MCLK1_PLL_DCO] = &a9_mclk1_pll_dco.hw, + [CLKID_MCLK1_0_PLL] = &a9_mclk1_0_pll.hw, + [CLKID_MCLK1_0_PRE] = &a9_mclk1_0_pre.hw, + [CLKID_MCLK1_0_SEL] = &a9_mclk1_0_sel.hw, + [CLKID_MCLK1_0_DIV] = &a9_mclk1_0_div.hw, + [CLKID_MCLK1_0] = &a9_mclk1_0.hw, + [CLKID_MCLK1_1_PLL] = &a9_mclk1_1_pll.hw, + [CLKID_MCLK1_1_PRE] = &a9_mclk1_1_pre.hw, + [CLKID_MCLK1_1_SEL] = &a9_mclk1_1_sel.hw, + [CLKID_MCLK1_1_DIV] = &a9_mclk1_1_div.hw, + [CLKID_MCLK1_1] = &a9_mclk1_1.hw, +}; + +static const struct meson_clkc_data a9_gp0_data = { + .hw_clks = { + .hws = a9_gp0_hw_clks, + .num = ARRAY_SIZE(a9_gp0_hw_clks), + }, +}; + +static const struct meson_clkc_data a9_hifi0_data = { + .hw_clks = { + .hws = a9_hifi0_hw_clks, + .num = ARRAY_SIZE(a9_hifi0_hw_clks), + }, +}; + +static const struct meson_clkc_data a9_hifi1_data = { + .hw_clks = { + .hws = a9_hifi1_hw_clks, + .num = ARRAY_SIZE(a9_hifi1_hw_clks), + }, +}; + +static const struct meson_clkc_data a9_mclk0_data = { + .hw_clks = { + .hws = a9_mclk0_hw_clks, + .num = ARRAY_SIZE(a9_mclk0_hw_clks), + }, +}; + +static const struct meson_clkc_data a9_mclk1_data = { + .hw_clks = { + .hws = a9_mclk1_hw_clks, + .num = ARRAY_SIZE(a9_mclk1_hw_clks), + }, +}; + +static const struct of_device_id a9_pll_clkc_match_table[] = { + { .compatible = "amlogic,a9-gp0-pll", .data = &a9_gp0_data, }, + { .compatible = "amlogic,a9-hifi0-pll", .data = &a9_hifi0_data, }, + { .compatible = "amlogic,a9-hifi1-pll", .data = &a9_hifi1_data, }, + { .compatible = "amlogic,a9-mclk0-pll", .data = &a9_mclk0_data, }, + { .compatible = "amlogic,a9-mclk1-pll", .data = &a9_mclk1_data, }, + {} +}; +MODULE_DEVICE_TABLE(of, a9_pll_clkc_match_table); + +static struct platform_driver a9_pll_clkc_driver = { + .probe = meson_clkc_mmio_probe, + .driver = { + .name = "a9-pll-clkc", + .of_match_table = a9_pll_clkc_match_table, + }, +}; +module_platform_driver(a9_pll_clkc_driver); + +MODULE_DESCRIPTION("Amlogic A9 PLL Clock Controller Driver"); +MODULE_AUTHOR("Jian Hu "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("CLK_MESON"); -- 2.47.1 From broonie at kernel.org Mon May 11 05:05:17 2026 From: broonie at kernel.org (Mark Brown) Date: Mon, 11 May 2026 21:05:17 +0900 Subject: [PATCH 00/10] spi: Use FIELD_MODIFY() for bitfield operations In-Reply-To: <20260430155456.36998-1-18255117159@163.com> References: <20260430155456.36998-1-18255117159@163.com> Message-ID: <177850111749.988959.3324914194988461245.b4-ty@b4> On Thu, 30 Apr 2026 23:54:46 +0800, Hans Zhang wrote: > spi: Use FIELD_MODIFY() for bitfield operations > > Replace open-coded bitfield modifications with the standard FIELD_MODIFY() > macro across multiple SPI controller drivers. This improves readability and > adds compile-time checking without functional changes. > > Each patch modifies a single driver, allowing independent review and > application. > > [...] Applied to https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-7.2 Thanks! [01/10] spi: amlogic-spifc-a1: Use FIELD_MODIFY() https://git.kernel.org/broonie/spi/c/8262b1421ddd [02/10] spi: amlogic-spisg: Use FIELD_MODIFY() https://git.kernel.org/broonie/spi/c/b69bfa593329 [03/10] spi: cadence-xspi: Use FIELD_MODIFY() https://git.kernel.org/broonie/spi/c/6fa473f4c5dc [04/10] spi: meson-spicc: Use FIELD_MODIFY() https://git.kernel.org/broonie/spi/c/cfdab17cd2d7 [05/10] spi: nxp-xspi: Use FIELD_MODIFY() https://git.kernel.org/broonie/spi/c/0f2efc6d4938 [06/10] spi: sn-f-ospi: Use FIELD_MODIFY() https://git.kernel.org/broonie/spi/c/579fcc06576d [07/10] spi: stm32-ospi: Use FIELD_MODIFY() https://git.kernel.org/broonie/spi/c/21ee6902a576 [08/10] spi: stm32-qspi: Use FIELD_MODIFY() https://git.kernel.org/broonie/spi/c/3e0530c087a9 [09/10] spi: sunplus-sp7021: Use FIELD_MODIFY() https://git.kernel.org/broonie/spi/c/673214ac9bcd [10/10] spi: uniphier: Use FIELD_MODIFY() https://git.kernel.org/broonie/spi/c/ce7984bea2a1 All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted. You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed. If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced. Please add any relevant lists and maintainers to the CCs when replying to this mail. Thanks, Mark From bmasney at redhat.com Mon May 11 08:21:56 2026 From: bmasney at redhat.com (Brian Masney) Date: Mon, 11 May 2026 11:21:56 -0400 Subject: [PATCH 06/10] clk: amlogic: PLL reset signal supports active-low configuration In-Reply-To: <20260511-b4-a9_clk-v1-6-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-6-41cb4071b7c9@amlogic.com> Message-ID: On Mon, May 11, 2026 at 08:47:28PM +0800, Jian Hu via B4 Relay wrote: > From: Jian Hu > > In the A9 design, the PLL reset signal is configured as active-low. > > Add the flag 'CLK_MESON_PLL_RST_N' to indicate that the PLL reset signal > is active-low. This flag isn't in the patch. I assume that you mean CLK_MESON_PLL_RST_ACTIVE_LOW? Brian > > Signed-off-by: Jian Hu > --- > drivers/clk/meson/clk-pll.c | 42 +++++++++++++++++++++++++++++++----------- > drivers/clk/meson/clk-pll.h | 2 ++ > 2 files changed, 33 insertions(+), 11 deletions(-) > > diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c > index 5a0bd75f85a9..8568ad6ba7b6 100644 > --- a/drivers/clk/meson/clk-pll.c > +++ b/drivers/clk/meson/clk-pll.c > @@ -295,10 +295,14 @@ static int meson_clk_pll_is_enabled(struct clk_hw *hw) > { > struct clk_regmap *clk = to_clk_regmap(hw); > struct meson_clk_pll_data *pll = meson_clk_pll_data(clk); > + unsigned int rst; > > - if (MESON_PARM_APPLICABLE(&pll->rst) && > - meson_parm_read(clk->map, &pll->rst)) > - return 0; > + if (MESON_PARM_APPLICABLE(&pll->rst)) { > + rst = meson_parm_read(clk->map, &pll->rst); > + if ((rst && !(pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW)) || > + (!rst && (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW))) > + return 0; > + } > > if (!meson_parm_read(clk->map, &pll->en) || > !meson_parm_read(clk->map, &pll->l)) > @@ -326,14 +330,22 @@ static int meson_clk_pll_init(struct clk_hw *hw) > return 0; > > if (pll->init_count) { > - if (MESON_PARM_APPLICABLE(&pll->rst)) > - meson_parm_write(clk->map, &pll->rst, 1); > + if (MESON_PARM_APPLICABLE(&pll->rst)) { > + if (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW) > + meson_parm_write(clk->map, &pll->rst, 0); > + else > + meson_parm_write(clk->map, &pll->rst, 1); > + } > > regmap_multi_reg_write(clk->map, pll->init_regs, > pll->init_count); > > - if (MESON_PARM_APPLICABLE(&pll->rst)) > - meson_parm_write(clk->map, &pll->rst, 0); > + if (MESON_PARM_APPLICABLE(&pll->rst)) { > + if (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW) > + meson_parm_write(clk->map, &pll->rst, 1); > + else > + meson_parm_write(clk->map, &pll->rst, 0); > + } > } > > return 0; > @@ -363,15 +375,23 @@ static int meson_clk_pll_enable(struct clk_hw *hw) > return 0; > > /* Make sure the pll is in reset */ > - if (MESON_PARM_APPLICABLE(&pll->rst)) > - meson_parm_write(clk->map, &pll->rst, 1); > + if (MESON_PARM_APPLICABLE(&pll->rst)) { > + if (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW) > + meson_parm_write(clk->map, &pll->rst, 0); > + else > + meson_parm_write(clk->map, &pll->rst, 1); > + } > > /* Enable the pll */ > meson_parm_write(clk->map, &pll->en, 1); > > /* Take the pll out reset */ > - if (MESON_PARM_APPLICABLE(&pll->rst)) > - meson_parm_write(clk->map, &pll->rst, 0); > + if (MESON_PARM_APPLICABLE(&pll->rst)) { > + if (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW) > + meson_parm_write(clk->map, &pll->rst, 1); > + else > + meson_parm_write(clk->map, &pll->rst, 0); > + } > > /* > * Compared with the previous SoCs, self-adaption current module > diff --git a/drivers/clk/meson/clk-pll.h b/drivers/clk/meson/clk-pll.h > index 97b7c70376a3..1be7e6e77631 100644 > --- a/drivers/clk/meson/clk-pll.h > +++ b/drivers/clk/meson/clk-pll.h > @@ -31,6 +31,8 @@ struct pll_mult_range { > #define CLK_MESON_PLL_NOINIT_ENABLED BIT(1) > /* l_detect signal is active-high */ > #define CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH BIT(2) > +/* rst signal is active-low (Power-on reset) */ > +#define CLK_MESON_PLL_RST_ACTIVE_LOW BIT(3) > > struct meson_clk_pll_data { > struct parm en; > > -- > 2.47.1 > > From bmasney at redhat.com Mon May 11 08:23:11 2026 From: bmasney at redhat.com (Brian Masney) Date: Mon, 11 May 2026 11:23:11 -0400 Subject: [PATCH 07/10] clk: amlogic: Support POWER_OF_TWO for PLL pre-divider In-Reply-To: <20260511-b4-a9_clk-v1-7-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-7-41cb4071b7c9@amlogic.com> Message-ID: On Mon, May 11, 2026 at 08:47:29PM +0800, Jian Hu via B4 Relay wrote: > From: Jian Hu > > The A9 PLL pre-divider uses a division factor of 2^n to ensure a clock > duty cycle of 50% after predivision. > > Add flag 'CLK_MESON_PLL_N_POWER_OF_TWO' to indicate that the PLL > pre-divider division factor is 2^n. > > Signed-off-by: Jian Hu Reviewed-by: Brian Masney From bmasney at redhat.com Mon May 11 08:36:31 2026 From: bmasney at redhat.com (Brian Masney) Date: Mon, 11 May 2026 11:36:31 -0400 Subject: [PATCH 08/10] clk: amlogic: Add A9 PLL clock controller driver In-Reply-To: <20260511-b4-a9_clk-v1-8-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-8-41cb4071b7c9@amlogic.com> Message-ID: Hi Jian, On Mon, May 11, 2026 at 08:47:30PM +0800, Jian Hu via B4 Relay wrote: > From: Jian Hu > > Add the PLL clock controller driver for the Amlogic A9 SoC family. > > Signed-off-by: Jian Hu > --- > drivers/clk/meson/Kconfig | 13 + > drivers/clk/meson/Makefile | 1 + > drivers/clk/meson/a9-pll.c | 831 +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 845 insertions(+) > > diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig > index cf8cf3f9e4ee..3549e67d6988 100644 > --- a/drivers/clk/meson/Kconfig > +++ b/drivers/clk/meson/Kconfig > @@ -132,6 +132,19 @@ config COMMON_CLK_A1_PERIPHERALS > device, A1 SoC Family. Say Y if you want A1 Peripherals clock > controller to work. > > +config COMMON_CLK_A9_PLL > + tristate "Amlogic A9 SoC PLL controller support" > + depends on ARM64 depends on ARM64 || COMPILE_TEST > + default ARCH_MESON > + select COMMON_CLK_MESON_REGMAP > + select COMMON_CLK_MESON_CLKC_UTILS > + select COMMON_CLK_MESON_PLL > + imply COMMON_CLK_SCMI > + help > + Support for the PLL clock controller on Amlogic A311Y3 based > + device, AKA A9. PLLs are required by most peripheral to operate. > + Say Y if you want A9 PLL clock controller to work. > + > config COMMON_CLK_C3_PLL > tristate "Amlogic C3 PLL clock controller" > depends on ARM64 > diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile > index c6719694a242..77636033061f 100644 > --- a/drivers/clk/meson/Makefile > +++ b/drivers/clk/meson/Makefile > @@ -19,6 +19,7 @@ obj-$(CONFIG_COMMON_CLK_AXG) += axg.o axg-aoclk.o > obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o > obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o > obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o > +obj-$(CONFIG_COMMON_CLK_A9_PLL) += a9-pll.o > obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o > obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o > obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o > diff --git a/drivers/clk/meson/a9-pll.c b/drivers/clk/meson/a9-pll.c > new file mode 100644 > index 000000000000..84b591c3afff > --- /dev/null > +++ b/drivers/clk/meson/a9-pll.c > @@ -0,0 +1,831 @@ > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) > +/* > + * Copyright (C) 2026 Amlogic, Inc. All rights reserved > + */ > + > +#include > +#include > +#include > +#include "clk-regmap.h" > +#include "clk-pll.h" > +#include "meson-clkc-utils.h" Sort the headers > + > +#define GP0PLL_CTRL0 0x00 > +#define GP0PLL_CTRL1 0x04 > +#define GP0PLL_CTRL2 0x08 > +#define GP0PLL_CTRL3 0x0c > +#define GP0PLL_CTRL4 0x10 > + > +/* HIFI0 and HIFI1 share the same IP and register offset layout. */ > +#define HIFIPLL_CTRL0 0x00 > +#define HIFIPLL_CTRL1 0x04 > +#define HIFIPLL_CTRL2 0x08 > +#define HIFIPLL_CTRL3 0x0c > +#define HIFIPLL_CTRL4 0x10 > + > +/* MCLK0 and MCLK1 share the same IP and register offset layout. */ > +#define MCLKPLL_CTRL0 0x00 > +#define MCLKPLL_CTRL1 0x04 > +#define MCLKPLL_CTRL2 0x08 > +#define MCLKPLL_CTRL3 0x0c > +#define MCLKPLL_CTRL4 0x10 > + > +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata) \ > + MESON_COMP_SEL(a9_, _name, _reg, _shift, _mask, _pdata, NULL, 0, 0) > + > +#define A9_COMP_DIV(_name, _reg, _shift, _width) \ > + MESON_COMP_DIV(a9_, _name, _reg, _shift, _width, 0, CLK_SET_RATE_PARENT) > + > +#define A9_COMP_GATE(_name, _reg, _bit) \ > + MESON_COMP_GATE(a9_, _name, _reg, _bit, CLK_SET_RATE_PARENT) > + > +/* > + * Compared with previous SoC PLLs, the A9 PLL input path has an inherent > + * 2-divider. The N pre-divider follows the same calculation rule as OD, > + * where the pre-divider ratio equals 2^N. > + * > + * A9 PLL is composed as follows: > + * > + * PLL > + * +---------------------------------+ > + * | | > + * | +--+ | > + * in/2 >>---[ /2^N ]-->| | +-----+ | > + * | | |------| DCO |----->> out > + * | +--------->| | +--v--+ | > + * | | +--+ | | > + * | | | | > + * | +--[ *(M + (F/Fmax) ]<--+ | > + * | | > + * +---------------------------------+ > + * > + * out = in / 2 * (m + frac / frac_max) / 2^n > + */ > + > +static struct clk_fixed_factor a9_gp0_in_div2_div = { > + .mult = 1, > + .div = 2, > + .hw.init = &(struct clk_init_data){ > + .name = "gp0_in_div2_div", > + .ops = &clk_fixed_factor_ops, > + .parent_data = &(const struct clk_parent_data) { > + .fw_name = "in0", > + }, > + .num_parents = 1, > + }, You can use CLK_HW_INIT_FW_NAME() for the hw.init here and other places below. > +}; > + > +static struct clk_regmap a9_gp0_in_div2 = { > + .data = &(struct clk_regmap_gate_data) { > + .offset = GP0PLL_CTRL0, > + .bit_idx = 27, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "gp0_in_div2", > + .ops = &clk_regmap_gate_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_gp0_in_div2_div.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +/* The output frequency range of the A9 PLL_DCO is 1.4 GHz to 2.8 GHz. */ > +static const struct pll_mult_range a9_pll_mult_range = { > + .min = 117, > + .max = 233, > +}; > + > +static const struct reg_sequence a9_gp0_pll_init_regs[] = { > + { .reg = GP0PLL_CTRL0, .def = 0x00010000 }, > + { .reg = GP0PLL_CTRL1, .def = 0x11480000 }, > + { .reg = GP0PLL_CTRL2, .def = 0x1219b010 }, > + { .reg = GP0PLL_CTRL3, .def = 0x00008010 } > +}; > + > +static struct clk_regmap a9_gp0_pll_dco = { > + .data = &(struct meson_clk_pll_data) { > + .en = { > + .reg_off = GP0PLL_CTRL0, > + .shift = 28, > + .width = 1, > + }, > + .m = { > + .reg_off = GP0PLL_CTRL0, > + .shift = 0, > + .width = 9, > + }, > + .n = { > + .reg_off = GP0PLL_CTRL0, > + .shift = 12, > + .width = 3, > + }, > + .frac = { > + .reg_off = GP0PLL_CTRL1, > + .shift = 0, > + .width = 17, > + }, > + .l = { > + .reg_off = GP0PLL_CTRL0, > + .shift = 31, > + .width = 1, > + }, > + .rst = { > + .reg_off = GP0PLL_CTRL0, > + .shift = 29, > + .width = 1, > + }, > + .l_detect = { > + .reg_off = GP0PLL_CTRL0, > + .shift = 30, > + .width = 1, > + }, > + .range = &a9_pll_mult_range, > + .init_regs = a9_gp0_pll_init_regs, > + .init_count = ARRAY_SIZE(a9_gp0_pll_init_regs), > + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | > + CLK_MESON_PLL_N_POWER_OF_TWO | > + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "gp0_pll_dco", > + .ops = &meson_clk_pll_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_gp0_in_div2.hw > + }, > + .num_parents = 1, > + }, You can use CLK_HW_INIT_HWS() here and other places below. Brian > +}; > + > +/* For gp0, hifi and mclk pll, the maximum value of od is 4. */ > +static const struct clk_div_table a9_pll_od_table[] = { > + { 0, 1 }, > + { 1, 2 }, > + { 2, 4 }, > + { 3, 8 }, > + { 4, 16 }, > + { /* sentinel */ } > +}; > + > +static struct clk_regmap a9_gp0_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = GP0PLL_CTRL0, > + .shift = 20, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "gp0_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_gp0_pll_dco.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static struct clk_fixed_factor a9_hifi0_in_div2_div = { > + .mult = 1, > + .div = 2, > + .hw.init = &(struct clk_init_data){ > + .name = "hifi0_in_div2_div", > + .ops = &clk_fixed_factor_ops, > + .parent_data = &(const struct clk_parent_data) { > + .fw_name = "in0", > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_hifi0_in_div2 = { > + .data = &(struct clk_regmap_gate_data) { > + .offset = HIFIPLL_CTRL0, > + .bit_idx = 27, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "hifi0_in_div2", > + .ops = &clk_regmap_gate_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_hifi0_in_div2_div.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static const struct reg_sequence a9_hifi0_pll_init_regs[] = { > + { .reg = HIFIPLL_CTRL0, .def = 0x00010000 }, > + { .reg = HIFIPLL_CTRL1, .def = 0x11480000 }, > + { .reg = HIFIPLL_CTRL2, .def = 0x1219b010 }, > + { .reg = HIFIPLL_CTRL3, .def = 0x00008010 } > +}; > + > +static struct clk_regmap a9_hifi0_pll_dco = { > + .data = &(struct meson_clk_pll_data) { > + .en = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 28, > + .width = 1, > + }, > + .m = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 0, > + .width = 9, > + }, > + .n = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 12, > + .width = 3, > + }, > + .frac = { > + .reg_off = HIFIPLL_CTRL1, > + .shift = 0, > + .width = 17, > + }, > + .l = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 31, > + .width = 1, > + }, > + .rst = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 29, > + .width = 1, > + }, > + .l_detect = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 30, > + .width = 1, > + }, > + .range = &a9_pll_mult_range, > + .init_regs = a9_hifi0_pll_init_regs, > + .init_count = ARRAY_SIZE(a9_hifi0_pll_init_regs), > + .frac_max = 100000, > + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | > + CLK_MESON_PLL_N_POWER_OF_TWO | > + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "hifi0_pll_dco", > + .ops = &meson_clk_pll_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_hifi0_in_div2.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_hifi0_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = HIFIPLL_CTRL0, > + .shift = 20, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "hifi0_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_hifi0_pll_dco.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static struct clk_fixed_factor a9_hifi1_in_div2_div = { > + .mult = 1, > + .div = 2, > + .hw.init = &(struct clk_init_data){ > + .name = "hifi1_in_div2_div", > + .ops = &clk_fixed_factor_ops, > + .parent_data = &(const struct clk_parent_data) { > + .fw_name = "in0", > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_hifi1_in_div2 = { > + .data = &(struct clk_regmap_gate_data) { > + .offset = HIFIPLL_CTRL0, > + .bit_idx = 27, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "hifi1_in_div2", > + .ops = &clk_regmap_gate_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_hifi1_in_div2_div.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static const struct reg_sequence a9_hifi1_pll_init_regs[] = { > + { .reg = HIFIPLL_CTRL0, .def = 0x00010000 }, > + { .reg = HIFIPLL_CTRL1, .def = 0x11480000 }, > + { .reg = HIFIPLL_CTRL2, .def = 0x1219b011 }, > + { .reg = HIFIPLL_CTRL3, .def = 0x00008010 } > +}; > + > +static struct clk_regmap a9_hifi1_pll_dco = { > + .data = &(struct meson_clk_pll_data) { > + .en = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 28, > + .width = 1, > + }, > + .m = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 0, > + .width = 9, > + }, > + .n = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 12, > + .width = 3, > + }, > + .frac = { > + .reg_off = HIFIPLL_CTRL1, > + .shift = 0, > + .width = 17, > + }, > + .l = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 31, > + .width = 1, > + }, > + .rst = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 29, > + .width = 1, > + }, > + .l_detect = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 30, > + .width = 1, > + }, > + .range = &a9_pll_mult_range, > + .init_regs = a9_hifi1_pll_init_regs, > + .init_count = ARRAY_SIZE(a9_hifi1_pll_init_regs), > + .frac_max = 100000, > + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | > + CLK_MESON_PLL_N_POWER_OF_TWO | > + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "hifi1_pll_dco", > + .ops = &meson_clk_pll_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_hifi1_in_div2.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_hifi1_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = HIFIPLL_CTRL0, > + .shift = 20, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "hifi1_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_hifi1_pll_dco.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +/* > + * Unlike GP0 and HIFI PLLs, the input divider 2 of MCLK PLL is > + * enabled by default and has no enable control bit. > + */ > +static struct clk_fixed_factor a9_mclk0_in_div2 = { > + .mult = 1, > + .div = 2, > + .hw.init = &(struct clk_init_data){ > + .name = "mclk0_in_div2_div", > + .ops = &clk_fixed_factor_ops, > + .parent_data = &(const struct clk_parent_data) { > + .fw_name = "in0", > + }, > + .num_parents = 1, > + }, > +}; > + > +static const struct reg_sequence a9_mclk0_pll_init_regs[] = { > + { .reg = MCLKPLL_CTRL1, .def = 0x00422000 }, > + { .reg = MCLKPLL_CTRL2, .def = 0x60000100 }, > + { .reg = MCLKPLL_CTRL3, .def = 0x02000200 }, > + { .reg = MCLKPLL_CTRL4, .def = 0xd616d616 } > +}; > + > +static struct clk_regmap a9_mclk0_pll_dco = { > + .data = &(struct meson_clk_pll_data) { > + .en = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 28, > + .width = 1, > + }, > + .m = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 0, > + .width = 9, > + }, > + .n = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 12, > + .width = 3, > + }, > + .l = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 31, > + .width = 1, > + }, > + .rst = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 29, > + .width = 1, > + }, > + .l_detect = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 30, > + .width = 1, > + }, > + .range = &a9_pll_mult_range, > + .init_regs = a9_mclk0_pll_init_regs, > + .init_count = ARRAY_SIZE(a9_mclk0_pll_init_regs), > + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | > + CLK_MESON_PLL_N_POWER_OF_TWO | > + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk0_pll_dco", > + .ops = &meson_clk_pll_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk0_in_div2.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk0_0_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 0, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk0_0_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk0_pll_dco.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk0_0_pre = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 3, > + .width = 5, > + .flags = CLK_DIVIDER_MAX_AT_ZERO, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk0_0_pre", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk0_0_pll.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static const struct clk_parent_data a9_mclk0_0_parents[] = { > + { .hw = &a9_mclk0_0_pre.hw }, > + { .fw_name = "in0" }, > + { .fw_name = "in1" }, > + { .fw_name = "in2" } > +}; > + > +static A9_COMP_SEL(mclk0_0, MCLKPLL_CTRL3, 12, 0x3, a9_mclk0_0_parents); > +static A9_COMP_DIV(mclk0_0, MCLKPLL_CTRL3, 10, 1); > +static A9_COMP_GATE(mclk0_0, MCLKPLL_CTRL3, 8); > + > +static struct clk_regmap a9_mclk0_1_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 16, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk0_1_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk0_pll_dco.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk0_1_pre = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 19, > + .width = 5, > + .flags = CLK_DIVIDER_MAX_AT_ZERO, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk0_1_pre", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk0_1_pll.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static const struct clk_parent_data a9_mclk0_1_parents[] = { > + { .hw = &a9_mclk0_1_pre.hw }, > + { .fw_name = "in0" }, > + { .fw_name = "in1" }, > + { .fw_name = "in2" } > +}; > + > +static A9_COMP_SEL(mclk0_1, MCLKPLL_CTRL3, 28, 0x3, a9_mclk0_1_parents); > +static A9_COMP_DIV(mclk0_1, MCLKPLL_CTRL3, 26, 1); > +static A9_COMP_GATE(mclk0_1, MCLKPLL_CTRL3, 24); > + > +static struct clk_fixed_factor a9_mclk1_in_div2 = { > + .mult = 1, > + .div = 2, > + .hw.init = &(struct clk_init_data){ > + .name = "mclk1_in_div2", > + .ops = &clk_fixed_factor_ops, > + .parent_data = &(const struct clk_parent_data) { > + .fw_name = "in0", > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk1_pll_dco = { > + .data = &(struct meson_clk_pll_data) { > + .en = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 28, > + .width = 1, > + }, > + .m = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 0, > + .width = 9, > + }, > + .n = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 12, > + .width = 3, > + }, > + .l = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 31, > + .width = 1, > + }, > + .rst = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 29, > + .width = 1, > + }, > + .l_detect = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 30, > + .width = 1, > + }, > + .range = &a9_pll_mult_range, > + .init_regs = a9_mclk0_pll_init_regs, > + .init_count = ARRAY_SIZE(a9_mclk0_pll_init_regs), > + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | > + CLK_MESON_PLL_N_POWER_OF_TWO | > + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk1_pll_dco", > + .ops = &meson_clk_pll_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk1_in_div2.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk1_0_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 0, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk1_0_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk1_pll_dco.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk1_0_pre = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 3, > + .width = 5, > + .flags = CLK_DIVIDER_MAX_AT_ZERO, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk1_0_pre", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk1_0_pll.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static const struct clk_parent_data a9_mclk1_0_parents[] = { > + { .hw = &a9_mclk1_0_pre.hw }, > + { .fw_name = "in0" }, > + { .fw_name = "in1" }, > + { .fw_name = "in2" } > +}; > + > +static A9_COMP_SEL(mclk1_0, MCLKPLL_CTRL3, 12, 0x3, a9_mclk1_0_parents); > +static A9_COMP_DIV(mclk1_0, MCLKPLL_CTRL3, 10, 1); > +static A9_COMP_GATE(mclk1_0, MCLKPLL_CTRL3, 8); > + > +static struct clk_regmap a9_mclk1_1_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 16, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk1_1_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk1_pll_dco.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk1_1_pre = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 19, > + .width = 5, > + .flags = CLK_DIVIDER_MAX_AT_ZERO, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk1_1_pre", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk1_1_pll.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static const struct clk_parent_data a9_mclk1_1_parents[] = { > + { .hw = &a9_mclk1_1_pre.hw }, > + { .fw_name = "in0" }, > + { .fw_name = "in1" }, > + { .fw_name = "in2" } > +}; > + > +static A9_COMP_SEL(mclk1_1, MCLKPLL_CTRL3, 28, 0x3, a9_mclk1_1_parents); > +static A9_COMP_DIV(mclk1_1, MCLKPLL_CTRL3, 26, 1); > +static A9_COMP_GATE(mclk1_1, MCLKPLL_CTRL3, 24); > + > +static struct clk_hw *a9_gp0_hw_clks[] = { > + [CLKID_GP0_IN_DIV2_DIV] = &a9_gp0_in_div2_div.hw, > + [CLKID_GP0_IN_DIV2] = &a9_gp0_in_div2.hw, > + [CLKID_GP0_PLL_DCO] = &a9_gp0_pll_dco.hw, > + [CLKID_GP0_PLL] = &a9_gp0_pll.hw, > +}; > + > +static struct clk_hw *a9_hifi0_hw_clks[] = { > + [CLKID_HIFI0_IN_DIV2_DIV] = &a9_hifi0_in_div2_div.hw, > + [CLKID_HIFI0_IN_DIV2] = &a9_hifi0_in_div2.hw, > + [CLKID_HIFI0_PLL_DCO] = &a9_hifi0_pll_dco.hw, > + [CLKID_HIFI0_PLL] = &a9_hifi0_pll.hw, > +}; > + > +static struct clk_hw *a9_hifi1_hw_clks[] = { > + [CLKID_HIFI1_IN_DIV2_DIV] = &a9_hifi1_in_div2_div.hw, > + [CLKID_HIFI1_IN_DIV2] = &a9_hifi1_in_div2.hw, > + [CLKID_HIFI1_PLL_DCO] = &a9_hifi1_pll_dco.hw, > + [CLKID_HIFI1_PLL] = &a9_hifi1_pll.hw, > +}; > + > +static struct clk_hw *a9_mclk0_hw_clks[] = { > + [CLKID_MCLK0_IN_DIV2] = &a9_mclk0_in_div2.hw, > + [CLKID_MCLK0_PLL_DCO] = &a9_mclk0_pll_dco.hw, > + [CLKID_MCLK0_0_PLL] = &a9_mclk0_0_pll.hw, > + [CLKID_MCLK0_0_PRE] = &a9_mclk0_0_pre.hw, > + [CLKID_MCLK0_0_SEL] = &a9_mclk0_0_sel.hw, > + [CLKID_MCLK0_0_DIV] = &a9_mclk0_0_div.hw, > + [CLKID_MCLK0_0] = &a9_mclk0_0.hw, > + [CLKID_MCLK0_1_PLL] = &a9_mclk0_1_pll.hw, > + [CLKID_MCLK0_1_PRE] = &a9_mclk0_1_pre.hw, > + [CLKID_MCLK0_1_SEL] = &a9_mclk0_1_sel.hw, > + [CLKID_MCLK0_1_DIV] = &a9_mclk0_1_div.hw, > + [CLKID_MCLK0_1] = &a9_mclk0_1.hw, > +}; > + > +static struct clk_hw *a9_mclk1_hw_clks[] = { > + [CLKID_MCLK1_IN_DIV2] = &a9_mclk1_in_div2.hw, > + [CLKID_MCLK1_PLL_DCO] = &a9_mclk1_pll_dco.hw, > + [CLKID_MCLK1_0_PLL] = &a9_mclk1_0_pll.hw, > + [CLKID_MCLK1_0_PRE] = &a9_mclk1_0_pre.hw, > + [CLKID_MCLK1_0_SEL] = &a9_mclk1_0_sel.hw, > + [CLKID_MCLK1_0_DIV] = &a9_mclk1_0_div.hw, > + [CLKID_MCLK1_0] = &a9_mclk1_0.hw, > + [CLKID_MCLK1_1_PLL] = &a9_mclk1_1_pll.hw, > + [CLKID_MCLK1_1_PRE] = &a9_mclk1_1_pre.hw, > + [CLKID_MCLK1_1_SEL] = &a9_mclk1_1_sel.hw, > + [CLKID_MCLK1_1_DIV] = &a9_mclk1_1_div.hw, > + [CLKID_MCLK1_1] = &a9_mclk1_1.hw, > +}; > + > +static const struct meson_clkc_data a9_gp0_data = { > + .hw_clks = { > + .hws = a9_gp0_hw_clks, > + .num = ARRAY_SIZE(a9_gp0_hw_clks), > + }, > +}; > + > +static const struct meson_clkc_data a9_hifi0_data = { > + .hw_clks = { > + .hws = a9_hifi0_hw_clks, > + .num = ARRAY_SIZE(a9_hifi0_hw_clks), > + }, > +}; > + > +static const struct meson_clkc_data a9_hifi1_data = { > + .hw_clks = { > + .hws = a9_hifi1_hw_clks, > + .num = ARRAY_SIZE(a9_hifi1_hw_clks), > + }, > +}; > + > +static const struct meson_clkc_data a9_mclk0_data = { > + .hw_clks = { > + .hws = a9_mclk0_hw_clks, > + .num = ARRAY_SIZE(a9_mclk0_hw_clks), > + }, > +}; > + > +static const struct meson_clkc_data a9_mclk1_data = { > + .hw_clks = { > + .hws = a9_mclk1_hw_clks, > + .num = ARRAY_SIZE(a9_mclk1_hw_clks), > + }, > +}; > + > +static const struct of_device_id a9_pll_clkc_match_table[] = { > + { .compatible = "amlogic,a9-gp0-pll", .data = &a9_gp0_data, }, > + { .compatible = "amlogic,a9-hifi0-pll", .data = &a9_hifi0_data, }, > + { .compatible = "amlogic,a9-hifi1-pll", .data = &a9_hifi1_data, }, > + { .compatible = "amlogic,a9-mclk0-pll", .data = &a9_mclk0_data, }, > + { .compatible = "amlogic,a9-mclk1-pll", .data = &a9_mclk1_data, }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, a9_pll_clkc_match_table); > + > +static struct platform_driver a9_pll_clkc_driver = { > + .probe = meson_clkc_mmio_probe, > + .driver = { > + .name = "a9-pll-clkc", > + .of_match_table = a9_pll_clkc_match_table, > + }, > +}; > +module_platform_driver(a9_pll_clkc_driver); > + > +MODULE_DESCRIPTION("Amlogic A9 PLL Clock Controller Driver"); > +MODULE_AUTHOR("Jian Hu "); > +MODULE_LICENSE("GPL"); > +MODULE_IMPORT_NS("CLK_MESON"); > > -- > 2.47.1 > > From bmasney at redhat.com Mon May 11 08:42:54 2026 From: bmasney at redhat.com (Brian Masney) Date: Mon, 11 May 2026 11:42:54 -0400 Subject: [PATCH 09/10] clk: amlogic: Add A9 peripherals clock controller driver In-Reply-To: <20260511-b4-a9_clk-v1-9-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-9-41cb4071b7c9@amlogic.com> Message-ID: Hi Jian, On Mon, May 11, 2026 at 08:47:31PM +0800, Jian Hu via B4 Relay wrote: > From: Jian Hu > > Add the peripherals clock controller driver for the Amlogic A9 SoC family. > > Signed-off-by: Jian Hu > --- > drivers/clk/meson/Kconfig | 15 + > drivers/clk/meson/Makefile | 1 + > drivers/clk/meson/a9-peripherals.c | 2317 ++++++++++++++++++++++++++++++++++++ > 3 files changed, 2333 insertions(+) > > diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig > index 3549e67d6988..48a15a5e1323 100644 > --- a/drivers/clk/meson/Kconfig > +++ b/drivers/clk/meson/Kconfig > @@ -145,6 +145,21 @@ config COMMON_CLK_A9_PLL > device, AKA A9. PLLs are required by most peripheral to operate. > Say Y if you want A9 PLL clock controller to work. > > +config COMMON_CLK_A9_PERIPHERALS > + tristate "Amlogic A9 SoC peripherals clock controller support" > + depends on ARM64 depends on ARM64 || COMPILE_TEST > + default ARCH_MESON > + select COMMON_CLK_MESON_REGMAP > + select COMMON_CLK_MESON_CLKC_UTILS > + select COMMON_CLK_MESON_DUALDIV > + select COMMON_CLK_MESON_VID_PLL_DIV > + imply COMMON_CLK_SCMI > + imply COMMON_CLK_A9_PLL > + help > + Support for the peripherals clock controller on Amlogic A311Y3 based > + device, AKA A9. Peripherals are required by most peripheral to operate. > + Say Y if you want A9 peripherals clock controller to work. > + > config COMMON_CLK_C3_PLL > tristate "Amlogic C3 PLL clock controller" > depends on ARM64 > diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile > index 77636033061f..2b5b67b14efc 100644 > --- a/drivers/clk/meson/Makefile > +++ b/drivers/clk/meson/Makefile > @@ -20,6 +20,7 @@ obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o > obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o > obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o > obj-$(CONFIG_COMMON_CLK_A9_PLL) += a9-pll.o > +obj-$(CONFIG_COMMON_CLK_A9_PERIPHERALS) += a9-peripherals.o > obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o > obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o > obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o > diff --git a/drivers/clk/meson/a9-peripherals.c b/drivers/clk/meson/a9-peripherals.c > new file mode 100644 > index 000000000000..338a91c473ea > --- /dev/null > +++ b/drivers/clk/meson/a9-peripherals.c > @@ -0,0 +1,2317 @@ > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) > +/* > + * Copyright (C) 2026 Amlogic, Inc. All rights reserved > + */ > + > +#include > +#include > +#include > +#include "clk-regmap.h" > +#include "clk-dualdiv.h" > +#include "vid-pll-div.h" > +#include "meson-clkc-utils.h" Sort the headers. > + > +#define SYS_CLK_EN0_REG0 0x30 > +#define SYS_CLK_EN0_REG1 0x34 > +#define SYS_CLK_EN0_REG2 0x38 > +#define SYS_CLK_EN0_REG3 0x3c > +#define SD_EMMC_CLK_CTRL0 0x90 > +#define SD_EMMC_CLK_CTRL1 0x94 > +#define PWM_CLK_H_CTRL 0xbc > +#define PWM_CLK_I_CTRL 0xc0 > +#define PWM_CLK_J_CTRL 0xc4 > +#define PWM_CLK_K_CTRL 0xc8 > +#define PWM_CLK_L_CTRL 0xcc > +#define PWM_CLK_M_CTRL 0xd0 > +#define PWM_CLK_N_CTRL 0xd4 > +#define SPISG_CLK_CTRL 0x100 > +#define SPISG_CLK_CTRL1 0x104 > +#define SAR_CLK_CTRL 0x150 > +#define AMFC_CLK_CTRL 0x154 > +#define NNA_CLK_CTRL 0x15c > +#define USB_CLK_CTRL 0x160 > +#define PCIE_TL_CLK_CTRL 0x164 > +#define CMPR_CLK_CTRL 0x168 > +#define DEWARP_CLK_CTRL 0x16c > +#define SC_CLK_CTRL 0x170 > +#define DPTX_CLK_CTRL 0x178 > +#define ISP_CLK_CTRL 0x17c > +#define CVE_CLK_CTRL 0x180 > +#define PP_CLK_CTRL 0x184 > +#define GLB_CLK_CTRL 0x188 > +#define USB_CLK_CTRL0 0x18c > +#define USB_CLK_CTRL1 0x190 > +#define CAN_CLK_CTRL 0x194 > +#define CAN_CLK_CTRL1 0x198 > +#define I3C_CLK_CTRL 0x19c > +#define TS_CLK_CTRL 0x1a0 > +#define ETH_CLK_CTRL 0x1a4 > +#define GEN_CLK_CTRL 0x1a8 > +#define CLK12_24_CTRL 0x1ac > +#define MALI_CLK_CTRL 0x200 > +#define MALI_STACK_CLK_CTRL 0x204 > +#define DSPA_CLK_CTRL 0x220 > +#define HEVCF_CLK_CTRL 0x240 > +#define HCODEC_CLK_CTRL 0x244 > +#define VPU_CLK_CTRL 0x260 > +#define VAPB_CLK_CTRL 0x268 > +#define VPU_CLKB_CTRL 0x280 > +#define HDMI_CLK_CTRL 0x284 > +#define HTX_CLK_CTRL 0x28c > +#define HTX_CLK_CTRL1 0x290 > +#define HRX_CLK_CTRL 0x294 > +#define HRX_CLK_CTRL1 0x298 > +#define HRX_CLK_CTRL2 0x29c > +#define HRX_CLK_CTRL3 0x2a0 > +#define VID_LOCK_CLK_CTRL 0x2a4 > +#define VDIN_MEAS_CLK_CTRL 0x2a8 > +#define VID_PLL_CLK_DIV 0x2b0 > +#define VID_CLK_CTRL 0x2c0 > +#define VID_CLK_CTRL2 0x2c4 > +#define VID_CLK_DIV 0x2c8 > +#define VIID_CLK_DIV 0x2cc > +#define VIID_CLK_CTRL 0x2d0 > +#define MIPI_CSI_PHY_CLK_CTRL 0x2e0 > +#define DSI_MEAS_CLK_CTRL 0x2f4 > + > +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata, _table) \ > + MESON_COMP_SEL(a9_, _name, _reg, _shift, _mask, _pdata, _table, 0, 0) > + > +#define A9_COMP_DIV(_name, _reg, _shift, _width) \ > + MESON_COMP_DIV(a9_, _name, _reg, _shift, _width, 0, CLK_SET_RATE_PARENT) > + > +#define A9_COMP_GATE(_name, _reg, _bit, _iflags) \ > + MESON_COMP_GATE(a9_, _name, _reg, _bit, CLK_SET_RATE_PARENT | (_iflags)) > + > +static const struct clk_parent_data a9_sys_pclk_parents = { .fw_name = "sys" }; > + > +#define A9_SYS_PCLK(_name, _reg, _bit) \ > + MESON_PCLK(a9_##_name, _reg, _bit, &a9_sys_pclk_parents, 0) > + > +static A9_SYS_PCLK(sys_am_axi, SYS_CLK_EN0_REG0, 0); > +static A9_SYS_PCLK(sys_dos, SYS_CLK_EN0_REG0, 1); > +static A9_SYS_PCLK(sys_mipi_dsi, SYS_CLK_EN0_REG0, 3); > +static A9_SYS_PCLK(sys_eth_phy, SYS_CLK_EN0_REG0, 4); > +static A9_SYS_PCLK(sys_amfc, SYS_CLK_EN0_REG0, 5); > +static A9_SYS_PCLK(sys_mali, SYS_CLK_EN0_REG0, 6); > +static A9_SYS_PCLK(sys_nna, SYS_CLK_EN0_REG0, 7); > +static A9_SYS_PCLK(sys_eth_axi, SYS_CLK_EN0_REG0, 8); > +static A9_SYS_PCLK(sys_dp_apb, SYS_CLK_EN0_REG0, 9); > +static A9_SYS_PCLK(sys_edptx_apb, SYS_CLK_EN0_REG0, 10); > +static A9_SYS_PCLK(sys_u3hsg, SYS_CLK_EN0_REG0, 11); > +static A9_SYS_PCLK(sys_aucpu, SYS_CLK_EN0_REG0, 14); > +static A9_SYS_PCLK(sys_glb, SYS_CLK_EN0_REG0, 15); > +static A9_SYS_PCLK(sys_combo_dphy_apb, SYS_CLK_EN0_REG0, 17); > +static A9_SYS_PCLK(sys_hdmirx_apb, SYS_CLK_EN0_REG0, 18); > +static A9_SYS_PCLK(sys_hdmirx_pclk, SYS_CLK_EN0_REG0, 19); > +static A9_SYS_PCLK(sys_mipi_dsi_phy, SYS_CLK_EN0_REG0, 20); > +static A9_SYS_PCLK(sys_can0, SYS_CLK_EN0_REG0, 21); > +static A9_SYS_PCLK(sys_can1, SYS_CLK_EN0_REG0, 22); > +static A9_SYS_PCLK(sys_sd_emmc_a, SYS_CLK_EN0_REG0, 24); > +static A9_SYS_PCLK(sys_sd_emmc_b, SYS_CLK_EN0_REG0, 25); > +static A9_SYS_PCLK(sys_sd_emmc_c, SYS_CLK_EN0_REG0, 26); > +static A9_SYS_PCLK(sys_sc, SYS_CLK_EN0_REG0, 27); > +static A9_SYS_PCLK(sys_acodec, SYS_CLK_EN0_REG0, 28); > +static A9_SYS_PCLK(sys_mipi_isp, SYS_CLK_EN0_REG0, 29); > +static A9_SYS_PCLK(sys_msr, SYS_CLK_EN0_REG0, 30); > +static A9_SYS_PCLK(sys_audio, SYS_CLK_EN0_REG1, 0); > +static A9_SYS_PCLK(sys_mipi_dsi_b, SYS_CLK_EN0_REG1, 1); > +static A9_SYS_PCLK(sys_mipi_dsi1_phy, SYS_CLK_EN0_REG1, 2); > +static A9_SYS_PCLK(sys_eth, SYS_CLK_EN0_REG1, 3); > +static A9_SYS_PCLK(sys_eth_1g_mac, SYS_CLK_EN0_REG1, 4); > +static A9_SYS_PCLK(sys_uart_a, SYS_CLK_EN0_REG1, 5); > +static A9_SYS_PCLK(sys_uart_f, SYS_CLK_EN0_REG1, 10); > +static A9_SYS_PCLK(sys_ts_a55, SYS_CLK_EN0_REG1, 11); > +static A9_SYS_PCLK(sys_eth_1g_axi, SYS_CLK_EN0_REG1, 12); > +static A9_SYS_PCLK(sys_ts_dos, SYS_CLK_EN0_REG1, 13); > +static A9_SYS_PCLK(sys_u3drd_b, SYS_CLK_EN0_REG1, 14); > +static A9_SYS_PCLK(sys_ts_core, SYS_CLK_EN0_REG1, 15); > +static A9_SYS_PCLK(sys_ts_pll, SYS_CLK_EN0_REG1, 16); > +static A9_SYS_PCLK(sys_csi_dig_clkin, SYS_CLK_EN0_REG1, 18); > +static A9_SYS_PCLK(sys_cve, SYS_CLK_EN0_REG1, 19); > +static A9_SYS_PCLK(sys_ge2d, SYS_CLK_EN0_REG1, 20); > +static A9_SYS_PCLK(sys_spisg, SYS_CLK_EN0_REG1, 21); > +static A9_SYS_PCLK(sys_u3drd_1, SYS_CLK_EN0_REG1, 22); > +static A9_SYS_PCLK(sys_u2h, SYS_CLK_EN0_REG1, 23); > +static A9_SYS_PCLK(sys_pcie_mac_a, SYS_CLK_EN0_REG1, 24); > +static A9_SYS_PCLK(sys_u3drd_a, SYS_CLK_EN0_REG1, 25); > +static A9_SYS_PCLK(sys_u2drd, SYS_CLK_EN0_REG1, 26); > +static A9_SYS_PCLK(sys_pcie_phy, SYS_CLK_EN0_REG1, 27); > +static A9_SYS_PCLK(sys_pcie_mac_b, SYS_CLK_EN0_REG1, 28); > +static A9_SYS_PCLK(sys_periph, SYS_CLK_EN0_REG1, 29); > +static A9_SYS_PCLK(sys_pio, SYS_CLK_EN0_REG2, 0); > +static A9_SYS_PCLK(sys_i3c, SYS_CLK_EN0_REG2, 1); > +static A9_SYS_PCLK(sys_i2c_m_e, SYS_CLK_EN0_REG2, 2); > +static A9_SYS_PCLK(sys_i2c_m_f, SYS_CLK_EN0_REG2, 3); > +static A9_SYS_PCLK(sys_hdmitx_apb, SYS_CLK_EN0_REG2, 4); > +static A9_SYS_PCLK(sys_i2c_m_i, SYS_CLK_EN0_REG2, 5); > +static A9_SYS_PCLK(sys_i2c_m_g, SYS_CLK_EN0_REG2, 6); > +static A9_SYS_PCLK(sys_i2c_m_h, SYS_CLK_EN0_REG2, 7); > +static A9_SYS_PCLK(sys_hdmi20_aes, SYS_CLK_EN0_REG2, 9); > +static A9_SYS_PCLK(sys_csi2_host, SYS_CLK_EN0_REG2, 16); > +static A9_SYS_PCLK(sys_csi2_adapt, SYS_CLK_EN0_REG2, 17); > +static A9_SYS_PCLK(sys_dspa, SYS_CLK_EN0_REG2, 21); > +static A9_SYS_PCLK(sys_pp_dma, SYS_CLK_EN0_REG2, 22); > +static A9_SYS_PCLK(sys_pp_wrapper, SYS_CLK_EN0_REG2, 23); > +static A9_SYS_PCLK(sys_vpu_intr, SYS_CLK_EN0_REG2, 25); > +static A9_SYS_PCLK(sys_csi2_phy, SYS_CLK_EN0_REG2, 27); > +static A9_SYS_PCLK(sys_saradc, SYS_CLK_EN0_REG2, 28); > +static A9_SYS_PCLK(sys_pwm_j, SYS_CLK_EN0_REG2, 30); > +static A9_SYS_PCLK(sys_pwm_i, SYS_CLK_EN0_REG2, 31); > +static A9_SYS_PCLK(sys_pwm_h, SYS_CLK_EN0_REG3, 0); > +static A9_SYS_PCLK(sys_pwm_n, SYS_CLK_EN0_REG3, 8); > +static A9_SYS_PCLK(sys_pwm_m, SYS_CLK_EN0_REG3, 9); > +static A9_SYS_PCLK(sys_pwm_l, SYS_CLK_EN0_REG3, 10); > +static A9_SYS_PCLK(sys_pwm_k, SYS_CLK_EN0_REG3, 11); > + > +/* Channel 5 is unconnected. */ > +static u32 a9_sd_emmc_parents_val_table[] = { 0, 1, 2, 3, 4, 6, 7 }; > +static const struct clk_parent_data a9_sd_emmc_parents[] = { > + { .fw_name = "xtal", }, > + { .fw_name = "fdiv2", }, > + { .fw_name = "fdiv3", }, > + { .fw_name = "hifi0", }, > + { .fw_name = "fdiv2p5", }, > + { .fw_name = "gp1", }, > + { .fw_name = "gp0", } > +}; > + > +static A9_COMP_SEL(sd_emmc_a, SD_EMMC_CLK_CTRL0, 9, 0x7, a9_sd_emmc_parents, > + a9_sd_emmc_parents_val_table); > +static A9_COMP_DIV(sd_emmc_a, SD_EMMC_CLK_CTRL0, 0, 7); > +static A9_COMP_GATE(sd_emmc_a, SD_EMMC_CLK_CTRL0, 8, 0); > + > +static A9_COMP_SEL(sd_emmc_b, SD_EMMC_CLK_CTRL0, 25, 0x7, a9_sd_emmc_parents, > + a9_sd_emmc_parents_val_table); > +static A9_COMP_DIV(sd_emmc_b, SD_EMMC_CLK_CTRL0, 16, 7); > +static A9_COMP_GATE(sd_emmc_b, SD_EMMC_CLK_CTRL0, 24, 0); > + > +static A9_COMP_SEL(sd_emmc_c, SD_EMMC_CLK_CTRL1, 9, 0x7, a9_sd_emmc_parents, > + a9_sd_emmc_parents_val_table); > +static A9_COMP_DIV(sd_emmc_c, SD_EMMC_CLK_CTRL1, 0, 7); > +static A9_COMP_GATE(sd_emmc_c, SD_EMMC_CLK_CTRL1, 8, 0); > + > +static const struct clk_parent_data a9_pwm_parents[] = { > + { .fw_name = "xtal", }, > + { .fw_name = "fdiv5", }, > + { .fw_name = "fdiv4", }, > + { .fw_name = "fdiv3", } > +}; > + > +static A9_COMP_SEL(pwm_h, PWM_CLK_H_CTRL, 9, 0x7, a9_pwm_parents, NULL); > +static A9_COMP_DIV(pwm_h, PWM_CLK_H_CTRL, 0, 8); > +static A9_COMP_GATE(pwm_h, PWM_CLK_H_CTRL, 8, 0); > + > +static A9_COMP_SEL(pwm_i, PWM_CLK_I_CTRL, 9, 0x7, a9_pwm_parents, NULL); > +static A9_COMP_DIV(pwm_i, PWM_CLK_I_CTRL, 0, 8); > +static A9_COMP_GATE(pwm_i, PWM_CLK_I_CTRL, 8, 0); > + > +static A9_COMP_SEL(pwm_j, PWM_CLK_J_CTRL, 9, 0x7, a9_pwm_parents, NULL); > +static A9_COMP_DIV(pwm_j, PWM_CLK_J_CTRL, 0, 8); > +static A9_COMP_GATE(pwm_j, PWM_CLK_J_CTRL, 8, 0); > + > +static A9_COMP_SEL(pwm_k, PWM_CLK_K_CTRL, 9, 0x7, a9_pwm_parents, NULL); > +static A9_COMP_DIV(pwm_k, PWM_CLK_K_CTRL, 0, 8); > +static A9_COMP_GATE(pwm_k, PWM_CLK_K_CTRL, 8, 0); > + > +static A9_COMP_SEL(pwm_l, PWM_CLK_L_CTRL, 9, 0x7, a9_pwm_parents, NULL); > +static A9_COMP_DIV(pwm_l, PWM_CLK_L_CTRL, 0, 8); > +static A9_COMP_GATE(pwm_l, PWM_CLK_L_CTRL, 8, 0); > + > +static A9_COMP_SEL(pwm_m, PWM_CLK_M_CTRL, 9, 0x7, a9_pwm_parents, NULL); > +static A9_COMP_DIV(pwm_m, PWM_CLK_M_CTRL, 0, 8); > +static A9_COMP_GATE(pwm_m, PWM_CLK_M_CTRL, 8, 0); > + > +static A9_COMP_SEL(pwm_n, PWM_CLK_N_CTRL, 9, 0x7, a9_pwm_parents, NULL); > +static A9_COMP_DIV(pwm_n, PWM_CLK_N_CTRL, 0, 8); > +static A9_COMP_GATE(pwm_n, PWM_CLK_N_CTRL, 8, 0); > + > +static const struct clk_parent_data a9_spisg_parents[] = { > + { .fw_name = "xtal", }, > + { .fw_name = "sys", }, > + { .fw_name = "fdiv4", }, > + { .fw_name = "fdiv3", }, > + { .fw_name = "fdiv2", }, > + { .fw_name = "fdiv5", }, > + { .fw_name = "fdiv7", }, > + { .fw_name = "gp0", } > +}; > + > +static A9_COMP_SEL(spisg, SPISG_CLK_CTRL, 9, 0x7, a9_spisg_parents, NULL); > +static A9_COMP_DIV(spisg, SPISG_CLK_CTRL, 0, 6); > +static A9_COMP_GATE(spisg, SPISG_CLK_CTRL, 8, 0); > + > +static A9_COMP_SEL(spisg1, SPISG_CLK_CTRL, 25, 0x7, a9_spisg_parents, NULL); > +static A9_COMP_DIV(spisg1, SPISG_CLK_CTRL, 16, 6); > +static A9_COMP_GATE(spisg1, SPISG_CLK_CTRL, 24, 0); > + > +static A9_COMP_SEL(spisg2, SPISG_CLK_CTRL1, 9, 0x7, a9_spisg_parents, NULL); > +static A9_COMP_DIV(spisg2, SPISG_CLK_CTRL1, 0, 6); > +static A9_COMP_GATE(spisg2, SPISG_CLK_CTRL1, 8, 0); > + > +static const struct clk_parent_data a9_saradc_parents[] = { > + { .fw_name = "xtal", }, > + { .fw_name = "sys", } > +}; > + > +static A9_COMP_SEL(saradc, SAR_CLK_CTRL, 9, 0x7, a9_saradc_parents, NULL); > +static A9_COMP_DIV(saradc, SAR_CLK_CTRL, 0, 8); > +static A9_COMP_GATE(saradc, SAR_CLK_CTRL, 8, 0); > + > +static const struct clk_parent_data a9_amfc_parents[] = { > + { .fw_name = "xtal", }, > + { .fw_name = "sys", }, > + { .fw_name = "fdiv2", }, > + { .fw_name = "fdiv2p5", }, > + { .fw_name = "fdiv3", }, > + { .fw_name = "fdiv4", }, > + { .fw_name = "fdiv5", }, > + { .fw_name = "fdiv7", } > +}; > + > +static A9_COMP_SEL(amfc, AMFC_CLK_CTRL, 9, 0x7, a9_amfc_parents, NULL); > +static A9_COMP_DIV(amfc, AMFC_CLK_CTRL, 0, 6); > +static A9_COMP_GATE(amfc, AMFC_CLK_CTRL, 8, 0); > + > +static const struct clk_parent_data a9_nna_parents[] = { > + { .fw_name = "xtal", }, > + { .fw_name = "fdiv2p5", }, > + { .fw_name = "fdiv4", }, > + { .fw_name = "fdiv3", }, > + { .fw_name = "fdiv5", }, > + { .fw_name = "fdiv2", }, > + { .fw_name = "gp2", }, > + { .fw_name = "hifi", } hifi isn't in the dt bindings. Should this be hifi0 and/or hifi1? > +}; > + > +static A9_COMP_SEL(nna, NNA_CLK_CTRL, 9, 0x7, a9_nna_parents, NULL); > +static A9_COMP_DIV(nna, NNA_CLK_CTRL, 0, 7); > +static A9_COMP_GATE(nna, NNA_CLK_CTRL, 8, 0); > + > +/* Channel 5 and 6 are unconnected. */ > +static u32 a9_usb_250m_parents_val_table[] = { 0, 1, 2, 3, 4, 7 }; > +static const struct clk_parent_data a9_usb_250m_parents[] = { > + { .fw_name = "fdiv4", }, > + { .fw_name = "fdiv3", }, > + { .fw_name = "fdiv5", }, > + { .fw_name = "fdiv2", }, > + { .fw_name = "fdiv7", }, > + { .fw_name = "fdiv2p5", } > +}; > + > +static A9_COMP_SEL(usb_250m, USB_CLK_CTRL, 9, 0x7, a9_usb_250m_parents, > + a9_usb_250m_parents_val_table); > +static A9_COMP_DIV(usb_250m, USB_CLK_CTRL, 0, 7); > +static A9_COMP_GATE(usb_250m, USB_CLK_CTRL, 8, 0); > + > +static const struct clk_parent_data a9_usb_48m_pre_parents[] = { > + { .fw_name = "fdiv4", }, > + { .fw_name = "fdiv3", }, > + { .fw_name = "fdiv5", }, > + { .fw_name = "fdiv2", }, > + { .fw_name = "fdiv7", }, > + { .fw_name = "fdiv2p5", } > +}; > + > +static A9_COMP_SEL(usb_48m_pre, USB_CLK_CTRL, 25, 0x7, a9_usb_48m_pre_parents, > + NULL); > +static A9_COMP_DIV(usb_48m_pre, USB_CLK_CTRL, 16, 7); > +static A9_COMP_GATE(usb_48m_pre, USB_CLK_CTRL, 24, 0); > + > +static const struct clk_parent_data a9_pcie_tl_parents[] = { > + { .fw_name = "fdiv4", }, > + { .fw_name = "fdiv3", }, > + { .fw_name = "fdiv5", }, > + { .fw_name = "fdiv2", }, > + { .fw_name = "fdiv2p5", }, > + { .fw_name = "gp0", }, > + { .fw_name = "sys", }, > + { .fw_name = "xtal", } > +}; > + > +static A9_COMP_SEL(pcie_tl, PCIE_TL_CLK_CTRL, 9, 0x7, a9_pcie_tl_parents, > + NULL); > +static A9_COMP_DIV(pcie_tl, PCIE_TL_CLK_CTRL, 0, 7); > +static A9_COMP_GATE(pcie_tl, PCIE_TL_CLK_CTRL, 8, 0); > + > +static A9_COMP_SEL(pcie1_tl, PCIE_TL_CLK_CTRL, 25, 0x7, a9_pcie_tl_parents, > + NULL); > +static A9_COMP_DIV(pcie1_tl, PCIE_TL_CLK_CTRL, 16, 7); > +static A9_COMP_GATE(pcie1_tl, PCIE_TL_CLK_CTRL, 24, 0); > + > +static const struct clk_parent_data a9_cmpr_parents[] = { > + { .fw_name = "xtal", }, > + { .fw_name = "fdiv2p5", }, > + { .fw_name = "fdiv3", }, > + { .fw_name = "fdiv4", }, > + { .fw_name = "fdiv5", }, > + { .fw_name = "fdiv7", }, > + { .fw_name = "hifi0", }, > + { .fw_name = "gp1", } > +}; > + > +static A9_COMP_SEL(cmpr, CMPR_CLK_CTRL, 25, 0x7, a9_cmpr_parents, NULL); > +static A9_COMP_DIV(cmpr, CMPR_CLK_CTRL, 16, 7); > +static A9_COMP_GATE(cmpr, CMPR_CLK_CTRL, 24, 0); > + > +static const struct clk_parent_data a9_dewarpa_parents[] = { > + { .fw_name = "fdiv2p5", }, > + { .fw_name = "fdiv3", }, > + { .fw_name = "fdiv4", }, > + { .fw_name = "fdiv5", }, > + { .fw_name = "fdiv7", }, > + { .fw_name = "gp0", }, > + { .fw_name = "hifi0", }, > + { .fw_name = "gp1", } > +}; > + > +static A9_COMP_SEL(dewarpa, DEWARP_CLK_CTRL, 9, 0x7, a9_dewarpa_parents, NULL); > +static A9_COMP_DIV(dewarpa, DEWARP_CLK_CTRL, 0, 7); > +static A9_COMP_GATE(dewarpa, DEWARP_CLK_CTRL, 8, 0); > + > +static const struct clk_parent_data a9_sc_parents[] = { > + { .fw_name = "fdiv2", }, > + { .fw_name = "fdiv3", }, > + { .fw_name = "fdiv5", }, > + { .fw_name = "xtal", } > +}; > + > +static A9_COMP_SEL(sc_pre, SC_CLK_CTRL, 9, 0x7, a9_sc_parents, NULL); > +static A9_COMP_DIV(sc_pre, SC_CLK_CTRL, 0, 8); > +static A9_COMP_GATE(sc_pre, SC_CLK_CTRL, 8, 0); > + > +static struct clk_regmap a9_sc = { > + .data = &(struct clk_regmap_div_data) { > + .offset = SC_CLK_CTRL, > + .shift = 16, > + .width = 4, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "sc", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_sc_pre.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, You can use CLK_HW_INIT_HWS() here. Brian From bmasney at redhat.com Mon May 11 08:45:47 2026 From: bmasney at redhat.com (Brian Masney) Date: Mon, 11 May 2026 11:45:47 -0400 Subject: [PATCH 10/10] clk: amlogic: Add A9 AO clock controller driver In-Reply-To: <20260511-b4-a9_clk-v1-10-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-10-41cb4071b7c9@amlogic.com> Message-ID: Hi Jian, On Mon, May 11, 2026 at 08:47:32PM +0800, Jian Hu via B4 Relay wrote: > From: Jian Hu > > Add the Always-on clock controller driver for the Amlogic A9 SoC family. > > Signed-off-by: Jian Hu I'll only flag new things that I spot here that weren't mentioned in the other patches I reviewed in this series. > --- > drivers/clk/meson/Makefile | 2 +- > drivers/clk/meson/a9-aoclk.c | 494 +++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 495 insertions(+), 1 deletion(-) > > diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile > index 2b5b67b14efc..91af609ce815 100644 > --- a/drivers/clk/meson/Makefile > +++ b/drivers/clk/meson/Makefile > @@ -20,7 +20,7 @@ obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o > obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o > obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o > obj-$(CONFIG_COMMON_CLK_A9_PLL) += a9-pll.o > -obj-$(CONFIG_COMMON_CLK_A9_PERIPHERALS) += a9-peripherals.o > +obj-$(CONFIG_COMMON_CLK_A9_PERIPHERALS) += a9-peripherals.o a9-aoclk.o > obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o > obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o > obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o > diff --git a/drivers/clk/meson/a9-aoclk.c b/drivers/clk/meson/a9-aoclk.c > new file mode 100644 > index 000000000000..3c42eaf585d2 > --- /dev/null > +++ b/drivers/clk/meson/a9-aoclk.c > @@ -0,0 +1,494 @@ > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) > +/* > + * Copyright (C) 2026 Amlogic, Inc. All rights reserved > + */ > + > +#include > +#include > +#include > +#include "clk-regmap.h" > +#include "clk-dualdiv.h" > +#include "meson-clkc-utils.h" > + > +#define AO_OSCIN_CTRL 0x00 > +#define AO_SYS_CLK0 0x04 > +#define AO_PWM_CLK_A_CTRL 0x1c > +#define AO_PWM_CLK_B_CTRL 0x20 > +#define AO_PWM_CLK_C_CTRL 0x24 > +#define AO_PWM_CLK_D_CTRL 0x28 > +#define AO_PWM_CLK_E_CTRL 0x2c > +#define AO_PWM_CLK_F_CTRL 0x30 > +#define AO_PWM_CLK_G_CTRL 0x34 > +#define AO_CEC_CTRL0 0x38 > +#define AO_CEC_CTRL1 0x3c > +#define AO_RTC_BY_OSCIN_CTRL0 0x50 > +#define AO_RTC_BY_OSCIN_CTRL1 0x54 > + > +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata) \ > + MESON_COMP_SEL(a9_, _name, _reg, _shift, _mask, _pdata, NULL, 0, 0) > + > +#define A9_COMP_DIV(_name, _reg, _shift, _width) \ > + MESON_COMP_DIV(a9_, _name, _reg, _shift, _width, 0, CLK_SET_RATE_PARENT) > + > +#define A9_COMP_GATE(_name, _reg, _bit) \ > + MESON_COMP_GATE(a9_, _name, _reg, _bit, CLK_SET_RATE_PARENT) > + > +static struct clk_regmap a9_ao_xtal_in = { > + .data = &(struct clk_regmap_gate_data){ > + .offset = AO_OSCIN_CTRL, > + .bit_idx = 3, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "ao_xtal_in", > + .ops = &clk_regmap_gate_ops, > + .parent_data = &(const struct clk_parent_data) { > + .fw_name = "xtal", > + }, > + .num_parents = 1, > + /* > + * It may be ao_sys's parent clock, its child clocks mark > + * CLK_IS_CRITICAL, So mark CLK_IS_CRITICAL for it. > + */ > + .flags = CLK_IS_CRITICAL, > + }, > +}; > + > +static struct clk_regmap a9_ao_xtal = { > + .data = &(struct clk_regmap_mux_data) { > + .offset = AO_OSCIN_CTRL, > + .mask = 0x1, > + .shift = 0, > + }, > + .hw.init = &(struct clk_init_data){ > + .name = "ao_xtal", > + .ops = &clk_regmap_mux_ops, > + /* ext_32k is from external PAD, do not automatically reparent */ > + .parent_data = (const struct clk_parent_data []) { > + { .hw = &a9_ao_xtal_in.hw }, > + { .fw_name = "ext_32k", }, > + }, > + .num_parents = 2, > + .flags = CLK_SET_RATE_NO_REPARENT, > + }, > +}; > + > +static struct clk_regmap a9_ao_sys = { > + .data = &(struct clk_regmap_mux_data) { > + .offset = AO_OSCIN_CTRL, > + .mask = 0x1, > + .shift = 1, > + }, > + .hw.init = &(struct clk_init_data){ > + .name = "ao_sys", > + .ops = &clk_regmap_mux_ops, > + .parent_data = (const struct clk_parent_data []) { > + { .hw = &a9_ao_xtal.hw }, > + { .fw_name = "sys", }, > + }, > + .num_parents = 2, > + .flags = CLK_SET_PARENT_GATE, > + }, > +}; > + > +static const struct clk_parent_data a9_ao_pclk_parents = { .hw = &a9_ao_sys.hw }; > + > +#define A9_AO_PCLK(_name, _bit, _flags) \ > + MESON_PCLK(a9_ao_sys_##_name, AO_SYS_CLK0, _bit, \ > + &a9_ao_pclk_parents, _flags) > + > +/* > + * A9 integrates a low-power microprocessor (Always-on CPU: AOCPU). Some AO sys > + * clocks control the AOCPU modules. Mark the AOCPU-related clocks with > + * CLK_IS_CRITICAL to avoid them being disabled and impacting AOCPU functionality. > + * AOCPU-related clocks list: > + * - clktree > + * - rst_ctrl > + * - pad > + * - irq > + * - pwrctrl > + * - aocpu > + * - sram > + */ > +static A9_AO_PCLK(i2c3, 0, 0); > +static A9_AO_PCLK(rtc_reg, 1, 0); > +static A9_AO_PCLK(clktree, 2, CLK_IS_CRITICAL); > +static A9_AO_PCLK(rst_ctrl, 3, CLK_IS_CRITICAL); > +static A9_AO_PCLK(pad, 4, CLK_IS_CRITICAL); > +static A9_AO_PCLK(rtc_dig, 5, 0); > +static A9_AO_PCLK(irq, 6, CLK_IS_CRITICAL); > +static A9_AO_PCLK(pwrctrl, 7, CLK_IS_CRITICAL); > +static A9_AO_PCLK(pwm_a, 8, 0); > +static A9_AO_PCLK(pwm_b, 9, 0); > +static A9_AO_PCLK(pwm_c, 10, 0); > +static A9_AO_PCLK(pwm_d, 11, 0); > +static A9_AO_PCLK(pwm_e, 12, 0); > +static A9_AO_PCLK(pwm_f, 13, 0); > +static A9_AO_PCLK(pwm_g, 14, 0); > +static A9_AO_PCLK(i2c_a, 15, 0); > +static A9_AO_PCLK(i2c_b, 16, 0); > +static A9_AO_PCLK(i2c_c, 17, 0); > +static A9_AO_PCLK(i2c_d, 18, 0); > +static A9_AO_PCLK(sed, 19, 0); > +static A9_AO_PCLK(ir_ctrl, 20, 0); > +static A9_AO_PCLK(uart_b, 21, 0); > +static A9_AO_PCLK(uart_c, 22, 0); > +static A9_AO_PCLK(uart_d, 23, 0); > +static A9_AO_PCLK(uart_e, 24, 0); > +static A9_AO_PCLK(spisg_0, 25, 0); > +static A9_AO_PCLK(rtc_secure, 26, 0); > +static A9_AO_PCLK(cec, 27, 0); > +static A9_AO_PCLK(aocpu, 28, CLK_IS_CRITICAL); > +static A9_AO_PCLK(sram, 29, CLK_IS_CRITICAL); > +static A9_AO_PCLK(spisg_1, 30, 0); > +static A9_AO_PCLK(spisg_2, 31, 0); > + > +static const struct clk_parent_data a9_ao_pwm_parents[] = { > + { .hw = &a9_ao_xtal.hw }, > + { .fw_name = "fdiv5", }, > + { .fw_name = "fdiv4", }, > + { .fw_name = "fdiv3", } > +}; > + > +static A9_COMP_SEL(ao_pwm_a, AO_PWM_CLK_A_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_a, AO_PWM_CLK_A_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_a, AO_PWM_CLK_A_CTRL, 8); > + > +static A9_COMP_SEL(ao_pwm_b, AO_PWM_CLK_B_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_b, AO_PWM_CLK_B_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_b, AO_PWM_CLK_A_CTRL, 8); Should this be AO_PWM_CLK_B_CTRL ? > + > +static A9_COMP_SEL(ao_pwm_c, AO_PWM_CLK_C_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_c, AO_PWM_CLK_C_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_c, AO_PWM_CLK_C_CTRL, 8); > + > +static A9_COMP_SEL(ao_pwm_d, AO_PWM_CLK_D_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_d, AO_PWM_CLK_D_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_d, AO_PWM_CLK_D_CTRL, 8); > + > +static A9_COMP_SEL(ao_pwm_e, AO_PWM_CLK_E_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_e, AO_PWM_CLK_E_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_e, AO_PWM_CLK_E_CTRL, 8); > + > +static A9_COMP_SEL(ao_pwm_f, AO_PWM_CLK_F_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_f, AO_PWM_CLK_F_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_f, AO_PWM_CLK_F_CTRL, 8); > + > +static A9_COMP_SEL(ao_pwm_g, AO_PWM_CLK_G_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_g, AO_PWM_CLK_G_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_g, AO_PWM_CLK_G_CTRL, 8); > + > +static struct clk_regmap a9_ao_rtc_dualdiv_in = { > + .data = &(struct clk_regmap_gate_data){ > + .offset = AO_RTC_BY_OSCIN_CTRL0, > + .bit_idx = 31, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "ao_rtc_duandiv_in", s/duandiv/dualdiv/ ? Brian From bmasney at redhat.com Mon May 11 08:47:10 2026 From: bmasney at redhat.com (Brian Masney) Date: Mon, 11 May 2026 11:47:10 -0400 Subject: [PATCH 05/10] clk: amlogic: PLL l_detect signal supports active-high configuration In-Reply-To: <20260511-b4-a9_clk-v1-5-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-5-41cb4071b7c9@amlogic.com> Message-ID: On Mon, May 11, 2026 at 08:47:27PM +0800, Jian Hu via B4 Relay wrote: > From: Jian Hu > > l_detect controls the enable/disable of the PLL lock-detect module. > > For A9, the l_detect signal is active-high: > 0 -> Disable lock-detect module; > 1 -> Enable lock-detect module. > > Here, a flag CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH is added to handle cases > like A9, where the signal is active-high. > > Signed-off-by: Jian Hu Reviewed-by: Brian Masney From robh at kernel.org Mon May 11 09:59:36 2026 From: robh at kernel.org (Rob Herring (Arm)) Date: Mon, 11 May 2026 11:59:36 -0500 Subject: [PATCH] dt-bindings: Consolidate "sram" property definition Message-ID: <20260511165942.2774868-1-robh@kernel.org> The "sram" property has become a de facto standard property, so create a common schema for it and drop all the duplicated definitions. Signed-off-by: Rob Herring (Arm) --- .../imx/fsl,imx8qxp-dc-command-sequencer.yaml | 2 +- .../devicetree/bindings/display/msm/gpu.yaml | 6 +---- .../bindings/dma/stericsson,dma40.yaml | 8 ++---- .../bindings/media/cnm,wave521c.yaml | 2 +- .../bindings/media/nxp,imx8-jpeg.yaml | 6 ++--- .../bindings/media/rockchip,vdec.yaml | 5 ++-- .../bindings/media/st,stm32-dcmi.yaml | 6 ++--- .../devicetree/bindings/net/mediatek,net.yaml | 3 +-- .../bindings/net/ti,icssg-prueth.yaml | 2 +- .../bindings/net/ti,icssm-prueth.yaml | 2 +- .../remoteproc/amlogic,meson-mx-ao-arc.yaml | 7 +---- .../bindings/remoteproc/ti,k3-dsp-rproc.yaml | 8 ------ .../bindings/remoteproc/ti,k3-r5f-rproc.yaml | 8 ------ .../remoteproc/xlnx,zynqmp-r5fss.yaml | 9 +------ .../devicetree/bindings/spi/st,stm32-spi.yaml | 10 +++---- .../bindings/sram/sram-consumer.yaml | 26 +++++++++++++++++++ 16 files changed, 48 insertions(+), 62 deletions(-) create mode 100644 Documentation/devicetree/bindings/sram/sram-consumer.yaml diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml index 27118f4c0d28..fd095e5742c5 100644 --- a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml +++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml @@ -41,7 +41,7 @@ properties: - const: sw3 sram: - $ref: /schemas/types.yaml#/definitions/phandle + maxItems: 1 description: phandle pointing to the mmio-sram device node required: diff --git a/Documentation/devicetree/bindings/display/msm/gpu.yaml b/Documentation/devicetree/bindings/display/msm/gpu.yaml index 04b2328903ca..358759fad8dc 100644 --- a/Documentation/devicetree/bindings/display/msm/gpu.yaml +++ b/Documentation/devicetree/bindings/display/msm/gpu.yaml @@ -84,13 +84,9 @@ properties: maxItems: 64 sram: - $ref: /schemas/types.yaml#/definitions/phandle-array minItems: 1 maxItems: 4 - items: - maxItems: 1 - description: | - phandles to one or more reserved on-chip SRAM regions. + description: phandle to the On Chip Memory (OCMEM) that's present on some a3xx and a4xx Snapdragon SoCs. See Documentation/devicetree/bindings/sram/qcom,ocmem.yaml diff --git a/Documentation/devicetree/bindings/dma/stericsson,dma40.yaml b/Documentation/devicetree/bindings/dma/stericsson,dma40.yaml index 607da11e7baa..d8f92838f4c9 100644 --- a/Documentation/devicetree/bindings/dma/stericsson,dma40.yaml +++ b/Documentation/devicetree/bindings/dma/stericsson,dma40.yaml @@ -136,13 +136,9 @@ properties: maxItems: 1 sram: - $ref: /schemas/types.yaml#/definitions/phandle-array - description: A phandle array with inner size 1 (no arg cells). - First phandle is the LCPA (Logical Channel Parameter Address) memory. - Second phandle is the LCLA (Logical Channel Link base Address) memory. - maxItems: 2 items: - maxItems: 1 + - description: LCPA (Logical Channel Parameter Address) memory. + - description: LCLA (Logical Channel Link base Address) memory. memcpy-channels: $ref: /schemas/types.yaml#/definitions/uint32-array diff --git a/Documentation/devicetree/bindings/media/cnm,wave521c.yaml b/Documentation/devicetree/bindings/media/cnm,wave521c.yaml index 6a11c1d11fb5..6cd33dfd095d 100644 --- a/Documentation/devicetree/bindings/media/cnm,wave521c.yaml +++ b/Documentation/devicetree/bindings/media/cnm,wave521c.yaml @@ -37,7 +37,7 @@ properties: maxItems: 1 sram: - $ref: /schemas/types.yaml#/definitions/phandle + maxItems: 1 description: The VPU uses the SRAM to store some of the reference data instead of storing it on DMA memory. It is mainly used for the purpose of reducing diff --git a/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml b/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml index 18cc6315a821..6ba668aa633d 100644 --- a/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml +++ b/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml @@ -56,10 +56,10 @@ properties: maxItems: 5 # Wrapper and 4 slots sram: - $ref: /schemas/types.yaml#/definitions/phandle + maxItems: 1 description: - Optional phandle to a reserved on-chip SRAM regions. The SRAM can - be used for descriptor storage, which may improve bus utilization. + The SRAM can be used for descriptor storage, which may improve bus + utilization. required: - compatible diff --git a/Documentation/devicetree/bindings/media/rockchip,vdec.yaml b/Documentation/devicetree/bindings/media/rockchip,vdec.yaml index 42022401d0ff..4f38a0ef29d8 100644 --- a/Documentation/devicetree/bindings/media/rockchip,vdec.yaml +++ b/Documentation/devicetree/bindings/media/rockchip,vdec.yaml @@ -91,9 +91,8 @@ properties: maxItems: 1 sram: - $ref: /schemas/types.yaml#/definitions/phandle - description: | - phandle to a reserved on-chip SRAM regions. + maxItems: 1 + description: Some SoCs, like rk3588 provide on-chip SRAM to store temporary buffers during decoding. diff --git a/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml b/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml index d9fbb90b0977..7c2ddd27780f 100644 --- a/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml +++ b/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml @@ -47,10 +47,10 @@ properties: maxItems: 1 sram: - $ref: /schemas/types.yaml#/definitions/phandle + maxItems: 1 description: - phandle to a reserved SRAM region which is used as temporary - storage memory between DMA and MDMA engines. + SRAM region which is used as temporary storage memory between DMA and + MDMA engines. port: $ref: /schemas/graph.yaml#/$defs/port-base diff --git a/Documentation/devicetree/bindings/net/mediatek,net.yaml b/Documentation/devicetree/bindings/net/mediatek,net.yaml index cc346946291a..6bbd83c6aaf7 100644 --- a/Documentation/devicetree/bindings/net/mediatek,net.yaml +++ b/Documentation/devicetree/bindings/net/mediatek,net.yaml @@ -67,8 +67,7 @@ properties: - const: ppe sram: - $ref: /schemas/types.yaml#/definitions/phandle - description: phandle to mmio SRAM + maxItems: 1 mediatek,ethsys: $ref: /schemas/types.yaml#/definitions/phandle diff --git a/Documentation/devicetree/bindings/net/ti,icssg-prueth.yaml b/Documentation/devicetree/bindings/net/ti,icssg-prueth.yaml index c296e5711848..883033b19b8f 100644 --- a/Documentation/devicetree/bindings/net/ti,icssg-prueth.yaml +++ b/Documentation/devicetree/bindings/net/ti,icssg-prueth.yaml @@ -21,7 +21,7 @@ properties: - ti,am654-sr1-icssg-prueth # for AM65x SoC family, SR1.0 sram: - $ref: /schemas/types.yaml#/definitions/phandle + maxItems: 1 description: phandle to MSMC SRAM node diff --git a/Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml b/Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml index a98ad45ca66f..9370c43bc66a 100644 --- a/Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml +++ b/Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml @@ -24,7 +24,7 @@ properties: - ti,am3359-prueth # for AM33x SoC family sram: - $ref: /schemas/types.yaml#/definitions/phandle + maxItems: 1 description: phandle to OCMC SRAM node diff --git a/Documentation/devicetree/bindings/remoteproc/amlogic,meson-mx-ao-arc.yaml b/Documentation/devicetree/bindings/remoteproc/amlogic,meson-mx-ao-arc.yaml index 76e8ca44906a..3f710433e937 100644 --- a/Documentation/devicetree/bindings/remoteproc/amlogic,meson-mx-ao-arc.yaml +++ b/Documentation/devicetree/bindings/remoteproc/amlogic,meson-mx-ao-arc.yaml @@ -48,12 +48,7 @@ properties: minItems: 1 sram: - $ref: /schemas/types.yaml#/definitions/phandle - description: - phandles to a reserved SRAM region which is used as the memory of - the ARC core. The region should be defined as child nodes of the - AHB SRAM node as per the generic bindings in - Documentation/devicetree/bindings/sram/sram.yaml + maxItems: 1 amlogic,secbus2: $ref: /schemas/types.yaml#/definitions/phandle diff --git a/Documentation/devicetree/bindings/remoteproc/ti,k3-dsp-rproc.yaml b/Documentation/devicetree/bindings/remoteproc/ti,k3-dsp-rproc.yaml index b51bb863d759..8b1ed384ef22 100644 --- a/Documentation/devicetree/bindings/remoteproc/ti,k3-dsp-rproc.yaml +++ b/Documentation/devicetree/bindings/remoteproc/ti,k3-dsp-rproc.yaml @@ -75,16 +75,8 @@ properties: # -------------------- sram: - $ref: /schemas/types.yaml#/definitions/phandle-array minItems: 1 maxItems: 4 - items: - maxItems: 1 - description: | - phandles to one or more reserved on-chip SRAM regions. The regions - should be defined as child nodes of the respective SRAM node, and - should be defined as per the generic bindings in, - Documentation/devicetree/bindings/sram/sram.yaml allOf: - if: diff --git a/Documentation/devicetree/bindings/remoteproc/ti,k3-r5f-rproc.yaml b/Documentation/devicetree/bindings/remoteproc/ti,k3-r5f-rproc.yaml index 775e9b3a1938..14e6b2f817b3 100644 --- a/Documentation/devicetree/bindings/remoteproc/ti,k3-r5f-rproc.yaml +++ b/Documentation/devicetree/bindings/remoteproc/ti,k3-r5f-rproc.yaml @@ -224,16 +224,8 @@ patternProperties: at 0x0) or 0 (BTCM at 0x0), default value is 1 if omitted. sram: - $ref: /schemas/types.yaml#/definitions/phandle-array minItems: 1 maxItems: 4 - items: - maxItems: 1 - description: | - phandles to one or more reserved on-chip SRAM regions. The regions - should be defined as child nodes of the respective SRAM node, and - should be defined as per the generic bindings in, - Documentation/devicetree/bindings/sram/sram.yaml required: - compatible diff --git a/Documentation/devicetree/bindings/remoteproc/xlnx,zynqmp-r5fss.yaml b/Documentation/devicetree/bindings/remoteproc/xlnx,zynqmp-r5fss.yaml index ee63c03949c9..c7d5e58330d6 100644 --- a/Documentation/devicetree/bindings/remoteproc/xlnx,zynqmp-r5fss.yaml +++ b/Documentation/devicetree/bindings/remoteproc/xlnx,zynqmp-r5fss.yaml @@ -106,20 +106,13 @@ patternProperties: - const: rx sram: - $ref: /schemas/types.yaml#/definitions/phandle-array minItems: 1 maxItems: 8 - items: - maxItems: 1 - description: | + description: phandles to one or more reserved on-chip SRAM regions. Other than TCM, the RPU can execute instructions and access data from the OCM memory, the main DDR memory, and other system memories. - The regions should be defined as child nodes of the respective SRAM - node, and should be defined as per the generic bindings in - Documentation/devicetree/bindings/sram/sram.yaml - memory-region: description: | List of phandles to the reserved memory regions associated with the diff --git a/Documentation/devicetree/bindings/spi/st,stm32-spi.yaml b/Documentation/devicetree/bindings/spi/st,stm32-spi.yaml index 472e92974714..6d7d595e4ab3 100644 --- a/Documentation/devicetree/bindings/spi/st,stm32-spi.yaml +++ b/Documentation/devicetree/bindings/spi/st,stm32-spi.yaml @@ -89,12 +89,10 @@ properties: - const: rxm2m sram: - $ref: /schemas/types.yaml#/definitions/phandle - description: | - Phandles to a reserved SRAM region which is used as temporary - storage memory between DMA and MDMA engines. - The region should be defined as child node of the AHB SRAM node - as per the generic bindings in Documentation/devicetree/bindings/sram/sram.yaml + maxItems: 1 + description: + SRAM region which is used as temporary storage memory between DMA and + MDMA engines. power-domains: maxItems: 1 diff --git a/Documentation/devicetree/bindings/sram/sram-consumer.yaml b/Documentation/devicetree/bindings/sram/sram-consumer.yaml new file mode 100644 index 000000000000..f00087bd2879 --- /dev/null +++ b/Documentation/devicetree/bindings/sram/sram-consumer.yaml @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sram/sram-consumer.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SRAM Consumers + +maintainers: + - Rob Herring + +select: true + +properties: + sram: + description: + Phandles to one or more reserved on-chip SRAM regions. The regions + should be defined as child nodes of the respective SRAM node, and + should be defined as per the generic bindings in, + Documentation/devicetree/bindings/sram/sram.yaml + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + maxItems: 1 + +additionalProperties: true +... -- 2.53.0 From linusw at kernel.org Mon May 11 11:55:46 2026 From: linusw at kernel.org (Linus Walleij) Date: Mon, 11 May 2026 20:55:46 +0200 Subject: [PATCH] dt-bindings: Consolidate "sram" property definition In-Reply-To: <20260511165942.2774868-1-robh@kernel.org> References: <20260511165942.2774868-1-robh@kernel.org> Message-ID: On Mon, May 11, 2026 at 6:59?PM Rob Herring (Arm) wrote: > The "sram" property has become a de facto standard property, so create a > common schema for it and drop all the duplicated definitions. > > Signed-off-by: Rob Herring (Arm) I like it. Reviewed-by: Linus Walleij Yours, Linus Walleij From linusw at kernel.org Mon May 11 13:26:13 2026 From: linusw at kernel.org (Linus Walleij) Date: Mon, 11 May 2026 22:26:13 +0200 Subject: [PATCH v2 0/2] pinctrl: add support amlogic a9 In-Reply-To: <20260507-a9-pinctrl-v2-0-49774feff2ef@amlogic.com> References: <20260507-a9-pinctrl-v2-0-49774feff2ef@amlogic.com> Message-ID: On Thu, May 7, 2026 at 10:21?AM Xianwei Zhao via B4 Relay wrote: > Add pinctrl bindings and driver about for amlogic a9. > > Signed-off-by: Xianwei Zhao Patches applied! Yours, Linus Walleij From kuba at kernel.org Mon May 11 16:09:32 2026 From: kuba at kernel.org (Jakub Kicinski) Date: Mon, 11 May 2026 16:09:32 -0700 Subject: [PATCH] dt-bindings: Consolidate "sram" property definition In-Reply-To: <20260511165942.2774868-1-robh@kernel.org> References: <20260511165942.2774868-1-robh@kernel.org> Message-ID: <20260511160932.0e2cf50a@kernel.org> On Mon, 11 May 2026 11:59:36 -0500 Rob Herring (Arm) wrote: > .../bindings/net/ti,icssg-prueth.yaml | 2 +- > .../bindings/net/ti,icssm-prueth.yaml | 2 +- Acked-by: Jakub Kicinski From broonie at kernel.org Mon May 11 17:46:57 2026 From: broonie at kernel.org (Mark Brown) Date: Tue, 12 May 2026 09:46:57 +0900 Subject: [PATCH] dt-bindings: Consolidate "sram" property definition In-Reply-To: <20260511165942.2774868-1-robh@kernel.org> References: <20260511165942.2774868-1-robh@kernel.org> Message-ID: On Mon, May 11, 2026 at 11:59:36AM -0500, Rob Herring (Arm) wrote: > The "sram" property has become a de facto standard property, so create a > common schema for it and drop all the duplicated definitions. Acked-by: Mark Brown -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 488 bytes Desc: not available URL: From victor.liu at nxp.com Mon May 11 19:57:59 2026 From: victor.liu at nxp.com (Liu Ying) Date: Tue, 12 May 2026 10:57:59 +0800 Subject: [PATCH] dt-bindings: Consolidate "sram" property definition In-Reply-To: <20260511165942.2774868-1-robh@kernel.org> References: <20260511165942.2774868-1-robh@kernel.org> Message-ID: On Mon, May 11, 2026 at 11:59:36AM -0500, Rob Herring (Arm) wrote: > .../imx/fsl,imx8qxp-dc-command-sequencer.yaml | 2 +- Reviewed-by: Liu Ying #fsl,imx8qxp-dc-command-sequencer.yaml -- Regards, Liu Ying From lorenzo at kernel.org Tue May 12 02:15:12 2026 From: lorenzo at kernel.org (Lorenzo Bianconi) Date: Tue, 12 May 2026 11:15:12 +0200 Subject: [PATCH] dt-bindings: Consolidate "sram" property definition In-Reply-To: <20260511165942.2774868-1-robh@kernel.org> References: <20260511165942.2774868-1-robh@kernel.org> Message-ID: > The "sram" property has become a de facto standard property, so create a > common schema for it and drop all the duplicated definitions. > > Signed-off-by: Rob Herring (Arm) Acked-by: Lorenzo Bianconi > --- > .../imx/fsl,imx8qxp-dc-command-sequencer.yaml | 2 +- > .../devicetree/bindings/display/msm/gpu.yaml | 6 +---- > .../bindings/dma/stericsson,dma40.yaml | 8 ++---- > .../bindings/media/cnm,wave521c.yaml | 2 +- > .../bindings/media/nxp,imx8-jpeg.yaml | 6 ++--- > .../bindings/media/rockchip,vdec.yaml | 5 ++-- > .../bindings/media/st,stm32-dcmi.yaml | 6 ++--- > .../devicetree/bindings/net/mediatek,net.yaml | 3 +-- > .../bindings/net/ti,icssg-prueth.yaml | 2 +- > .../bindings/net/ti,icssm-prueth.yaml | 2 +- > .../remoteproc/amlogic,meson-mx-ao-arc.yaml | 7 +---- > .../bindings/remoteproc/ti,k3-dsp-rproc.yaml | 8 ------ > .../bindings/remoteproc/ti,k3-r5f-rproc.yaml | 8 ------ > .../remoteproc/xlnx,zynqmp-r5fss.yaml | 9 +------ > .../devicetree/bindings/spi/st,stm32-spi.yaml | 10 +++---- > .../bindings/sram/sram-consumer.yaml | 26 +++++++++++++++++++ > 16 files changed, 48 insertions(+), 62 deletions(-) > create mode 100644 Documentation/devicetree/bindings/sram/sram-consumer.yaml > > diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml > index 27118f4c0d28..fd095e5742c5 100644 > --- a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml > +++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml > @@ -41,7 +41,7 @@ properties: > - const: sw3 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > + maxItems: 1 > description: phandle pointing to the mmio-sram device node > > required: > diff --git a/Documentation/devicetree/bindings/display/msm/gpu.yaml b/Documentation/devicetree/bindings/display/msm/gpu.yaml > index 04b2328903ca..358759fad8dc 100644 > --- a/Documentation/devicetree/bindings/display/msm/gpu.yaml > +++ b/Documentation/devicetree/bindings/display/msm/gpu.yaml > @@ -84,13 +84,9 @@ properties: > maxItems: 64 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle-array > minItems: 1 > maxItems: 4 > - items: > - maxItems: 1 > - description: | > - phandles to one or more reserved on-chip SRAM regions. > + description: > phandle to the On Chip Memory (OCMEM) that's present on some a3xx and > a4xx Snapdragon SoCs. See > Documentation/devicetree/bindings/sram/qcom,ocmem.yaml > diff --git a/Documentation/devicetree/bindings/dma/stericsson,dma40.yaml b/Documentation/devicetree/bindings/dma/stericsson,dma40.yaml > index 607da11e7baa..d8f92838f4c9 100644 > --- a/Documentation/devicetree/bindings/dma/stericsson,dma40.yaml > +++ b/Documentation/devicetree/bindings/dma/stericsson,dma40.yaml > @@ -136,13 +136,9 @@ properties: > maxItems: 1 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle-array > - description: A phandle array with inner size 1 (no arg cells). > - First phandle is the LCPA (Logical Channel Parameter Address) memory. > - Second phandle is the LCLA (Logical Channel Link base Address) memory. > - maxItems: 2 > items: > - maxItems: 1 > + - description: LCPA (Logical Channel Parameter Address) memory. > + - description: LCLA (Logical Channel Link base Address) memory. > > memcpy-channels: > $ref: /schemas/types.yaml#/definitions/uint32-array > diff --git a/Documentation/devicetree/bindings/media/cnm,wave521c.yaml b/Documentation/devicetree/bindings/media/cnm,wave521c.yaml > index 6a11c1d11fb5..6cd33dfd095d 100644 > --- a/Documentation/devicetree/bindings/media/cnm,wave521c.yaml > +++ b/Documentation/devicetree/bindings/media/cnm,wave521c.yaml > @@ -37,7 +37,7 @@ properties: > maxItems: 1 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > + maxItems: 1 > description: > The VPU uses the SRAM to store some of the reference data instead of > storing it on DMA memory. It is mainly used for the purpose of reducing > diff --git a/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml b/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml > index 18cc6315a821..6ba668aa633d 100644 > --- a/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml > +++ b/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml > @@ -56,10 +56,10 @@ properties: > maxItems: 5 # Wrapper and 4 slots > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > + maxItems: 1 > description: > - Optional phandle to a reserved on-chip SRAM regions. The SRAM can > - be used for descriptor storage, which may improve bus utilization. > + The SRAM can be used for descriptor storage, which may improve bus > + utilization. > > required: > - compatible > diff --git a/Documentation/devicetree/bindings/media/rockchip,vdec.yaml b/Documentation/devicetree/bindings/media/rockchip,vdec.yaml > index 42022401d0ff..4f38a0ef29d8 100644 > --- a/Documentation/devicetree/bindings/media/rockchip,vdec.yaml > +++ b/Documentation/devicetree/bindings/media/rockchip,vdec.yaml > @@ -91,9 +91,8 @@ properties: > maxItems: 1 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > - description: | > - phandle to a reserved on-chip SRAM regions. > + maxItems: 1 > + description: > Some SoCs, like rk3588 provide on-chip SRAM to store temporary > buffers during decoding. > > diff --git a/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml b/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml > index d9fbb90b0977..7c2ddd27780f 100644 > --- a/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml > +++ b/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml > @@ -47,10 +47,10 @@ properties: > maxItems: 1 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > + maxItems: 1 > description: > - phandle to a reserved SRAM region which is used as temporary > - storage memory between DMA and MDMA engines. > + SRAM region which is used as temporary storage memory between DMA and > + MDMA engines. > > port: > $ref: /schemas/graph.yaml#/$defs/port-base > diff --git a/Documentation/devicetree/bindings/net/mediatek,net.yaml b/Documentation/devicetree/bindings/net/mediatek,net.yaml > index cc346946291a..6bbd83c6aaf7 100644 > --- a/Documentation/devicetree/bindings/net/mediatek,net.yaml > +++ b/Documentation/devicetree/bindings/net/mediatek,net.yaml > @@ -67,8 +67,7 @@ properties: > - const: ppe > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > - description: phandle to mmio SRAM > + maxItems: 1 > > mediatek,ethsys: > $ref: /schemas/types.yaml#/definitions/phandle > diff --git a/Documentation/devicetree/bindings/net/ti,icssg-prueth.yaml b/Documentation/devicetree/bindings/net/ti,icssg-prueth.yaml > index c296e5711848..883033b19b8f 100644 > --- a/Documentation/devicetree/bindings/net/ti,icssg-prueth.yaml > +++ b/Documentation/devicetree/bindings/net/ti,icssg-prueth.yaml > @@ -21,7 +21,7 @@ properties: > - ti,am654-sr1-icssg-prueth # for AM65x SoC family, SR1.0 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > + maxItems: 1 > description: > phandle to MSMC SRAM node > > diff --git a/Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml b/Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml > index a98ad45ca66f..9370c43bc66a 100644 > --- a/Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml > +++ b/Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml > @@ -24,7 +24,7 @@ properties: > - ti,am3359-prueth # for AM33x SoC family > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > + maxItems: 1 > description: > phandle to OCMC SRAM node > > diff --git a/Documentation/devicetree/bindings/remoteproc/amlogic,meson-mx-ao-arc.yaml b/Documentation/devicetree/bindings/remoteproc/amlogic,meson-mx-ao-arc.yaml > index 76e8ca44906a..3f710433e937 100644 > --- a/Documentation/devicetree/bindings/remoteproc/amlogic,meson-mx-ao-arc.yaml > +++ b/Documentation/devicetree/bindings/remoteproc/amlogic,meson-mx-ao-arc.yaml > @@ -48,12 +48,7 @@ properties: > minItems: 1 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > - description: > - phandles to a reserved SRAM region which is used as the memory of > - the ARC core. The region should be defined as child nodes of the > - AHB SRAM node as per the generic bindings in > - Documentation/devicetree/bindings/sram/sram.yaml > + maxItems: 1 > > amlogic,secbus2: > $ref: /schemas/types.yaml#/definitions/phandle > diff --git a/Documentation/devicetree/bindings/remoteproc/ti,k3-dsp-rproc.yaml b/Documentation/devicetree/bindings/remoteproc/ti,k3-dsp-rproc.yaml > index b51bb863d759..8b1ed384ef22 100644 > --- a/Documentation/devicetree/bindings/remoteproc/ti,k3-dsp-rproc.yaml > +++ b/Documentation/devicetree/bindings/remoteproc/ti,k3-dsp-rproc.yaml > @@ -75,16 +75,8 @@ properties: > # -------------------- > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle-array > minItems: 1 > maxItems: 4 > - items: > - maxItems: 1 > - description: | > - phandles to one or more reserved on-chip SRAM regions. The regions > - should be defined as child nodes of the respective SRAM node, and > - should be defined as per the generic bindings in, > - Documentation/devicetree/bindings/sram/sram.yaml > > allOf: > - if: > diff --git a/Documentation/devicetree/bindings/remoteproc/ti,k3-r5f-rproc.yaml b/Documentation/devicetree/bindings/remoteproc/ti,k3-r5f-rproc.yaml > index 775e9b3a1938..14e6b2f817b3 100644 > --- a/Documentation/devicetree/bindings/remoteproc/ti,k3-r5f-rproc.yaml > +++ b/Documentation/devicetree/bindings/remoteproc/ti,k3-r5f-rproc.yaml > @@ -224,16 +224,8 @@ patternProperties: > at 0x0) or 0 (BTCM at 0x0), default value is 1 if omitted. > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle-array > minItems: 1 > maxItems: 4 > - items: > - maxItems: 1 > - description: | > - phandles to one or more reserved on-chip SRAM regions. The regions > - should be defined as child nodes of the respective SRAM node, and > - should be defined as per the generic bindings in, > - Documentation/devicetree/bindings/sram/sram.yaml > > required: > - compatible > diff --git a/Documentation/devicetree/bindings/remoteproc/xlnx,zynqmp-r5fss.yaml b/Documentation/devicetree/bindings/remoteproc/xlnx,zynqmp-r5fss.yaml > index ee63c03949c9..c7d5e58330d6 100644 > --- a/Documentation/devicetree/bindings/remoteproc/xlnx,zynqmp-r5fss.yaml > +++ b/Documentation/devicetree/bindings/remoteproc/xlnx,zynqmp-r5fss.yaml > @@ -106,20 +106,13 @@ patternProperties: > - const: rx > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle-array > minItems: 1 > maxItems: 8 > - items: > - maxItems: 1 > - description: | > + description: > phandles to one or more reserved on-chip SRAM regions. Other than TCM, > the RPU can execute instructions and access data from the OCM memory, > the main DDR memory, and other system memories. > > - The regions should be defined as child nodes of the respective SRAM > - node, and should be defined as per the generic bindings in > - Documentation/devicetree/bindings/sram/sram.yaml > - > memory-region: > description: | > List of phandles to the reserved memory regions associated with the > diff --git a/Documentation/devicetree/bindings/spi/st,stm32-spi.yaml b/Documentation/devicetree/bindings/spi/st,stm32-spi.yaml > index 472e92974714..6d7d595e4ab3 100644 > --- a/Documentation/devicetree/bindings/spi/st,stm32-spi.yaml > +++ b/Documentation/devicetree/bindings/spi/st,stm32-spi.yaml > @@ -89,12 +89,10 @@ properties: > - const: rxm2m > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > - description: | > - Phandles to a reserved SRAM region which is used as temporary > - storage memory between DMA and MDMA engines. > - The region should be defined as child node of the AHB SRAM node > - as per the generic bindings in Documentation/devicetree/bindings/sram/sram.yaml > + maxItems: 1 > + description: > + SRAM region which is used as temporary storage memory between DMA and > + MDMA engines. > > power-domains: > maxItems: 1 > diff --git a/Documentation/devicetree/bindings/sram/sram-consumer.yaml b/Documentation/devicetree/bindings/sram/sram-consumer.yaml > new file mode 100644 > index 000000000000..f00087bd2879 > --- /dev/null > +++ b/Documentation/devicetree/bindings/sram/sram-consumer.yaml > @@ -0,0 +1,26 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/sram/sram-consumer.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: SRAM Consumers > + > +maintainers: > + - Rob Herring > + > +select: true > + > +properties: > + sram: > + description: > + Phandles to one or more reserved on-chip SRAM regions. The regions > + should be defined as child nodes of the respective SRAM node, and > + should be defined as per the generic bindings in, > + Documentation/devicetree/bindings/sram/sram.yaml > + $ref: /schemas/types.yaml#/definitions/phandle-array > + items: > + maxItems: 1 > + > +additionalProperties: true > +... > -- > 2.53.0 > -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 228 bytes Desc: not available URL: From devnull+linux-kernel-dev.aliel.fr at kernel.org Tue May 12 10:47:28 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Tue, 12 May 2026 19:47:28 +0200 Subject: [PATCH 1/2] arm64: dts: amlogic: t7: Fix pwm_ao_c pinmux definitions In-Reply-To: <20260512-add-kvim4-sysled-v1-0-7178719a43e7@aliel.fr> References: <20260512-add-kvim4-sysled-v1-0-7178719a43e7@aliel.fr> Message-ID: <20260512-add-kvim4-sysled-v1-1-7178719a43e7@aliel.fr> From: Ronald Claveau The pwm_ao_c pin node was incomplete: it was missing the group name suffix, conflating two distinct pin groups (pwm_ao_c_d and pwm_ao_c_e) into a single, ambiguous entry. Split the node into two separate pinmux entries: - pwm_ao_c_d_pins: uses group "pwm_ao_c_d" - pwm_ao_c_e_pins: uses group "pwm_ao_c_e" Both alternate pins are not yet referenced by any peripheral node, so this has no functional impact on existing boards. No backport needed. Fixes: ee6e05a49b93 ("arm64: dts: amlogic: t7: Add PWM pinctrl nodes") Signed-off-by: Ronald Claveau --- arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi index 7fe72c94ed623..62f6b9baad28b 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi @@ -400,9 +400,17 @@ mux { }; }; - pwm_ao_c_pins: pwm-ao-c { + pwm_ao_c_d_pins: pwm-ao-c-d { mux { - groups = "pwm_ao_c"; + groups = "pwm_ao_c_d"; + function = "pwm_ao_c"; + bias-disable; + }; + }; + + pwm_ao_c_e_pins: pwm-ao-c-e { + mux { + groups = "pwm_ao_c_e"; function = "pwm_ao_c"; bias-disable; }; -- 2.49.0 From devnull+linux-kernel-dev.aliel.fr at kernel.org Tue May 12 10:47:29 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Tue, 12 May 2026 19:47:29 +0200 Subject: [PATCH 2/2] arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED In-Reply-To: <20260512-add-kvim4-sysled-v1-0-7178719a43e7@aliel.fr> References: <20260512-add-kvim4-sysled-v1-0-7178719a43e7@aliel.fr> Message-ID: <20260512-add-kvim4-sysled-v1-2-7178719a43e7@aliel.fr> From: Ronald Claveau The VIM4 board exposes a status LED wired to the PWM_AO_C_D output. Enable the pwm_ao_cd controller with its pinmux, and declare a pwm-leds node with a heartbeat trigger. Also, move the xtal-clk node to restore alphabetical ordering. Signed-off-by: Ronald Claveau --- .../dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 30 +++++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index 69d6118ba57e7..c41525a34b721 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -45,13 +45,6 @@ secmon_reserved_bl32: secmon at 5300000 { }; }; - xtal: xtal-clk { - compatible = "fixed-clock"; - clock-frequency = <24000000>; - clock-output-names = "xtal"; - #clock-cells = <0>; - }; - dc_in: regulator-dc-in { compatible = "regulator-fixed"; regulator-name = "DC_IN"; @@ -60,6 +53,16 @@ dc_in: regulator-dc-in { regulator-always-on; }; + pwm-leds { + compatible = "pwm-leds"; + + status { + linux,default-trigger="heartbeat"; + max-brightness = <255>; + pwms = <&pwm_ao_cd 0 30040 0>; + }; + }; + sd_3v3: regulator-sdcard-3v3 { compatible = "regulator-fixed"; regulator-name = "SD_3V3"; @@ -155,6 +158,13 @@ wifi32k: wifi32k { clock-frequency = <32768>; pwms = <&pwm_ab 0 30518 0>; }; + + xtal: xtal-clk { + compatible = "fixed-clock"; + clock-frequency = <24000000>; + clock-output-names = "xtal"; + #clock-cells = <0>; + }; }; &pwm_ab { @@ -163,6 +173,12 @@ &pwm_ab { pinctrl-names = "default"; }; +&pwm_ao_cd { + status = "okay"; + pinctrl-0 = <&pwm_ao_c_d_pins>; + pinctrl-names = "default"; +}; + /* SDIO */ &sd_emmc_a { status = "okay"; -- 2.49.0 From devnull+linux-kernel-dev.aliel.fr at kernel.org Tue May 12 10:47:27 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Tue, 12 May 2026 19:47:27 +0200 Subject: [PATCH 0/2] Khadas VIM4 PWM status LED support Message-ID: <20260512-add-kvim4-sysled-v1-0-7178719a43e7@aliel.fr> This series adds support for the PWM-driven status LED on the Khadas VIM4 board (Amlogic T7). The VIM4 exposes a heartbeat LED wired to the PWM_AO_C output, routed through pin group pwm_ao_c_d. Before wiring it up in the board DTS, the SoC pinmux definitions had to be corrected: the original pwm_ao_c node was conflating two distinct pin groups (pwm_ao_c_d and pwm_ao_c_e) into a single ambiguous entry. Patch 1 fixes the pwm_ao_c pinmux entries in the T7 DTSI by splitting them into two properly named nodes. Neither alternate is in use yet, so there is no functional impact on existing boards. Patch 2 enables the pwm_ao_cd controller on the VIM4 and adds a pwm-leds node with a heartbeat trigger. The xtal-clk node is also moved to restore alphabetical ordering among root node children. Signed-off-by: Ronald Claveau --- Ronald Claveau (2): arm64: dts: amlogic: t7: Fix pwm_ao_c pinmux definitions arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED .../dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 30 +++++++++++++++++----- arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 12 +++++++-- 2 files changed, 33 insertions(+), 9 deletions(-) --- base-commit: 31f32e8cdf59291e467250dfc57d1a8c718f63d2 change-id: 20260512-add-kvim4-sysled-8cc159524561 Best regards, -- Ronald Claveau From jian.hu at amlogic.com Tue May 12 20:53:15 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Wed, 13 May 2026 11:53:15 +0800 Subject: [PATCH 06/10] clk: amlogic: PLL reset signal supports active-low configuration In-Reply-To: References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-6-41cb4071b7c9@amlogic.com> Message-ID: Hi Brain, Thanks for your review. On 5/11/2026 11:21 PM, Brian Masney wrote: > [ EXTERNAL EMAIL ] > > On Mon, May 11, 2026 at 08:47:28PM +0800, Jian Hu via B4 Relay wrote: >> From: Jian Hu >> >> In the A9 design, the PLL reset signal is configured as active-low. >> >> Add the flag 'CLK_MESON_PLL_RST_N' to indicate that the PLL reset signal >> is active-low. > This flag isn't in the patch. I assume that you mean > CLK_MESON_PLL_RST_ACTIVE_LOW? > > Brian Yes,? You are right, the flag should indeed be CLK_MESON_PLL_RST_ACTIVE_LOW. I will fix the description in the next version. Thank you for pointing it out. [......] Best regards, Jian From jian.hu at amlogic.com Wed May 13 00:25:38 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Wed, 13 May 2026 15:25:38 +0800 Subject: [PATCH 08/10] clk: amlogic: Add A9 PLL clock controller driver In-Reply-To: References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-8-41cb4071b7c9@amlogic.com> Message-ID: <5aa307a7-bd01-424e-87a3-1c7b4f787a1b@amlogic.com> On 5/11/2026 11:36 PM, Brian Masney wrote: > [ EXTERNAL EMAIL ] > > Hi Jian, > > On Mon, May 11, 2026 at 08:47:30PM +0800, Jian Hu via B4 Relay wrote: >> From: Jian Hu >> >> Add the PLL clock controller driver for the Amlogic A9 SoC family. >> >> Signed-off-by: Jian Hu >> --- >> drivers/clk/meson/Kconfig | 13 + >> drivers/clk/meson/Makefile | 1 + >> drivers/clk/meson/a9-pll.c | 831 +++++++++++++++++++++++++++++++++++++++++++++ >> 3 files changed, 845 insertions(+) >> >> diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig >> index cf8cf3f9e4ee..3549e67d6988 100644 >> --- a/drivers/clk/meson/Kconfig >> +++ b/drivers/clk/meson/Kconfig >> @@ -132,6 +132,19 @@ config COMMON_CLK_A1_PERIPHERALS >> device, A1 SoC Family. Say Y if you want A1 Peripherals clock >> controller to work. >> >> +config COMMON_CLK_A9_PLL >> + tristate "Amlogic A9 SoC PLL controller support" >> + depends on ARM64 > depends on ARM64 || COMPILE_TEST Ok, I will add COMPILE_TEST in the next version. >> + default ARCH_MESON >> + select COMMON_CLK_MESON_REGMAP >> + select COMMON_CLK_MESON_CLKC_UTILS >> + select COMMON_CLK_MESON_PLL >> + imply COMMON_CLK_SCMI >> + help >> + Support for the PLL clock controller on Amlogic A311Y3 based >> + device, AKA A9. PLLs are required by most peripheral to operate. >> + Say Y if you want A9 PLL clock controller to work. >> + >> config COMMON_CLK_C3_PLL >> tristate "Amlogic C3 PLL clock controller" >> depends on ARM64 >> diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile >> index c6719694a242..77636033061f 100644 >> --- a/drivers/clk/meson/Makefile >> +++ b/drivers/clk/meson/Makefile >> @@ -19,6 +19,7 @@ obj-$(CONFIG_COMMON_CLK_AXG) += axg.o axg-aoclk.o >> obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o >> obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o >> obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o >> +obj-$(CONFIG_COMMON_CLK_A9_PLL) += a9-pll.o >> obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o >> obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o >> obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o >> diff --git a/drivers/clk/meson/a9-pll.c b/drivers/clk/meson/a9-pll.c >> new file mode 100644 >> index 000000000000..84b591c3afff >> --- /dev/null >> +++ b/drivers/clk/meson/a9-pll.c >> @@ -0,0 +1,831 @@ >> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) >> +/* >> + * Copyright (C) 2026 Amlogic, Inc. All rights reserved >> + */ >> + >> +#include >> +#include >> +#include >> +#include "clk-regmap.h" >> +#include "clk-pll.h" >> +#include "meson-clkc-utils.h" > Sort the headers Ok , I will place dt-bindings header at the top. After updated: #include #include #include #include "clk-regmap.h" #include "clk-pll.h" #include "meson-clkc-utils.h" If I have misunderstood, please correct me. >> + >> +#define GP0PLL_CTRL0 0x00 >> +#define GP0PLL_CTRL1 0x04 >> +#define GP0PLL_CTRL2 0x08 >> +#define GP0PLL_CTRL3 0x0c >> +#define GP0PLL_CTRL4 0x10 >> + >> +/* HIFI0 and HIFI1 share the same IP and register offset layout. */ >> +#define HIFIPLL_CTRL0 0x00 >> +#define HIFIPLL_CTRL1 0x04 >> +#define HIFIPLL_CTRL2 0x08 >> +#define HIFIPLL_CTRL3 0x0c >> +#define HIFIPLL_CTRL4 0x10 >> + >> +/* MCLK0 and MCLK1 share the same IP and register offset layout. */ >> +#define MCLKPLL_CTRL0 0x00 >> +#define MCLKPLL_CTRL1 0x04 >> +#define MCLKPLL_CTRL2 0x08 >> +#define MCLKPLL_CTRL3 0x0c >> +#define MCLKPLL_CTRL4 0x10 >> + >> +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata) \ >> + MESON_COMP_SEL(a9_, _name, _reg, _shift, _mask, _pdata, NULL, 0, 0) >> + >> +#define A9_COMP_DIV(_name, _reg, _shift, _width) \ >> + MESON_COMP_DIV(a9_, _name, _reg, _shift, _width, 0, CLK_SET_RATE_PARENT) >> + >> +#define A9_COMP_GATE(_name, _reg, _bit) \ >> + MESON_COMP_GATE(a9_, _name, _reg, _bit, CLK_SET_RATE_PARENT) >> + >> +/* >> + * Compared with previous SoC PLLs, the A9 PLL input path has an inherent >> + * 2-divider. The N pre-divider follows the same calculation rule as OD, >> + * where the pre-divider ratio equals 2^N. >> + * >> + * A9 PLL is composed as follows: >> + * >> + * PLL >> + * +---------------------------------+ >> + * | | >> + * | +--+ | >> + * in/2 >>---[ /2^N ]-->| | +-----+ | >> + * | | |------| DCO |----->> out >> + * | +--------->| | +--v--+ | >> + * | | +--+ | | >> + * | | | | >> + * | +--[ *(M + (F/Fmax) ]<--+ | >> + * | | >> + * +---------------------------------+ >> + * >> + * out = in / 2 * (m + frac / frac_max) / 2^n >> + */ >> + >> +static struct clk_fixed_factor a9_gp0_in_div2_div = { >> + .mult = 1, >> + .div = 2, >> + .hw.init = &(struct clk_init_data){ >> + .name = "gp0_in_div2_div", >> + .ops = &clk_fixed_factor_ops, >> + .parent_data = &(const struct clk_parent_data) { >> + .fw_name = "in0", >> + }, >> + .num_parents = 1, >> + }, > You can use CLK_HW_INIT_FW_NAME() for the hw.init here and other places > below. Ok, I will use CLK_HW_INIT_FW_NAME instead in the next version. >> +}; >> + >> +static struct clk_regmap a9_gp0_in_div2 = { >> + .data = &(struct clk_regmap_gate_data) { >> + .offset = GP0PLL_CTRL0, >> + .bit_idx = 27, >> + }, >> + .hw.init = &(struct clk_init_data) { >> + .name = "gp0_in_div2", >> + .ops = &clk_regmap_gate_ops, >> + .parent_hws = (const struct clk_hw *[]) { >> + &a9_gp0_in_div2_div.hw >> + }, >> + .num_parents = 1, >> + }, >> +}; >> + >> +/* The output frequency range of the A9 PLL_DCO is 1.4 GHz to 2.8 GHz. */ >> +static const struct pll_mult_range a9_pll_mult_range = { >> + .min = 117, >> + .max = 233, >> +}; >> + >> +static const struct reg_sequence a9_gp0_pll_init_regs[] = { >> + { .reg = GP0PLL_CTRL0, .def = 0x00010000 }, >> + { .reg = GP0PLL_CTRL1, .def = 0x11480000 }, >> + { .reg = GP0PLL_CTRL2, .def = 0x1219b010 }, >> + { .reg = GP0PLL_CTRL3, .def = 0x00008010 } >> +}; >> + >> +static struct clk_regmap a9_gp0_pll_dco = { >> + .data = &(struct meson_clk_pll_data) { >> + .en = { >> + .reg_off = GP0PLL_CTRL0, >> + .shift = 28, >> + .width = 1, >> + }, >> + .m = { >> + .reg_off = GP0PLL_CTRL0, >> + .shift = 0, >> + .width = 9, >> + }, >> + .n = { >> + .reg_off = GP0PLL_CTRL0, >> + .shift = 12, >> + .width = 3, >> + }, >> + .frac = { >> + .reg_off = GP0PLL_CTRL1, >> + .shift = 0, >> + .width = 17, >> + }, >> + .l = { >> + .reg_off = GP0PLL_CTRL0, >> + .shift = 31, >> + .width = 1, >> + }, >> + .rst = { >> + .reg_off = GP0PLL_CTRL0, >> + .shift = 29, >> + .width = 1, >> + }, >> + .l_detect = { >> + .reg_off = GP0PLL_CTRL0, >> + .shift = 30, >> + .width = 1, >> + }, >> + .range = &a9_pll_mult_range, >> + .init_regs = a9_gp0_pll_init_regs, >> + .init_count = ARRAY_SIZE(a9_gp0_pll_init_regs), >> + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | >> + CLK_MESON_PLL_N_POWER_OF_TWO | >> + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, >> + }, >> + .hw.init = &(struct clk_init_data) { >> + .name = "gp0_pll_dco", >> + .ops = &meson_clk_pll_ops, >> + .parent_hws = (const struct clk_hw *[]) { >> + &a9_gp0_in_div2.hw >> + }, >> + .num_parents = 1, >> + }, > You can use CLK_HW_INIT_HWS() here and other places below. > > Brian > Ok, I will use CLK_HW_INIT_HW instead for single parent case. Best regards, Jian [......] >> 2.47.1 >> >> From neil.armstrong at linaro.org Wed May 13 00:40:02 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 13 May 2026 09:40:02 +0200 Subject: [ANN] sashiko review allowed to reply Message-ID: <1ef9839c-caa2-4a36-bec5-84a2a1dddbe6@linaro.org> Hi, As an experiment, I allowed the https://sashiko.dev/ review tool to reply to the linux-amlogic messages with some review. Note that the review performed by LLMs must be used with extreme care since they may hallucinate or completely miss the point of the changeset. But I think it can give us some inputs when submitting and reviewing patches. If somehow it becomes too noisy I'll disable it. Thanks, Neil -- Neil Armstrong Senior Kernel Engineer - Linaro https://linaro.org/ From neil.armstrong at linaro.org Wed May 13 01:04:16 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 13 May 2026 10:04:16 +0200 Subject: [PATCH 1/2] arm64: dts: amlogic: t7: Fix pwm_ao_c pinmux definitions In-Reply-To: <20260512-add-kvim4-sysled-v1-1-7178719a43e7@aliel.fr> References: <20260512-add-kvim4-sysled-v1-0-7178719a43e7@aliel.fr> <20260512-add-kvim4-sysled-v1-1-7178719a43e7@aliel.fr> Message-ID: On 5/12/26 19:47, Ronald Claveau via B4 Relay wrote: > From: Ronald Claveau > > The pwm_ao_c pin node was incomplete: it was missing the group name > suffix, conflating two distinct pin groups (pwm_ao_c_d and pwm_ao_c_e) > into a single, ambiguous entry. > > Split the node into two separate pinmux entries: > - pwm_ao_c_d_pins: uses group "pwm_ao_c_d" > - pwm_ao_c_e_pins: uses group "pwm_ao_c_e" > > Both alternate pins are not yet referenced by any peripheral node, > so this has no functional impact on existing boards. No backport needed. > > Fixes: ee6e05a49b93 ("arm64: dts: amlogic: t7: Add PWM pinctrl nodes") > Signed-off-by: Ronald Claveau > --- > arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 12 ++++++++++-- > 1 file changed, 10 insertions(+), 2 deletions(-) > > diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi > index 7fe72c94ed623..62f6b9baad28b 100644 > --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi > +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi > @@ -400,9 +400,17 @@ mux { > }; > }; > > - pwm_ao_c_pins: pwm-ao-c { > + pwm_ao_c_d_pins: pwm-ao-c-d { > mux { > - groups = "pwm_ao_c"; > + groups = "pwm_ao_c_d"; > + function = "pwm_ao_c"; > + bias-disable; > + }; > + }; > + > + pwm_ao_c_e_pins: pwm-ao-c-e { > + mux { > + groups = "pwm_ao_c_e"; > function = "pwm_ao_c"; > bias-disable; > }; > Reviewed-by: Neil Armstrong I'll squash it on the old commit. Thanks, Neil From neil.armstrong at linaro.org Wed May 13 01:05:26 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 13 May 2026 10:05:26 +0200 Subject: [PATCH 2/2] arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED In-Reply-To: <20260512-add-kvim4-sysled-v1-2-7178719a43e7@aliel.fr> References: <20260512-add-kvim4-sysled-v1-0-7178719a43e7@aliel.fr> <20260512-add-kvim4-sysled-v1-2-7178719a43e7@aliel.fr> Message-ID: <7ad282ae-fffa-4d0a-9cec-65d8f6b40544@linaro.org> On 5/12/26 19:47, Ronald Claveau via B4 Relay wrote: > From: Ronald Claveau > > The VIM4 board exposes a status LED wired to the PWM_AO_C_D output. > Enable the pwm_ao_cd controller with its pinmux, and declare a > pwm-leds node with a heartbeat trigger. > > Also, move the xtal-clk node to restore alphabetical ordering. Please send a separate patch for that > > Signed-off-by: Ronald Claveau > --- > .../dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 30 +++++++++++++++++----- > 1 file changed, 23 insertions(+), 7 deletions(-) > > diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > index 69d6118ba57e7..c41525a34b721 100644 > --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > @@ -45,13 +45,6 @@ secmon_reserved_bl32: secmon at 5300000 { > }; > }; > > - xtal: xtal-clk { > - compatible = "fixed-clock"; > - clock-frequency = <24000000>; > - clock-output-names = "xtal"; > - #clock-cells = <0>; > - }; > - > dc_in: regulator-dc-in { > compatible = "regulator-fixed"; > regulator-name = "DC_IN"; > @@ -60,6 +53,16 @@ dc_in: regulator-dc-in { > regulator-always-on; > }; > > + pwm-leds { > + compatible = "pwm-leds"; > + > + status { > + linux,default-trigger="heartbeat"; > + max-brightness = <255>; > + pwms = <&pwm_ao_cd 0 30040 0>; > + }; > + }; > + > sd_3v3: regulator-sdcard-3v3 { > compatible = "regulator-fixed"; > regulator-name = "SD_3V3"; > @@ -155,6 +158,13 @@ wifi32k: wifi32k { > clock-frequency = <32768>; > pwms = <&pwm_ab 0 30518 0>; > }; > + > + xtal: xtal-clk { > + compatible = "fixed-clock"; > + clock-frequency = <24000000>; > + clock-output-names = "xtal"; > + #clock-cells = <0>; > + }; > }; > > &pwm_ab { > @@ -163,6 +173,12 @@ &pwm_ab { > pinctrl-names = "default"; > }; > > +&pwm_ao_cd { > + status = "okay"; > + pinctrl-0 = <&pwm_ao_c_d_pins>; > + pinctrl-names = "default"; > +}; > + > /* SDIO */ > &sd_emmc_a { > status = "okay"; > From vkoul at kernel.org Wed May 13 01:22:29 2026 From: vkoul at kernel.org (Vinod Koul) Date: Wed, 13 May 2026 13:52:29 +0530 Subject: [PATCH] dt-bindings: Consolidate "sram" property definition In-Reply-To: <20260511165942.2774868-1-robh@kernel.org> References: <20260511165942.2774868-1-robh@kernel.org> Message-ID: On 11-05-26, 11:59, Rob Herring (Arm) wrote: > The "sram" property has become a de facto standard property, so create a > common schema for it and drop all the duplicated definitions. Acked-by: Vinod Koul -- ~Vinod From neil.armstrong at linaro.org Wed May 13 01:29:50 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 13 May 2026 10:29:50 +0200 Subject: [PATCH 0/2] Khadas VIM4 PWM status LED support In-Reply-To: <20260512-add-kvim4-sysled-v1-0-7178719a43e7@aliel.fr> References: <20260512-add-kvim4-sysled-v1-0-7178719a43e7@aliel.fr> Message-ID: <890de4b9-5481-44ab-adfd-0c58d9ae8239@linaro.org> On 5/12/26 19:47, Ronald Claveau via B4 Relay wrote: > This series adds support for the PWM-driven status LED on the Khadas > VIM4 board (Amlogic T7). > > The VIM4 exposes a heartbeat LED wired to the PWM_AO_C output, routed > through pin group pwm_ao_c_d. Before wiring it up in the board DTS, > the SoC pinmux definitions had to be corrected: the original > pwm_ao_c node was conflating two distinct pin groups (pwm_ao_c_d and > pwm_ao_c_e) into a single ambiguous entry. > > Patch 1 fixes the pwm_ao_c pinmux entries in the T7 DTSI by splitting > them into two properly named nodes. Neither alternate is in use yet, > so there is no functional impact on existing boards. > > Patch 2 enables the pwm_ao_cd controller on the VIM4 and adds a > pwm-leds node with a heartbeat trigger. The xtal-clk node is also > moved to restore alphabetical ordering among root node children. > > Signed-off-by: Ronald Claveau > --- > Ronald Claveau (2): > arm64: dts: amlogic: t7: Fix pwm_ao_c pinmux definitions > arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED > > .../dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 30 +++++++++++++++++----- > arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 12 +++++++-- > 2 files changed, 33 insertions(+), 9 deletions(-) > --- > base-commit: 31f32e8cdf59291e467250dfc57d1a8c718f63d2 > change-id: 20260512-add-kvim4-sysled-8cc159524561 > > Best regards, Squashed patch 1 on ee6e05a49b93 ("arm64: dts: amlogic: t7: Add PWM pinctrl nodes") Neil From jian.hu at amlogic.com Wed May 13 01:50:57 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Wed, 13 May 2026 16:50:57 +0800 Subject: [PATCH 09/10] clk: amlogic: Add A9 peripherals clock controller driver In-Reply-To: References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-9-41cb4071b7c9@amlogic.com> Message-ID: <00d8ed18-f753-405a-9ba4-e044129bf33a@amlogic.com> On 5/11/2026 11:42 PM, Brian Masney wrote: > [ EXTERNAL EMAIL ] > > Hi Jian, > > On Mon, May 11, 2026 at 08:47:31PM +0800, Jian Hu via B4 Relay wrote: >> From: Jian Hu >> >> Add the peripherals clock controller driver for the Amlogic A9 SoC family. >> >> Signed-off-by: Jian Hu >> --- >> drivers/clk/meson/Kconfig | 15 + >> drivers/clk/meson/Makefile | 1 + >> drivers/clk/meson/a9-peripherals.c | 2317 ++++++++++++++++++++++++++++++++++++ >> 3 files changed, 2333 insertions(+) >> >> diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig >> index 3549e67d6988..48a15a5e1323 100644 >> --- a/drivers/clk/meson/Kconfig >> +++ b/drivers/clk/meson/Kconfig >> @@ -145,6 +145,21 @@ config COMMON_CLK_A9_PLL >> device, AKA A9. PLLs are required by most peripheral to operate. >> Say Y if you want A9 PLL clock controller to work. >> >> +config COMMON_CLK_A9_PERIPHERALS >> + tristate "Amlogic A9 SoC peripherals clock controller support" >> + depends on ARM64 > depends on ARM64 || COMPILE_TEST Ok, I will add COMPILE_TEST in the next version. >> + default ARCH_MESON >> + select COMMON_CLK_MESON_REGMAP >> + select COMMON_CLK_MESON_CLKC_UTILS >> + select COMMON_CLK_MESON_DUALDIV >> + select COMMON_CLK_MESON_VID_PLL_DIV >> + imply COMMON_CLK_SCMI >> + imply COMMON_CLK_A9_PLL >> + help >> + Support for the peripherals clock controller on Amlogic A311Y3 based >> + device, AKA A9. Peripherals are required by most peripheral to operate. >> + Say Y if you want A9 peripherals clock controller to work. >> + >> config COMMON_CLK_C3_PLL >> tristate "Amlogic C3 PLL clock controller" >> depends on ARM64 >> diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile >> index 77636033061f..2b5b67b14efc 100644 >> --- a/drivers/clk/meson/Makefile >> +++ b/drivers/clk/meson/Makefile >> @@ -20,6 +20,7 @@ obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o >> obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o >> obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o >> obj-$(CONFIG_COMMON_CLK_A9_PLL) += a9-pll.o >> +obj-$(CONFIG_COMMON_CLK_A9_PERIPHERALS) += a9-peripherals.o >> obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o >> obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o >> obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o >> diff --git a/drivers/clk/meson/a9-peripherals.c b/drivers/clk/meson/a9-peripherals.c >> new file mode 100644 >> index 000000000000..338a91c473ea >> --- /dev/null >> +++ b/drivers/clk/meson/a9-peripherals.c >> @@ -0,0 +1,2317 @@ >> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) >> +/* >> + * Copyright (C) 2026 Amlogic, Inc. All rights reserved >> + */ >> + >> +#include >> +#include >> +#include >> +#include "clk-regmap.h" >> +#include "clk-dualdiv.h" >> +#include "vid-pll-div.h" >> +#include "meson-clkc-utils.h" > Sort the headers. Ok, I will place them in order. After updated: #include #include #include #include "clk-regmap.h" #include "clk-dualdiv.h" #include "meson-clkc-utils.h" #include "vid-pll-div.h" [......] >> +static const struct clk_parent_data a9_nna_parents[] = { >> + { .fw_name = "xtal", }, >> + { .fw_name = "fdiv2p5", }, >> + { .fw_name = "fdiv4", }, >> + { .fw_name = "fdiv3", }, >> + { .fw_name = "fdiv5", }, >> + { .fw_name = "fdiv2", }, >> + { .fw_name = "gp2", }, >> + { .fw_name = "hifi", } > hifi isn't in the dt bindings. Should this be hifi0 and/or hifi1? It should be hifi0?I will fix it in the next version. Thank you for pointing it out. [......] >> + >> +static struct clk_regmap a9_sc = { >> + .data = &(struct clk_regmap_div_data) { >> + .offset = SC_CLK_CTRL, >> + .shift = 16, >> + .width = 4, >> + }, >> + .hw.init = &(struct clk_init_data) { >> + .name = "sc", >> + .ops = &clk_regmap_divider_ops, >> + .parent_hws = (const struct clk_hw *[]) { >> + &a9_sc_pre.hw >> + }, >> + .num_parents = 1, >> + .flags = CLK_SET_RATE_PARENT, >> + }, > You can use CLK_HW_INIT_HWS() here. > > Brian Ok, I will use CLK_HW_INIT_HWS instead, and the same below. Best regards, Jian From jian.hu at amlogic.com Wed May 13 02:19:21 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Wed, 13 May 2026 17:19:21 +0800 Subject: [PATCH 10/10] clk: amlogic: Add A9 AO clock controller driver In-Reply-To: References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-10-41cb4071b7c9@amlogic.com> Message-ID: On 5/11/2026 11:45 PM, Brian Masney wrote: > [ EXTERNAL EMAIL ] > > Hi Jian, > > On Mon, May 11, 2026 at 08:47:32PM +0800, Jian Hu via B4 Relay wrote: >> From: Jian Hu >> >> Add the Always-on clock controller driver for the Amlogic A9 SoC family. >> >> Signed-off-by: Jian Hu > I'll only flag new things that I spot here that weren't mentioned in > the other patches I reviewed in this series. Got you, I will sort the header, also use CLK_HW_INIT_FW_NAME/CLK_HW_INIT_HWS for this driver. [......] >> +static A9_COMP_SEL(ao_pwm_a, AO_PWM_CLK_A_CTRL, 9, 0x7, a9_ao_pwm_parents); >> +static A9_COMP_DIV(ao_pwm_a, AO_PWM_CLK_A_CTRL, 0, 8); >> +static A9_COMP_GATE(ao_pwm_a, AO_PWM_CLK_A_CTRL, 8); >> + >> +static A9_COMP_SEL(ao_pwm_b, AO_PWM_CLK_B_CTRL, 9, 0x7, a9_ao_pwm_parents); >> +static A9_COMP_DIV(ao_pwm_b, AO_PWM_CLK_B_CTRL, 0, 8); >> +static A9_COMP_GATE(ao_pwm_b, AO_PWM_CLK_A_CTRL, 8); > Should this be AO_PWM_CLK_B_CTRL ? Yes, it should be AO_PWM_CLK_B_CTRL. Thank you for pointing it out. >> + >> +static A9_COMP_SEL(ao_pwm_c, AO_PWM_CLK_C_CTRL, 9, 0x7, a9_ao_pwm_parents); >> +static A9_COMP_DIV(ao_pwm_c, AO_PWM_CLK_C_CTRL, 0, 8); >> +static A9_COMP_GATE(ao_pwm_c, AO_PWM_CLK_C_CTRL, 8); >> + >> +static A9_COMP_SEL(ao_pwm_d, AO_PWM_CLK_D_CTRL, 9, 0x7, a9_ao_pwm_parents); >> +static A9_COMP_DIV(ao_pwm_d, AO_PWM_CLK_D_CTRL, 0, 8); >> +static A9_COMP_GATE(ao_pwm_d, AO_PWM_CLK_D_CTRL, 8); >> + >> +static A9_COMP_SEL(ao_pwm_e, AO_PWM_CLK_E_CTRL, 9, 0x7, a9_ao_pwm_parents); >> +static A9_COMP_DIV(ao_pwm_e, AO_PWM_CLK_E_CTRL, 0, 8); >> +static A9_COMP_GATE(ao_pwm_e, AO_PWM_CLK_E_CTRL, 8); >> + >> +static A9_COMP_SEL(ao_pwm_f, AO_PWM_CLK_F_CTRL, 9, 0x7, a9_ao_pwm_parents); >> +static A9_COMP_DIV(ao_pwm_f, AO_PWM_CLK_F_CTRL, 0, 8); >> +static A9_COMP_GATE(ao_pwm_f, AO_PWM_CLK_F_CTRL, 8); >> + >> +static A9_COMP_SEL(ao_pwm_g, AO_PWM_CLK_G_CTRL, 9, 0x7, a9_ao_pwm_parents); >> +static A9_COMP_DIV(ao_pwm_g, AO_PWM_CLK_G_CTRL, 0, 8); >> +static A9_COMP_GATE(ao_pwm_g, AO_PWM_CLK_G_CTRL, 8); >> + >> +static struct clk_regmap a9_ao_rtc_dualdiv_in = { >> + .data = &(struct clk_regmap_gate_data){ >> + .offset = AO_RTC_BY_OSCIN_CTRL0, >> + .bit_idx = 31, >> + }, >> + .hw.init = &(struct clk_init_data) { >> + .name = "ao_rtc_duandiv_in", > s/duandiv/dualdiv/ ? > > Brian Ok, I will fix the duandiv name. Thank you for pointing it out. From linux-kernel-dev at aliel.fr Wed May 13 03:26:05 2026 From: linux-kernel-dev at aliel.fr (Ronald Claveau) Date: Wed, 13 May 2026 12:26:05 +0200 Subject: [PATCH 2/2] arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED In-Reply-To: <7ad282ae-fffa-4d0a-9cec-65d8f6b40544@linaro.org> References: <20260512-add-kvim4-sysled-v1-0-7178719a43e7@aliel.fr> <20260512-add-kvim4-sysled-v1-2-7178719a43e7@aliel.fr> <7ad282ae-fffa-4d0a-9cec-65d8f6b40544@linaro.org> Message-ID: <2615455d-4b4b-43cb-a0eb-ee8ea3cd68fb@aliel.fr> On 5/13/26 10:05 AM, Neil Armstrong wrote: > On 5/12/26 19:47, Ronald Claveau via B4 Relay wrote: >> From: Ronald Claveau >> >> The VIM4 board exposes a status LED wired to the PWM_AO_C_D output. >> Enable the pwm_ao_cd controller with its pinmux, and declare a >> pwm-leds node with a heartbeat trigger. >> >> Also, move the xtal-clk node to restore alphabetical ordering. > > Please send a separate patch for that > Thanks for your review, I will add a new patch. >> >> Signed-off-by: Ronald Claveau >> --- >> ? .../dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts? | 30 ++++++++++++ >> +++++----- >> ? 1 file changed, 23 insertions(+), 7 deletions(-) >> >> diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas- >> vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts >> index 69d6118ba57e7..c41525a34b721 100644 >> --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts >> +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts >> @@ -45,13 +45,6 @@ secmon_reserved_bl32: secmon at 5300000 { >> ????????? }; >> ????? }; >> ? -??? xtal: xtal-clk { >> -??????? compatible = "fixed-clock"; >> -??????? clock-frequency = <24000000>; >> -??????? clock-output-names = "xtal"; >> -??????? #clock-cells = <0>; >> -??? }; >> - >> ????? dc_in: regulator-dc-in { >> ????????? compatible = "regulator-fixed"; >> ????????? regulator-name = "DC_IN"; >> @@ -60,6 +53,16 @@ dc_in: regulator-dc-in { >> ????????? regulator-always-on; >> ????? }; >> ? +??? pwm-leds { >> +??????? compatible = "pwm-leds"; >> + >> +??????? status { >> +??????????? linux,default-trigger="heartbeat"; >> +??????????? max-brightness = <255>; >> +??????????? pwms = <&pwm_ao_cd 0 30040 0>; >> +??????? }; >> +??? }; >> + >> ????? sd_3v3: regulator-sdcard-3v3 { >> ????????? compatible = "regulator-fixed"; >> ????????? regulator-name = "SD_3V3"; >> @@ -155,6 +158,13 @@ wifi32k: wifi32k { >> ????????? clock-frequency = <32768>; >> ????????????? pwms = <&pwm_ab 0 30518 0>; >> ????? }; >> + >> +??? xtal: xtal-clk { >> +??????? compatible = "fixed-clock"; >> +??????? clock-frequency = <24000000>; >> +??????? clock-output-names = "xtal"; >> +??????? #clock-cells = <0>; >> +??? }; >> ? }; >> ? ? &pwm_ab { >> @@ -163,6 +173,12 @@ &pwm_ab { >> ????? pinctrl-names = "default"; >> ? }; >> ? +&pwm_ao_cd { >> +??? status = "okay"; >> +??? pinctrl-0 = <&pwm_ao_c_d_pins>; >> +??? pinctrl-names = "default"; >> +}; >> + >> ? /* SDIO */ >> ? &sd_emmc_a { >> ????? status = "okay"; >> > -- Best regards, Ronald From devnull+linux-kernel-dev.aliel.fr at kernel.org Wed May 13 03:43:52 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Wed, 13 May 2026 12:43:52 +0200 Subject: [PATCH v2 0/3] Khadas VIM4 PWM status LED support Message-ID: <20260513-add-kvim4-sysled-v2-0-3ec9779e8875@aliel.fr> This series adds support for the PWM-driven status LED on the Khadas VIM4 board (Amlogic T7). The VIM4 exposes a heartbeat LED wired to the PWM_AO_C output, routed through pin group pwm_ao_c_d. Before wiring it up in the board DTS, the SoC pinmux definitions had to be corrected: the original pwm_ao_c node was conflating two distinct pin groups (pwm_ao_c_d and pwm_ao_c_e) into a single ambiguous entry. Patch 1 fixes the pwm_ao_c pinmux entries in the T7 DTSI by splitting them into two properly named nodes. Neither alternate is in use yet, so there is no functional impact on existing boards. Patch 2 moves the xtal-clk node to restore alphabetical ordering among root node children. Patch 3 enables the pwm_ao_cd controller on the VIM4 and adds a pwm-leds node with a heartbeat trigger. Signed-off-by: Ronald Claveau --- Changes in v2: - PATCH 2-3: Create a new patch specific to the reordering action. According to Neil's review. - Link to v1: https://lore.kernel.org/r/20260512-add-kvim4-sysled-v1-0-7178719a43e7 at aliel.fr --- Ronald Claveau (3): arm64: dts: amlogic: t7: Fix pwm_ao_c pinmux definitions arm64: dts: amlogic: t7: khadas-vim4: reorder root node arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED .../dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 30 +++++++++++++++++----- arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 12 +++++++-- 2 files changed, 33 insertions(+), 9 deletions(-) --- base-commit: 31f32e8cdf59291e467250dfc57d1a8c718f63d2 change-id: 20260512-add-kvim4-sysled-8cc159524561 Best regards, -- Ronald Claveau From devnull+linux-kernel-dev.aliel.fr at kernel.org Wed May 13 03:43:53 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Wed, 13 May 2026 12:43:53 +0200 Subject: [PATCH v2 1/3] arm64: dts: amlogic: t7: Fix pwm_ao_c pinmux definitions In-Reply-To: <20260513-add-kvim4-sysled-v2-0-3ec9779e8875@aliel.fr> References: <20260513-add-kvim4-sysled-v2-0-3ec9779e8875@aliel.fr> Message-ID: <20260513-add-kvim4-sysled-v2-1-3ec9779e8875@aliel.fr> From: Ronald Claveau The pwm_ao_c pin node was incomplete: it was missing the group name suffix, conflating two distinct pin groups (pwm_ao_c_d and pwm_ao_c_e) into a single, ambiguous entry. Split the node into two separate pinmux entries: - pwm_ao_c_d_pins: uses group "pwm_ao_c_d" - pwm_ao_c_e_pins: uses group "pwm_ao_c_e" Both alternate pins are not yet referenced by any peripheral node, so this has no functional impact on existing boards. No backport needed. Fixes: ee6e05a49b93 ("arm64: dts: amlogic: t7: Add PWM pinctrl nodes") Reviewed-by: Neil Armstrong Signed-off-by: Ronald Claveau --- arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi index 7fe72c94ed623..62f6b9baad28b 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi @@ -400,9 +400,17 @@ mux { }; }; - pwm_ao_c_pins: pwm-ao-c { + pwm_ao_c_d_pins: pwm-ao-c-d { mux { - groups = "pwm_ao_c"; + groups = "pwm_ao_c_d"; + function = "pwm_ao_c"; + bias-disable; + }; + }; + + pwm_ao_c_e_pins: pwm-ao-c-e { + mux { + groups = "pwm_ao_c_e"; function = "pwm_ao_c"; bias-disable; }; -- 2.49.0 From devnull+linux-kernel-dev.aliel.fr at kernel.org Wed May 13 03:43:54 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Wed, 13 May 2026 12:43:54 +0200 Subject: [PATCH v2 2/3] arm64: dts: amlogic: t7: khadas-vim4: reorder root node In-Reply-To: <20260513-add-kvim4-sysled-v2-0-3ec9779e8875@aliel.fr> References: <20260513-add-kvim4-sysled-v2-0-3ec9779e8875@aliel.fr> Message-ID: <20260513-add-kvim4-sysled-v2-2-3ec9779e8875@aliel.fr> From: Ronald Claveau Move the xtal-clk node to restore alphabetical ordering. Signed-off-by: Ronald Claveau --- .../boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index 69d6118ba57e7..fd1b983354a01 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -45,13 +45,6 @@ secmon_reserved_bl32: secmon at 5300000 { }; }; - xtal: xtal-clk { - compatible = "fixed-clock"; - clock-frequency = <24000000>; - clock-output-names = "xtal"; - #clock-cells = <0>; - }; - dc_in: regulator-dc-in { compatible = "regulator-fixed"; regulator-name = "DC_IN"; @@ -155,6 +148,13 @@ wifi32k: wifi32k { clock-frequency = <32768>; pwms = <&pwm_ab 0 30518 0>; }; + + xtal: xtal-clk { + compatible = "fixed-clock"; + clock-frequency = <24000000>; + clock-output-names = "xtal"; + #clock-cells = <0>; + }; }; &pwm_ab { -- 2.49.0 From devnull+linux-kernel-dev.aliel.fr at kernel.org Wed May 13 03:43:55 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Wed, 13 May 2026 12:43:55 +0200 Subject: [PATCH v2 3/3] arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED In-Reply-To: <20260513-add-kvim4-sysled-v2-0-3ec9779e8875@aliel.fr> References: <20260513-add-kvim4-sysled-v2-0-3ec9779e8875@aliel.fr> Message-ID: <20260513-add-kvim4-sysled-v2-3-3ec9779e8875@aliel.fr> From: Ronald Claveau The VIM4 board exposes a status LED wired to the PWM_AO_C_D output. Enable the pwm_ao_cd controller with its pinmux, and declare a pwm-leds node with a heartbeat trigger. Signed-off-by: Ronald Claveau --- .../boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index fd1b983354a01..c41525a34b721 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -53,6 +53,16 @@ dc_in: regulator-dc-in { regulator-always-on; }; + pwm-leds { + compatible = "pwm-leds"; + + status { + linux,default-trigger="heartbeat"; + max-brightness = <255>; + pwms = <&pwm_ao_cd 0 30040 0>; + }; + }; + sd_3v3: regulator-sdcard-3v3 { compatible = "regulator-fixed"; regulator-name = "SD_3V3"; @@ -163,6 +173,12 @@ &pwm_ab { pinctrl-names = "default"; }; +&pwm_ao_cd { + status = "okay"; + pinctrl-0 = <&pwm_ao_c_d_pins>; + pinctrl-names = "default"; +}; + /* SDIO */ &sd_emmc_a { status = "okay"; -- 2.49.0 From neil.armstrong at linaro.org Wed May 13 05:21:25 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 13 May 2026 14:21:25 +0200 Subject: [PATCH v2 2/3] arm64: dts: amlogic: t7: khadas-vim4: reorder root node In-Reply-To: <20260513-add-kvim4-sysled-v2-2-3ec9779e8875@aliel.fr> References: <20260513-add-kvim4-sysled-v2-0-3ec9779e8875@aliel.fr> <20260513-add-kvim4-sysled-v2-2-3ec9779e8875@aliel.fr> Message-ID: On 5/13/26 12:43, Ronald Claveau via B4 Relay wrote: > From: Ronald Claveau > > Move the xtal-clk node to restore alphabetical ordering. > > Signed-off-by: Ronald Claveau > --- > .../boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 14 +++++++------- > 1 file changed, 7 insertions(+), 7 deletions(-) > > diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > index 69d6118ba57e7..fd1b983354a01 100644 > --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > @@ -45,13 +45,6 @@ secmon_reserved_bl32: secmon at 5300000 { > }; > }; > > - xtal: xtal-clk { > - compatible = "fixed-clock"; > - clock-frequency = <24000000>; > - clock-output-names = "xtal"; > - #clock-cells = <0>; > - }; > - > dc_in: regulator-dc-in { > compatible = "regulator-fixed"; > regulator-name = "DC_IN"; > @@ -155,6 +148,13 @@ wifi32k: wifi32k { > clock-frequency = <32768>; > pwms = <&pwm_ab 0 30518 0>; > }; > + > + xtal: xtal-clk { > + compatible = "fixed-clock"; > + clock-frequency = <24000000>; > + clock-output-names = "xtal"; > + #clock-cells = <0>; > + }; > }; > > &pwm_ab { > Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Wed May 13 05:35:31 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 13 May 2026 14:35:31 +0200 Subject: [PATCH v2 3/3] arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED In-Reply-To: <20260513-add-kvim4-sysled-v2-3-3ec9779e8875@aliel.fr> References: <20260513-add-kvim4-sysled-v2-0-3ec9779e8875@aliel.fr> <20260513-add-kvim4-sysled-v2-3-3ec9779e8875@aliel.fr> Message-ID: <3ab93bcd-81d7-4917-ae6d-3b8e7ba81dc6@linaro.org> On 5/13/26 12:43, Ronald Claveau via B4 Relay wrote: > From: Ronald Claveau > > The VIM4 board exposes a status LED wired to the PWM_AO_C_D output. > Enable the pwm_ao_cd controller with its pinmux, and declare a > pwm-leds node with a heartbeat trigger. > > Signed-off-by: Ronald Claveau > --- > .../boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 16 ++++++++++++++++ > 1 file changed, 16 insertions(+) > > diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > index fd1b983354a01..c41525a34b721 100644 > --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > @@ -53,6 +53,16 @@ dc_in: regulator-dc-in { > regulator-always-on; > }; > > + pwm-leds { > + compatible = "pwm-leds"; > + > + status { > + linux,default-trigger="heartbeat"; > + max-brightness = <255>; > + pwms = <&pwm_ao_cd 0 30040 0>; > + }; > + }; > + > sd_3v3: regulator-sdcard-3v3 { > compatible = "regulator-fixed"; > regulator-name = "SD_3V3"; > @@ -163,6 +173,12 @@ &pwm_ab { > pinctrl-names = "default"; > }; > > +&pwm_ao_cd { > + status = "okay"; > + pinctrl-0 = <&pwm_ao_c_d_pins>; > + pinctrl-names = "default"; > +}; > + > /* SDIO */ > &sd_emmc_a { > status = "okay"; > Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Wed May 13 05:37:04 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 13 May 2026 14:37:04 +0200 Subject: (subset) [PATCH v2 0/3] Khadas VIM4 PWM status LED support In-Reply-To: <20260513-add-kvim4-sysled-v2-0-3ec9779e8875@aliel.fr> References: <20260513-add-kvim4-sysled-v2-0-3ec9779e8875@aliel.fr> Message-ID: <177867582446.1433419.3719715146957232929.b4-ty@b4> Hi, On Wed, 13 May 2026 12:43:52 +0200, Ronald Claveau wrote: > This series adds support for the PWM-driven status LED on the Khadas > VIM4 board (Amlogic T7). > > The VIM4 exposes a heartbeat LED wired to the PWM_AO_C output, routed > through pin group pwm_ao_c_d. Before wiring it up in the board DTS, > the SoC pinmux definitions had to be corrected: the original > pwm_ao_c node was conflating two distinct pin groups (pwm_ao_c_d and > pwm_ao_c_e) into a single ambiguous entry. > > [...] Thanks, Applied to https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git (v7.2/arm64-dt) [2/3] arm64: dts: amlogic: t7: khadas-vim4: reorder root node https://git.kernel.org/amlogic/c/308e24fb9571be23aaee2e2a5de3da6f5cb3b029 [3/3] arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED https://git.kernel.org/amlogic/c/2bb37dc2976dc980a05ca93f529ff9977a24875d These changes has been applied on the intermediate git tree [1]. The v7.2/arm64-dt branch will then be sent via a formal Pull Request to the Linux SoC maintainers for inclusion in their intermediate git branches in order to be sent to Linus during the next merge window, or sooner if it's a set of fixes. In the cases of fixes, those will be merged in the current release candidate kernel and as soon they appear on the Linux master branch they will be backported to the previous Stable and Long-Stable kernels [2]. The intermediate git branches are merged daily in the linux-next tree [3], people are encouraged testing these pre-release kernels and report issues on the relevant mailing-lists. If problems are discovered on those changes, please submit a signed-off-by revert patch followed by a corrective changeset. [1] https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git [2] https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git [3] https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git -- Neil From neil.armstrong at linaro.org Wed May 13 05:37:02 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 13 May 2026 14:37:02 +0200 Subject: [PATCH v2] arm64: dts: amlogic: t7: khadas-vim4: Remove invalid property In-Reply-To: <20260330-fix-invalid-property-v2-1-228c51c8de93@aliel.fr> References: <20260330-fix-invalid-property-v2-1-228c51c8de93@aliel.fr> Message-ID: <177867582268.1433419.12828948814232574314.b4-ty@b4> Hi, On Mon, 30 Mar 2026 14:11:21 +0200, Ronald Claveau wrote: > Fix introduced invalid property for Khadas VIM4 sdcard regulator. > > arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dtb: regulator-sdcard-3v3 (regulator-fixed): Unevaluated properties are not allowed ('enable-active-low' was unexpected) > > Thanks, Applied to https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git (v7.2/arm64-dt) [1/1] arm64: dts: amlogic: t7: khadas-vim4: Remove invalid property https://git.kernel.org/amlogic/c/6f14ba3c3fa3d67b72e052f431f6776470eb2cbd These changes has been applied on the intermediate git tree [1]. The v7.2/arm64-dt branch will then be sent via a formal Pull Request to the Linux SoC maintainers for inclusion in their intermediate git branches in order to be sent to Linus during the next merge window, or sooner if it's a set of fixes. In the cases of fixes, those will be merged in the current release candidate kernel and as soon they appear on the Linux master branch they will be backported to the previous Stable and Long-Stable kernels [2]. The intermediate git branches are merged daily in the linux-next tree [3], people are encouraged testing these pre-release kernels and report issues on the relevant mailing-lists. If problems are discovered on those changes, please submit a signed-off-by revert patch followed by a corrective changeset. [1] https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git [2] https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git [3] https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git -- Neil From neil.armstrong at linaro.org Wed May 13 05:37:03 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 13 May 2026 14:37:03 +0200 Subject: (subset) [PATCH 2/2] arm64: dts: amlogic: t7: Fix missing required reset property In-Reply-To: <20260331-fix-aml-t7-null-reset-v1-2-eb95b625234c@aliel.fr> References: <20260331-fix-aml-t7-null-reset-v1-2-eb95b625234c@aliel.fr> Message-ID: <177867582359.1433419.10249035871609628577.b4-ty@b4> Hi, On Tue, 31 Mar 2026 16:24:05 +0200, Ronald Claveau wrote: > CHECK_DTBS shows missing reset required property in T7 DTBS. > A new CHECK_DTBS with this patch does not show this anymore. Thanks, Applied to https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git (v7.2/arm64-dt) [2/2] arm64: dts: amlogic: t7: Fix missing required reset property https://git.kernel.org/amlogic/c/068bad69dc4d3ffdd8ecf41b455cc9e1617d00bc These changes has been applied on the intermediate git tree [1]. The v7.2/arm64-dt branch will then be sent via a formal Pull Request to the Linux SoC maintainers for inclusion in their intermediate git branches in order to be sent to Linus during the next merge window, or sooner if it's a set of fixes. In the cases of fixes, those will be merged in the current release candidate kernel and as soon they appear on the Linux master branch they will be backported to the previous Stable and Long-Stable kernels [2]. The intermediate git branches are merged daily in the linux-next tree [3], people are encouraged testing these pre-release kernels and report issues on the relevant mailing-lists. If problems are discovered on those changes, please submit a signed-off-by revert patch followed by a corrective changeset. [1] https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git [2] https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git [3] https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git -- Neil From dmitry.baryshkov at oss.qualcomm.com Wed May 13 06:26:12 2026 From: dmitry.baryshkov at oss.qualcomm.com (Dmitry Baryshkov) Date: Wed, 13 May 2026 16:26:12 +0300 Subject: [PATCH] dt-bindings: Consolidate "sram" property definition In-Reply-To: <20260511165942.2774868-1-robh@kernel.org> References: <20260511165942.2774868-1-robh@kernel.org> Message-ID: On Mon, May 11, 2026 at 11:59:36AM -0500, Rob Herring (Arm) wrote: > The "sram" property has become a de facto standard property, so create a > common schema for it and drop all the duplicated definitions. > > Signed-off-by: Rob Herring (Arm) > --- > .../imx/fsl,imx8qxp-dc-command-sequencer.yaml | 2 +- > .../devicetree/bindings/display/msm/gpu.yaml | 6 +---- Acked-by: Dmitry Baryshkov # display/msm > .../bindings/dma/stericsson,dma40.yaml | 8 ++---- > .../bindings/media/cnm,wave521c.yaml | 2 +- > .../bindings/media/nxp,imx8-jpeg.yaml | 6 ++--- > .../bindings/media/rockchip,vdec.yaml | 5 ++-- > .../bindings/media/st,stm32-dcmi.yaml | 6 ++--- > .../devicetree/bindings/net/mediatek,net.yaml | 3 +-- > .../bindings/net/ti,icssg-prueth.yaml | 2 +- > .../bindings/net/ti,icssm-prueth.yaml | 2 +- > .../remoteproc/amlogic,meson-mx-ao-arc.yaml | 7 +---- > .../bindings/remoteproc/ti,k3-dsp-rproc.yaml | 8 ------ > .../bindings/remoteproc/ti,k3-r5f-rproc.yaml | 8 ------ > .../remoteproc/xlnx,zynqmp-r5fss.yaml | 9 +------ > .../devicetree/bindings/spi/st,stm32-spi.yaml | 10 +++---- > .../bindings/sram/sram-consumer.yaml | 26 +++++++++++++++++++ > 16 files changed, 48 insertions(+), 62 deletions(-) > create mode 100644 Documentation/devicetree/bindings/sram/sram-consumer.yaml > -- With best wishes Dmitry From krzk at kernel.org Wed May 13 10:59:33 2026 From: krzk at kernel.org (Krzysztof Kozlowski) Date: Wed, 13 May 2026 19:59:33 +0200 Subject: [PATCH] dt-bindings: Consolidate "sram" property definition In-Reply-To: <20260511165942.2774868-1-robh@kernel.org> References: <20260511165942.2774868-1-robh@kernel.org> Message-ID: On 11/05/2026 18:59, Rob Herring (Arm) wrote: > The "sram" property has become a de facto standard property, so create a > common schema for it and drop all the duplicated definitions. > > Signed-off-by: Rob Herring (Arm) Reviewed-by: Krzysztof Kozlowski Best regards, Krzysztof From dmitry.baryshkov at oss.qualcomm.com Wed May 13 11:23:20 2026 From: dmitry.baryshkov at oss.qualcomm.com (Dmitry Baryshkov) Date: Wed, 13 May 2026 21:23:20 +0300 Subject: [PATCH RESEND v3 0/6] drm: handle IRQ_HPD events correctly Message-ID: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> Both DisplayPort and HDMI standards define a way for the Sink / display to notify the Source / host about some kinds of events. In case of HDMI it's as simple as singnalling changes to the EDID. In case of DisplayPort it's more complicated and requires actual checking of the DPCD registers. Currently USB-C drivers don't have a way to deliver the IRQ_HPD notifications, leading to missing MST notifications. Provide necessary plumbing to let IRQ_HPD events be passed to the DisplayPort drivers. Note: the Yoga C630 UCSI driver and Acer Aspire1 EC driver are not yet enabled to send the IRQ_HPD events. Both of them would need some more reverse engineering to find out how the event is being reported by the EC. Signed-off-by: Dmitry Baryshkov --- Changes in v3: - Fixed build error if aux bridges are disabled (Intel GFX CI) - Link to v2: https://patch.msgid.link/20260420-hpd-irq-events-v2-0-402ffe27e9e9 at oss.qualcomm.com Changes in v2: - Change irq_hpd arg to be an enum, possibly desribing other uses (Toni) - Account for that, chaning the API accordingly (with_irq -> extra, etc.) - Wire up AUX bridge notifications - Link to v1: https://patch.msgid.link/20260416-hpd-irq-events-v1-0-1ab1f1cfb2b2 at oss.qualcomm.com --- Dmitry Baryshkov (6): drm/connector: report IRQ_HPD events to drm_connector_oob_hotplug_event() drm/bridge: pass down IRQ_HPD to the drivers drm/bridge: aux-hpd: let drivers pass IRQ_HPD events drm/msm: dp: handle the IRQ_HPD events reported by USB-C soc: qcom: pmic-glink-altmode: pass down HPD_IRQ events usb: typec: ucsi: huawei-gaokun: pass down HPD_IRQ events drivers/gpu/drm/bridge/aux-hpd-bridge.c | 11 +++++---- drivers/gpu/drm/bridge/chrontel-ch7033.c | 3 ++- drivers/gpu/drm/bridge/lontium-lt8912b.c | 3 ++- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 3 ++- drivers/gpu/drm/bridge/ti-tfp410.c | 4 ++-- drivers/gpu/drm/display/drm_bridge_connector.c | 22 ++++++++++-------- drivers/gpu/drm/drm_bridge.c | 20 ++++++++++------- drivers/gpu/drm/drm_connector.c | 7 ++++-- drivers/gpu/drm/i915/display/intel_dp.c | 3 ++- drivers/gpu/drm/meson/meson_encoder_hdmi.c | 3 ++- drivers/gpu/drm/msm/dp/dp_display.c | 6 ++++- drivers/gpu/drm/msm/dp/dp_drm.h | 3 ++- drivers/gpu/drm/omapdrm/dss/hdmi4.c | 3 ++- drivers/soc/qcom/pmic_glink_altmode.c | 6 ++++- drivers/usb/typec/altmodes/displayport.c | 15 +++++++++---- drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 11 +++++---- include/drm/bridge/aux-bridge.h | 13 +++++++++-- include/drm/drm_bridge.h | 31 +++++++++++++++++++++----- include/drm/drm_connector.h | 22 ++++++++++++++++-- 19 files changed, 138 insertions(+), 51 deletions(-) --- base-commit: c7275b05bc428c7373d97aa2da02d3a7fa6b9f66 change-id: 20260414-hpd-irq-events-e72bc076a5f1 Best regards, -- With best wishes Dmitry -- With best wishes Dmitry From dmitry.baryshkov at oss.qualcomm.com Wed May 13 11:23:21 2026 From: dmitry.baryshkov at oss.qualcomm.com (Dmitry Baryshkov) Date: Wed, 13 May 2026 21:23:21 +0300 Subject: [PATCH RESEND v3 1/6] drm/connector: report IRQ_HPD events to drm_connector_oob_hotplug_event() In-Reply-To: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> Message-ID: <20260513-hpd-irq-events-v3-1-086857017f16@oss.qualcomm.com> The DisplayPort standard defines a special kind of events called IRQ. These events are used to notify DP Source about the events on the Sink side. It is extremely important for DP MST handling, where the MST events are reported through this IRQ. In case of the USB-C DP AltMode there is no actual HPD pulse, but the events are ported through the bits in the AltMode VDOs. Extend the drm_connector_oob_hotplug_event() interface and report IRQ events to the DisplayPort Sink drivers. Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/drm_connector.c | 5 ++++- drivers/usb/typec/altmodes/displayport.c | 15 +++++++++++---- include/drm/drm_connector.h | 19 ++++++++++++++++++- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 47dc53c4a738..edee9daccd51 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -3510,6 +3510,8 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) * drm_connector_oob_hotplug_event - Report out-of-band hotplug event to connector * @connector_fwnode: fwnode_handle to report the event on * @status: hot plug detect logical state + * @extra_status: additional information provided by the sink without changing + * the HPD state (or in addition to such a change). * * On some hardware a hotplug event notification may come from outside the display * driver / device. An example of this is some USB Type-C setups where the hardware @@ -3520,7 +3522,8 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) * a drm_connector reference through calling drm_connector_find_by_fwnode(). */ void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct drm_connector *connector; diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 35d9c3086990..7182a8e2e710 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -189,7 +189,9 @@ static int dp_altmode_status_update(struct dp_altmode *dp) } else { drm_connector_oob_hotplug_event(dp->connector_fwnode, hpd ? connector_status_connected : - connector_status_disconnected); + connector_status_disconnected, + (hpd && irq_hpd) ? DRM_CONNECTOR_DP_IRQ_HPD : + DRM_CONNECTOR_NO_EXTRA_STATUS); dp->hpd = hpd; sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); if (hpd && irq_hpd) { @@ -212,7 +214,10 @@ static int dp_altmode_configured(struct dp_altmode *dp) */ if (dp->pending_hpd) { drm_connector_oob_hotplug_event(dp->connector_fwnode, - connector_status_connected); + connector_status_connected, + dp->pending_irq_hpd ? + DRM_CONNECTOR_DP_IRQ_HPD : + DRM_CONNECTOR_NO_EXTRA_STATUS); sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); dp->pending_hpd = false; if (dp->pending_irq_hpd) { @@ -397,7 +402,8 @@ static int dp_altmode_vdm(struct typec_altmode *alt, dp->data.conf = 0; if (dp->hpd) { drm_connector_oob_hotplug_event(dp->connector_fwnode, - connector_status_disconnected); + connector_status_disconnected, + DRM_CONNECTOR_NO_EXTRA_STATUS); dp->hpd = false; sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); } @@ -827,7 +833,8 @@ void dp_altmode_remove(struct typec_altmode *alt) if (dp->connector_fwnode) { drm_connector_oob_hotplug_event(dp->connector_fwnode, - connector_status_disconnected); + connector_status_disconnected, + DRM_CONNECTOR_NO_EXTRA_STATUS); fwnode_handle_put(dp->connector_fwnode); } diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index f83f28cae207..e05197e970d3 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -91,6 +91,22 @@ enum drm_connector_status { connector_status_unknown = 3, }; +/** + * enum drm_connector_status_extra - additional events sent by the sink / + * display together or in replacement of the HPD status changes. + */ +enum drm_connector_status_extra { + /** + * @DRM_CONNECTOR_NO_EXTRA_STATUS: No additional status reported. + */ + DRM_CONNECTOR_NO_EXTRA_STATUS, + /** + * @DRM_CONNECTOR_DP_IRQ_HPD: DisplayPort Sink has sent the + * IRQ_HPD (either by the HPD short pulse or via the AltMode event). + */ + DRM_CONNECTOR_DP_IRQ_HPD, +}; + /** * enum drm_connector_registration_state - userspace registration status for * a &drm_connector @@ -2521,7 +2537,8 @@ drm_connector_is_unregistered(struct drm_connector *connector) } void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, - enum drm_connector_status status); + enum drm_connector_status status, + enum drm_connector_status_extra extra_status); const char *drm_get_connector_type_name(unsigned int connector_type); const char *drm_get_connector_status_name(enum drm_connector_status status); const char *drm_get_subpixel_order_name(enum subpixel_order order); -- 2.47.3 From dmitry.baryshkov at oss.qualcomm.com Wed May 13 11:23:22 2026 From: dmitry.baryshkov at oss.qualcomm.com (Dmitry Baryshkov) Date: Wed, 13 May 2026 21:23:22 +0300 Subject: [PATCH RESEND v3 2/6] drm/bridge: pass down IRQ_HPD to the drivers In-Reply-To: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> Message-ID: <20260513-hpd-irq-events-v3-2-086857017f16@oss.qualcomm.com> Pass down the notifications about the IRQ_HPD events down to the individual drivers, letting them handle those as required. Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/bridge/chrontel-ch7033.c | 3 ++- drivers/gpu/drm/bridge/lontium-lt8912b.c | 3 ++- drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 3 ++- drivers/gpu/drm/bridge/ti-tfp410.c | 4 ++-- drivers/gpu/drm/display/drm_bridge_connector.c | 22 +++++++++++++--------- drivers/gpu/drm/drm_bridge.c | 5 +++-- drivers/gpu/drm/drm_connector.c | 2 +- drivers/gpu/drm/i915/display/intel_dp.c | 3 ++- drivers/gpu/drm/meson/meson_encoder_hdmi.c | 3 ++- drivers/gpu/drm/msm/dp/dp_display.c | 3 ++- drivers/gpu/drm/msm/dp/dp_drm.h | 3 ++- drivers/gpu/drm/omapdrm/dss/hdmi4.c | 3 ++- include/drm/drm_bridge.h | 9 ++++++--- include/drm/drm_connector.h | 3 ++- 14 files changed, 43 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/drm/bridge/chrontel-ch7033.c b/drivers/gpu/drm/bridge/chrontel-ch7033.c index 54d49d4882c8..04e6b4c00a28 100644 --- a/drivers/gpu/drm/bridge/chrontel-ch7033.c +++ b/drivers/gpu/drm/bridge/chrontel-ch7033.c @@ -259,7 +259,8 @@ static const struct drm_connector_helper_funcs ch7033_connector_helper_funcs = { .best_encoder = ch7033_connector_best_encoder, }; -static void ch7033_hpd_event(void *arg, enum drm_connector_status status) +static void ch7033_hpd_event(void *arg, enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct ch7033_priv *priv = arg; diff --git a/drivers/gpu/drm/bridge/lontium-lt8912b.c b/drivers/gpu/drm/bridge/lontium-lt8912b.c index 8a0b48efca58..b404f0cbf60d 100644 --- a/drivers/gpu/drm/bridge/lontium-lt8912b.c +++ b/drivers/gpu/drm/bridge/lontium-lt8912b.c @@ -504,7 +504,8 @@ static int lt8912_attach_dsi(struct lt8912 *lt) return 0; } -static void lt8912_bridge_hpd_cb(void *data, enum drm_connector_status status) +static void lt8912_bridge_hpd_cb(void *data, enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct lt8912 *lt = data; diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c index 11aab07d88df..ca41ebe9f26f 100644 --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c @@ -430,7 +430,8 @@ static const struct drm_edid *lt9611uxc_bridge_edid_read(struct drm_bridge *brid static void lt9611uxc_bridge_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { const struct drm_edid *drm_edid; diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c index 3b6b0e92cf89..199916662895 100644 --- a/drivers/gpu/drm/bridge/ti-tfp410.c +++ b/drivers/gpu/drm/bridge/ti-tfp410.c @@ -39,7 +39,6 @@ drm_bridge_to_tfp410(struct drm_bridge *bridge) { return container_of(bridge, struct tfp410, bridge); } - static inline struct tfp410 * drm_connector_to_tfp410(struct drm_connector *connector) { @@ -110,7 +109,8 @@ static void tfp410_hpd_work_func(struct work_struct *work) drm_helper_hpd_irq_event(dvi->bridge.dev); } -static void tfp410_hpd_callback(void *arg, enum drm_connector_status status) +static void tfp410_hpd_callback(void *arg, enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct tfp410 *dvi = arg; diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c index 39cc18f78eda..5fdb1a231cec 100644 --- a/drivers/gpu/drm/display/drm_bridge_connector.c +++ b/drivers/gpu/drm/display/drm_bridge_connector.c @@ -141,7 +141,8 @@ struct drm_bridge_connector { */ static void drm_bridge_connector_hpd_notify(struct drm_connector *connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct drm_bridge_connector *bridge_connector = to_drm_bridge_connector(connector); @@ -149,12 +150,13 @@ static void drm_bridge_connector_hpd_notify(struct drm_connector *connector, /* Notify all bridges in the pipeline of hotplug events. */ drm_for_each_bridge_in_chain_scoped(bridge_connector->encoder, bridge) { if (bridge->funcs->hpd_notify) - bridge->funcs->hpd_notify(bridge, connector, status); + bridge->funcs->hpd_notify(bridge, connector, status, extra_status); } } static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bridge_connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct drm_connector *connector = &drm_bridge_connector->base; struct drm_device *dev = connector->dev; @@ -163,24 +165,26 @@ static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bri connector->status = status; mutex_unlock(&dev->mode_config.mutex); - drm_bridge_connector_hpd_notify(connector, status); + drm_bridge_connector_hpd_notify(connector, status, extra_status); drm_kms_helper_connector_hotplug_event(connector); } static void drm_bridge_connector_hpd_cb(void *cb_data, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { - drm_bridge_connector_handle_hpd(cb_data, status); + drm_bridge_connector_handle_hpd(cb_data, status, extra_status); } static void drm_bridge_connector_oob_hotplug_event(struct drm_connector *connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct drm_bridge_connector *bridge_connector = to_drm_bridge_connector(connector); - drm_bridge_connector_handle_hpd(bridge_connector, status); + drm_bridge_connector_handle_hpd(bridge_connector, status, extra_status); } static void drm_bridge_connector_enable_hpd(struct drm_connector *connector) @@ -223,7 +227,7 @@ drm_bridge_connector_detect(struct drm_connector *connector, bool force) if (hdmi) drm_atomic_helper_connector_hdmi_hotplug(connector, status); - drm_bridge_connector_hpd_notify(connector, status); + drm_bridge_connector_hpd_notify(connector, status, DRM_CONNECTOR_NO_EXTRA_STATUS); } else { switch (connector->connector_type) { case DRM_MODE_CONNECTOR_DPI: diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index d6f512b73389..c8c3301cd936 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -1444,7 +1444,8 @@ EXPORT_SYMBOL_GPL(drm_bridge_edid_read); */ void drm_bridge_hpd_enable(struct drm_bridge *bridge, void (*cb)(void *data, - enum drm_connector_status status), + enum drm_connector_status status, + enum drm_connector_status_extra extra_status), void *data) { if (!(bridge->ops & DRM_BRIDGE_OP_HPD)) @@ -1509,7 +1510,7 @@ void drm_bridge_hpd_notify(struct drm_bridge *bridge, { mutex_lock(&bridge->hpd_mutex); if (bridge->hpd_cb) - bridge->hpd_cb(bridge->hpd_data, status); + bridge->hpd_cb(bridge->hpd_data, status, DRM_CONNECTOR_NO_EXTRA_STATUS); mutex_unlock(&bridge->hpd_mutex); } EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify); diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index edee9daccd51..415eb834808c 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -3532,7 +3532,7 @@ void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, return; if (connector->funcs->oob_hotplug_event) - connector->funcs->oob_hotplug_event(connector, status); + connector->funcs->oob_hotplug_event(connector, status, extra_status); drm_connector_put(connector); } diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index 4955bd8b11d7..98bbcab2067b 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -6779,7 +6779,8 @@ static int intel_dp_connector_atomic_check(struct drm_connector *_connector, } static void intel_dp_oob_hotplug_event(struct drm_connector *_connector, - enum drm_connector_status hpd_state) + enum drm_connector_status hpd_state, + enum drm_connector_status_extra extra_status) { struct intel_connector *connector = to_intel_connector(_connector); struct intel_display *display = to_intel_display(connector); diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c index 1abb0572bb5f..691b9996c8a4 100644 --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c @@ -323,7 +323,8 @@ static int meson_encoder_hdmi_atomic_check(struct drm_bridge *bridge, static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index d2124d625485..7a0623fdbd8e 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1785,7 +1785,8 @@ void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge) void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge); struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h index 9eb3431dd93a..74da3ef6b625 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.h +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -41,6 +41,7 @@ void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, - enum drm_connector_status status); + enum drm_connector_status status, + enum drm_connector_status_extra extra_status); #endif /* _DP_DRM_H_ */ diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c index 29b2dfb90b5f..a7288791b2a5 100644 --- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c +++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c @@ -429,7 +429,8 @@ static void hdmi4_bridge_disable(struct drm_bridge *bridge, static void hdmi4_bridge_hpd_notify(struct drm_bridge *bridge, struct drm_connector *connector, - enum drm_connector_status status) + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index a8d67bd9ee50..3e4672fbd7a8 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -615,7 +615,8 @@ struct drm_bridge_funcs { */ void (*hpd_notify)(struct drm_bridge *bridge, struct drm_connector *connector, - enum drm_connector_status status); + enum drm_connector_status status, + enum drm_connector_status_extra extra_status); /** * @hpd_enable: @@ -1260,7 +1261,8 @@ struct drm_bridge { * @hpd_cb: Hot plug detection callback, registered with * drm_bridge_hpd_enable(). */ - void (*hpd_cb)(void *data, enum drm_connector_status status); + void (*hpd_cb)(void *data, enum drm_connector_status status, + enum drm_connector_status_extra extra_status); /** * @hpd_data: Private data passed to the Hot plug detection callback * @hpd_cb. @@ -1550,7 +1552,8 @@ const struct drm_edid *drm_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector); void drm_bridge_hpd_enable(struct drm_bridge *bridge, void (*cb)(void *data, - enum drm_connector_status status), + enum drm_connector_status status, + enum drm_connector_status_extra extra_status), void *data); void drm_bridge_hpd_disable(struct drm_bridge *bridge); void drm_bridge_hpd_notify(struct drm_bridge *bridge, diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index e05197e970d3..5ac5a64f83d9 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1720,7 +1720,8 @@ struct drm_connector_funcs { * has been received from a source outside the display driver / device. */ void (*oob_hotplug_event)(struct drm_connector *connector, - enum drm_connector_status status); + enum drm_connector_status status, + enum drm_connector_status_extra extra_status); /** * @debugfs_init: -- 2.47.3 From dmitry.baryshkov at oss.qualcomm.com Wed May 13 11:23:23 2026 From: dmitry.baryshkov at oss.qualcomm.com (Dmitry Baryshkov) Date: Wed, 13 May 2026 21:23:23 +0300 Subject: [PATCH RESEND v3 3/6] drm/bridge: aux-hpd: let drivers pass IRQ_HPD events In-Reply-To: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> Message-ID: <20260513-hpd-irq-events-v3-3-086857017f16@oss.qualcomm.com> Let users of aux-hpd, the UCSI and PMIC GLINK drivers pass the IRQ_HPD events to the DisplayPort drivers. The drm_aux_hpd_bridge_notify() is keps to ease merging of the series, preventing extra cross-tree merges. It will be removed once all drivers are converted. The drm_bridge_hpd_notify() function is kept for the driver which only care about the connector status and will always pass false as the irq_hpd event. Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/bridge/aux-hpd-bridge.c | 11 +++++++---- drivers/gpu/drm/drm_bridge.c | 17 ++++++++++------- include/drm/bridge/aux-bridge.h | 13 +++++++++++-- include/drm/drm_bridge.h | 22 ++++++++++++++++++++-- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/bridge/aux-hpd-bridge.c b/drivers/gpu/drm/bridge/aux-hpd-bridge.c index f02a38a2638a..0e2f0b046121 100644 --- a/drivers/gpu/drm/bridge/aux-hpd-bridge.c +++ b/drivers/gpu/drm/bridge/aux-hpd-bridge.c @@ -136,16 +136,19 @@ struct device *drm_dp_hpd_bridge_register(struct device *parent, struct device_n EXPORT_SYMBOL_GPL(drm_dp_hpd_bridge_register); /** - * drm_aux_hpd_bridge_notify - notify hot plug detection events + * drm_aux_hpd_bridge_notify_extra - notify hot plug detection events * @dev: device created for the HPD bridge * @status: output connection status + * @extra_status: extra status bits like DRM_CONNECTOR_DP_IRQ_HPD * * A wrapper around drm_bridge_hpd_notify() that is used to report hot plug * detection events for bridges created via drm_dp_hpd_bridge_register(). * * This function shall be called in a context that can sleep. */ -void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status) +void drm_aux_hpd_bridge_notify_extra(struct device *dev, + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { struct auxiliary_device *adev = to_auxiliary_dev(dev); struct drm_aux_hpd_bridge_data *data = auxiliary_get_drvdata(adev); @@ -153,9 +156,9 @@ void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status sta if (!data) return; - drm_bridge_hpd_notify(&data->bridge, status); + drm_bridge_hpd_notify_extra(&data->bridge, status, extra_status); } -EXPORT_SYMBOL_GPL(drm_aux_hpd_bridge_notify); +EXPORT_SYMBOL_GPL(drm_aux_hpd_bridge_notify_extra); static int drm_aux_hpd_bridge_attach(struct drm_bridge *bridge, struct drm_encoder *encoder, diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index c8c3301cd936..09c3f5954ade 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -1495,25 +1495,28 @@ void drm_bridge_hpd_disable(struct drm_bridge *bridge) EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable); /** - * drm_bridge_hpd_notify - notify hot plug detection events + * drm_bridge_hpd_notify_extra - notify hot plug detection and sink IRQ events * @bridge: bridge control structure * @status: output connection status + * @extra_status: additional status recorded by the sink * * Bridge drivers shall call this function to report hot plug events when they - * detect a change in the output status, when hot plug detection has been - * enabled by drm_bridge_hpd_enable(). + * detect a change in the output status or when the sink has reported extra HPD + * status events (like the IRQ_HPD in case of the DisplayPort), when hot plug + * detection has been enabled by drm_bridge_hpd_enable(). * * This function shall be called in a context that can sleep. */ -void drm_bridge_hpd_notify(struct drm_bridge *bridge, - enum drm_connector_status status) +void drm_bridge_hpd_notify_extra(struct drm_bridge *bridge, + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { mutex_lock(&bridge->hpd_mutex); if (bridge->hpd_cb) - bridge->hpd_cb(bridge->hpd_data, status, DRM_CONNECTOR_NO_EXTRA_STATUS); + bridge->hpd_cb(bridge->hpd_data, status, extra_status); mutex_unlock(&bridge->hpd_mutex); } -EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify); +EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify_extra); #ifdef CONFIG_OF /** diff --git a/include/drm/bridge/aux-bridge.h b/include/drm/bridge/aux-bridge.h index c2f5a855512f..f9a86886b0df 100644 --- a/include/drm/bridge/aux-bridge.h +++ b/include/drm/bridge/aux-bridge.h @@ -25,7 +25,9 @@ struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent, str int devm_drm_dp_hpd_bridge_add(struct device *dev, struct auxiliary_device *adev); struct device *drm_dp_hpd_bridge_register(struct device *parent, struct device_node *np); -void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status); +void drm_aux_hpd_bridge_notify_extra(struct device *dev, + enum drm_connector_status status, + enum drm_connector_status_extra extra_status); #else static inline struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent, struct device_node *np) @@ -44,9 +46,16 @@ static inline struct device *drm_dp_hpd_bridge_register(struct device *parent, return NULL; } -static inline void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status) +static inline void drm_aux_hpd_bridge_notify_extra(struct device *dev, + enum drm_connector_status status, + enum drm_connector_status_extra extra_status) { } #endif +static inline void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status) +{ + drm_aux_hpd_bridge_notify_extra(dev, status, DRM_CONNECTOR_NO_EXTRA_STATUS); +} + #endif diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h index 3e4672fbd7a8..2cf604cf02db 100644 --- a/include/drm/drm_bridge.h +++ b/include/drm/drm_bridge.h @@ -1556,8 +1556,26 @@ void drm_bridge_hpd_enable(struct drm_bridge *bridge, enum drm_connector_status_extra extra_status), void *data); void drm_bridge_hpd_disable(struct drm_bridge *bridge); -void drm_bridge_hpd_notify(struct drm_bridge *bridge, - enum drm_connector_status status); +void drm_bridge_hpd_notify_extra(struct drm_bridge *bridge, + enum drm_connector_status status, + enum drm_connector_status_extra extra_status); + +/** + * drm_bridge_hpd_notify - notify hot plug detection events + * @bridge: bridge control structure + * @status: output connection status + * + * Bridge drivers shall call this function to report hot plug events when they + * detect a change in the output status, when hot plug detection has been + * enabled by drm_bridge_hpd_enable(). + * + * This function shall be called in a context that can sleep. + */ +static inline void drm_bridge_hpd_notify(struct drm_bridge *bridge, + enum drm_connector_status status) +{ + drm_bridge_hpd_notify_extra(bridge, status, DRM_CONNECTOR_NO_EXTRA_STATUS); +} #ifdef CONFIG_DRM_PANEL_BRIDGE bool drm_bridge_is_panel(const struct drm_bridge *bridge); -- 2.47.3 From dmitry.baryshkov at oss.qualcomm.com Wed May 13 11:23:24 2026 From: dmitry.baryshkov at oss.qualcomm.com (Dmitry Baryshkov) Date: Wed, 13 May 2026 21:23:24 +0300 Subject: [PATCH RESEND v3 4/6] drm/msm: dp: handle the IRQ_HPD events reported by USB-C In-Reply-To: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> Message-ID: <20260513-hpd-irq-events-v3-4-086857017f16@oss.qualcomm.com> Let the MSM DisplayPort driver properly track and handle IRQ_HPD delivered over the OOB events (e.g. from the USB-C AltMode handler). Signed-off-by: Dmitry Baryshkov --- drivers/gpu/drm/msm/dp/dp_display.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 7a0623fdbd8e..8df579bb320a 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -1800,4 +1800,7 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, msm_dp_add_event(dp, EV_HPD_PLUG_INT, 0, 0); else if (msm_dp_display->link_ready && status == connector_status_disconnected) msm_dp_add_event(dp, EV_HPD_UNPLUG_INT, 0, 0); + + if (extra_status == DRM_CONNECTOR_DP_IRQ_HPD) + msm_dp_add_event(dp, EV_IRQ_HPD_INT, 0, 0); } -- 2.47.3 From dmitry.baryshkov at oss.qualcomm.com Wed May 13 11:23:25 2026 From: dmitry.baryshkov at oss.qualcomm.com (Dmitry Baryshkov) Date: Wed, 13 May 2026 21:23:25 +0300 Subject: [PATCH RESEND v3 5/6] soc: qcom: pmic-glink-altmode: pass down HPD_IRQ events In-Reply-To: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> Message-ID: <20260513-hpd-irq-events-v3-5-086857017f16@oss.qualcomm.com> Pass IRQ_HPD events to the HPD bridge, letting those to be delivered to the DisplayPort driver. Signed-off-by: Dmitry Baryshkov --- drivers/soc/qcom/pmic_glink_altmode.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/soc/qcom/pmic_glink_altmode.c b/drivers/soc/qcom/pmic_glink_altmode.c index 619bad2c27ee..946eb20b8f83 100644 --- a/drivers/soc/qcom/pmic_glink_altmode.c +++ b/drivers/soc/qcom/pmic_glink_altmode.c @@ -373,7 +373,11 @@ static void pmic_glink_altmode_worker(struct work_struct *work) else conn_status = connector_status_disconnected; - drm_aux_hpd_bridge_notify(&alt_port->bridge->dev, conn_status); + drm_aux_hpd_bridge_notify_extra(&alt_port->bridge->dev, + conn_status, + alt_port->hpd_irq ? + DRM_CONNECTOR_DP_IRQ_HPD : + DRM_CONNECTOR_NO_EXTRA_STATUS); } else if (alt_port->mux_ctrl == MUX_CTRL_STATE_TUNNELING) { if (alt_port->svid == USB_TYPEC_TBT_SID) pmic_glink_altmode_enable_tbt(altmode, alt_port); -- 2.47.3 From dmitry.baryshkov at oss.qualcomm.com Wed May 13 11:23:26 2026 From: dmitry.baryshkov at oss.qualcomm.com (Dmitry Baryshkov) Date: Wed, 13 May 2026 21:23:26 +0300 Subject: [PATCH RESEND v3 6/6] usb: typec: ucsi: huawei-gaokun: pass down HPD_IRQ events In-Reply-To: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> Message-ID: <20260513-hpd-irq-events-v3-6-086857017f16@oss.qualcomm.com> Pass IRQ_HPD events to the HPD bridge, letting those to be delivered to the DisplayPort driver. Reviewed-by: Pengyu Luo Acked-by: Heikki Krogerus Signed-off-by: Dmitry Baryshkov --- drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c index ca749fde49bd..5e3f887ecbd8 100644 --- a/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c +++ b/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c @@ -299,10 +299,13 @@ static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port) /* UCSI callback .connector_status() have set orientation */ if (port->bridge) - drm_aux_hpd_bridge_notify(&port->bridge->dev, - port->hpd_state ? - connector_status_connected : - connector_status_disconnected); + drm_aux_hpd_bridge_notify_extra(&port->bridge->dev, + port->hpd_state ? + connector_status_connected : + connector_status_disconnected, + port->hpd_irq ? + DRM_CONNECTOR_DP_IRQ_HPD : + DRM_CONNECTOR_NO_EXTRA_STATUS); gaokun_ec_ucsi_pan_ack(uec->ec, port->idx); } -- 2.47.3 From sashiko-bot at kernel.org Wed May 13 16:44:32 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Wed, 13 May 2026 23:44:32 +0000 Subject: [PATCH 1/2] arm64: dts: amlogic: t7: Fix pwm_ao_c pinmux definitions In-Reply-To: <20260512-add-kvim4-sysled-v1-1-7178719a43e7@aliel.fr> References: <20260512-add-kvim4-sysled-v1-1-7178719a43e7@aliel.fr> Message-ID: <20260513234433.7D0A0C19425@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The commit provides an incomplete fix by resolving missing pin group suffixes for `pwm_ao_c` but overlooking identical bugs for `pwm_ao_g`, `pwm_ao_h`, and `pwm_vs` in the same device tree file. -- commit 45b298237b81cf91a3c02cc08237c80e6356d308 Author: Ronald Claveau arm64: dts: amlogic: t7: Fix pwm_ao_c pinmux definitions The pwm_ao_c pin node was incomplete: it was missing the group name suffix, conflating two distinct pin groups (pwm_ao_c_d and pwm_ao_c_e) into a single, ambiguous entry. > diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi > index 7fe72c94ed623..62f6b9baad28b 100644 > --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi > +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi > @@ -400,9 +400,17 @@ mux { > }; > }; > > - pwm_ao_c_pins: pwm-ao-c { > + pwm_ao_c_d_pins: pwm-ao-c-d { > mux { > - groups = "pwm_ao_c"; > + groups = "pwm_ao_c_d"; Should we also fix the regressions for pwm_ao_g, pwm_ao_h, and pwm_vs in this commit? In this same device tree file, pwm_ao_g_pins, pwm_ao_h_pins, and pwm_vs_pins are defined using the same generic string pattern: arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi: pwm_ao_g_pins: pwm-ao-g { mux { groups = "pwm_ao_g"; function = "pwm_ao_g"; bias-disable; }; }; In drivers/pinctrl/meson/pinctrl-amlogic-t7.c, these generic groups do not exist as valid pin arrays. The driver only defines suffixed variants like pwm_ao_g_d11, pwm_ao_g_d7, pwm_ao_g_e, pwm_ao_h_d5, pwm_ao_h_d10, pwm_vs_y, and pwm_vs_h. If any future or out-of-tree board tries to use these existing nodes, won't it result in a pinctrl lookup failure at runtime? > + function = "pwm_ao_c"; > + bias-disable; > + }; > + }; > + > + pwm_ao_c_e_pins: pwm-ao-c-e { > + mux { > + groups = "pwm_ao_c_e"; > function = "pwm_ao_c"; > bias-disable; > }; -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260512-add-kvim4-sysled-v1-0-7178719a43e7 at aliel.fr?part=1 From sashiko-bot at kernel.org Wed May 13 16:51:08 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Wed, 13 May 2026 23:51:08 +0000 Subject: [PATCH 2/2] arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED In-Reply-To: <20260512-add-kvim4-sysled-v1-2-7178719a43e7@aliel.fr> References: <20260512-add-kvim4-sysled-v1-2-7178719a43e7@aliel.fr> Message-ID: <20260513235109.9413BC19425@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The child node of `pwm-leds` is named `status`, which violates the DT schema bindings for PWM LEDs. -- commit 4d1f2b73cb8a598488da21baaa8d88280487e6f6 Author: Ronald Claveau arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED This commit adds a new pwm-leds node with a heartbeat trigger to the VIM4 board devicetree. It also enables the pwm_ao_cd controller and reorders the xtal-clk node. > diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > index 69d6118ba57e7..c41525a34b721 100644 > --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts [ ... ] > @@ -60,6 +53,16 @@ dc_in: regulator-dc-in { > regulator-always-on; > }; > > + pwm-leds { > + compatible = "pwm-leds"; > + > + status { Will this child node name trigger a devicetree schema validation warning? The PWM LEDs device tree binding schema restricts child node names using a pattern property. It expects child nodes to match the regular expression ^led(-[0-9a-f]+)?$. Since this node is named status, will dtbs_check treat it as an invalid additional node? > + linux,default-trigger="heartbeat"; > + max-brightness = <255>; > + pwms = <&pwm_ao_cd 0 30040 0>; > + }; > + }; -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260512-add-kvim4-sysled-v1-0-7178719a43e7 at aliel.fr?part=2 From sashiko-bot at kernel.org Wed May 13 21:05:19 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Thu, 14 May 2026 04:05:19 +0000 Subject: [PATCH v2 3/3] arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED In-Reply-To: <20260513-add-kvim4-sysled-v2-3-3ec9779e8875@aliel.fr> References: <20260513-add-kvim4-sysled-v2-3-3ec9779e8875@aliel.fr> Message-ID: <20260514040520.5FF16C2BCB7@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The subnode for the LED under `pwm-leds` is named `status`, which violates the `leds-pwm` device tree binding schema. -- commit fa7913b140af3e7d64c22b109d1765f504180ad0 Author: Ronald Claveau arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED This commit enables the pwm_ao_cd controller and adds a pwm-leds node with a heartbeat trigger to expose the status LED on the VIM4 board. > diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts > @@ -53,6 +53,16 @@ dc_in: regulator-dc-in { > regulator-always-on; > }; > > + pwm-leds { > + compatible = "pwm-leds"; > + > + status { Does this subnode name trigger a device tree schema validation error? The leds-pwm binding schema in Documentation/devicetree/bindings/leds/leds-pwm.yaml specifies additionalProperties: false and expects child node names to match the regular expression ^led(-[0-9a-f]+)?$. Using status as a node name is also confusing since it is commonly used as a standard device tree property. Should this be renamed to something like led-status or led-0 to comply with the schema? > + linux,default-trigger="heartbeat"; > + max-brightness = <255>; > + pwms = <&pwm_ao_cd 0 30040 0>; > + }; > + }; -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260513-add-kvim4-sysled-v2-0-3ec9779e8875 at aliel.fr?part=3 From sashiko-bot at kernel.org Thu May 14 03:44:23 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Thu, 14 May 2026 10:44:23 +0000 Subject: [PATCH RESEND v3 1/6] drm/connector: report IRQ_HPD events to drm_connector_oob_hotplug_event() In-Reply-To: <20260513-hpd-irq-events-v3-1-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-1-086857017f16@oss.qualcomm.com> Message-ID: <20260514104424.04B3FC2BCB3@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] IRQ_HPD events are silently dropped if a status update triggers a reconfiguration while the HPD state remains unchanged. - [High] Deferred events in `dp_altmode_configured()` unconditionally report the display as connected and leak stale `pending_irq_hpd` / `pending_hpd` state. -- commit 53d82f9a5286a8fa5530ca1be94f61940637586b Author: Dmitry Baryshkov drm/connector: report IRQ_HPD events to drm_connector_oob_hotplug_event() This commit extends the drm_connector_oob_hotplug_event() interface to report DisplayPort irq events. These events are now forwarded through the alt mode messages to the sink drivers to support multi-stream transport handling. > diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c > index 35d9c30869900..7182a8e2e7101 100644 > --- a/drivers/usb/typec/altmodes/displayport.c > +++ b/drivers/usb/typec/altmodes/displayport.c [ ... ] > @@ -212,7 +214,10 @@ static int dp_altmode_configured(struct dp_altmode *dp) > */ > if (dp->pending_hpd) { > drm_connector_oob_hotplug_event(dp->connector_fwnode, > - connector_status_connected); > + connector_status_connected, > + dp->pending_irq_hpd ? > + DRM_CONNECTOR_DP_IRQ_HPD : > + DRM_CONNECTOR_NO_EXTRA_STATUS); Are irq events silently dropped if a status update triggers a reconfiguration while the hotplug detect state remains unchanged? Looking at dp_altmode_status_update(), if a status update requires reconfiguration, the code attempts to buffer the event: if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) { ret = dp_altmode_configure(dp, con); if (!ret) { dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME : DP_STATE_CONFIGURE; if (dp->hpd != hpd) { dp->hpd = hpd; dp->pending_hpd = true; } if (dp->hpd && dp->pending_hpd && irq_hpd) dp->pending_irq_hpd = true; } } It only sets dp->pending_hpd = true if the hotplug detect state has toggled. If it remains unchanged, dp->pending_hpd stays false. This prevents dp->pending_irq_hpd from being set even if irq_hpd is true. Could this cause DisplayPort irq events that arrive concurrently with a configuration change to be lost, preventing downstream multi-stream transport topology updates and link training? Additionally, does this deferred event unconditionally report the display as connected and leak stale state? If multiple status update messages arrive before configuration completes, dp->hpd can transition to false while dp->pending_hpd and dp->pending_irq_hpd remain true. Similarly, if the alt mode is exited via CMD_EXIT_MODE while dp->pending_hpd is true, these pending flags are never cleared. Will this cause the drm driver to receive a spurious connection event and irq when configuration completes or when the alt mode is later re-entered? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260513-hpd-irq-events-v3-0-086857017f16 at oss.qualcomm.com?part=1 From lee at kernel.org Thu May 14 03:54:59 2026 From: lee at kernel.org (Lee Jones) Date: Thu, 14 May 2026 11:54:59 +0100 Subject: [PATCH v5 4/8] mfd: khadas-mcu: Add support for VIM4 MCU variant In-Reply-To: <20260424-add-mcu-fan-khadas-vim4-v5-4-afcfa7157b23@aliel.fr> References: <20260424-add-mcu-fan-khadas-vim4-v5-0-afcfa7157b23@aliel.fr> <20260424-add-mcu-fan-khadas-vim4-v5-4-afcfa7157b23@aliel.fr> Message-ID: <20260514105459.GJ305027@google.com> On Fri, 24 Apr 2026, Ronald Claveau via B4 Relay wrote: > From: Ronald Claveau > > Refactor probe() to use per-variant khadas_mcu_data > instead of hardcoded globals. > > Add dedicated regmap configuration and device data for the VIM4 MCU, > with its own volatile/writeable registers. > > Add the fan control register > (0?100 levels vs 0?3 for previous supported boards). > > Add a new compatible string "khadas,vim4-mcu". > > Reviewed-by: Neil Armstrong > Signed-off-by: Ronald Claveau > --- > drivers/mfd/khadas-mcu.c | 106 ++++++++++++++++++++++++++++++++++++++++++----- > 1 file changed, 95 insertions(+), 11 deletions(-) > > diff --git a/drivers/mfd/khadas-mcu.c b/drivers/mfd/khadas-mcu.c > index ba981a7886921..b36b3b3ab73c0 100644 > --- a/drivers/mfd/khadas-mcu.c > +++ b/drivers/mfd/khadas-mcu.c > @@ -75,15 +75,91 @@ static const struct regmap_config khadas_mcu_regmap_config = { > .cache_type = REGCACHE_MAPLE, > }; > > +static const struct khadas_mcu_fan_pdata khadas_mcu_fan_pdata = { > + .fan_reg = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG, > + .max_level = 3, > +}; What is 3? > + > static struct mfd_cell khadas_mcu_fan_cells[] = { > /* VIM1/2 Rev13+ and VIM3 only */ > - { .name = "khadas-mcu-fan-ctrl", }, > + { > + .name = "khadas-mcu-fan-ctrl", > + .platform_data = &khadas_mcu_fan_pdata, > + .pdata_size = sizeof(khadas_mcu_fan_pdata), > + }, > }; Worth making this const at one point. > > static struct mfd_cell khadas_mcu_cells[] = { > { .name = "khadas-mcu-user-mem", }, > }; > > +static const struct khadas_mcu_data khadas_mcu_data = { > + .regmap_config = &khadas_mcu_regmap_config, > + .cells = khadas_mcu_cells, > + .ncells = ARRAY_SIZE(khadas_mcu_cells), > + .fan_cells = khadas_mcu_fan_cells, > + .nfan_cells = ARRAY_SIZE(khadas_mcu_fan_cells), > +}; This is a red flag! > +static bool khadas_mcu_vim4_reg_volatile(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case KHADAS_MCU_PWR_OFF_CMD_REG: > + case KHADAS_MCU_VIM4_REST_CONF_REG: > + case KHADAS_MCU_WOL_INIT_START_REG: > + case KHADAS_MCU_VIM4_LED_ON_RAM_REG: > + case KHADAS_MCU_VIM4_FAN_CTRL_REG: > + case KHADAS_MCU_VIM4_WDT_EN_REG: > + case KHADAS_MCU_VIM4_SYS_RST_REG: > + return true; > + default: > + return false; > + } > +} > + > +static bool khadas_mcu_vim4_reg_writeable(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case KHADAS_MCU_VERSION_0_REG: > + case KHADAS_MCU_VERSION_1_REG: > + case KHADAS_MCU_SHUTDOWN_NORMAL_STATUS_REG: > + return false; > + default: > + return true; > + } > +} > + > +static const struct regmap_config khadas_mcu_vim4_regmap_config = { > + .reg_bits = 8, > + .reg_stride = 1, > + .val_bits = 8, > + .max_register = KHADAS_MCU_VIM4_SYS_RST_REG, > + .volatile_reg = khadas_mcu_vim4_reg_volatile, > + .writeable_reg = khadas_mcu_vim4_reg_writeable, > + .cache_type = REGCACHE_MAPLE, > +}; > + > +static const struct khadas_mcu_fan_pdata khadas_vim4_fan_pdata = { > + .fan_reg = KHADAS_MCU_VIM4_FAN_CTRL_REG, > + .max_level = 0x64, > +}; > + > +static const struct mfd_cell khadas_mcu_vim4_cells[] = { > + { > + .name = "khadas-mcu-fan-ctrl", > + .platform_data = &khadas_vim4_fan_pdata, > + .pdata_size = sizeof(khadas_vim4_fan_pdata), > + }, > +}; > + > +static const struct khadas_mcu_data khadas_vim4_mcu_data = { > + .regmap_config = &khadas_mcu_vim4_regmap_config, > + .cells = NULL, > + .ncells = 0, > + .fan_cells = khadas_mcu_vim4_cells, > + .nfan_cells = ARRAY_SIZE(khadas_mcu_vim4_cells), > +}; > + > static int khadas_mcu_probe(struct i2c_client *client) > { > struct device *dev = &client->dev; > @@ -94,28 +170,35 @@ static int khadas_mcu_probe(struct i2c_client *client) > if (!ddata) > return -ENOMEM; > > + ddata->data = i2c_get_match_data(client); > + if (!ddata->data) > + return -EINVAL; Shouldn't this be -ENODEV? > i2c_set_clientdata(client, ddata); > > ddata->dev = dev; > > - ddata->regmap = devm_regmap_init_i2c(client, &khadas_mcu_regmap_config); > + ddata->regmap = devm_regmap_init_i2c(client, > + ddata->data->regmap_config); Use up to 100-chars to prevent this kind of wrapping. > if (IS_ERR(ddata->regmap)) { > ret = PTR_ERR(ddata->regmap); > dev_err(dev, "Failed to allocate register map: %d\n", ret); > return ret; > } Maybe convert this to dev_err_probe() at one point. > - ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, > - khadas_mcu_cells, > - ARRAY_SIZE(khadas_mcu_cells), > - NULL, 0, NULL); > - if (ret) > - return ret; > + if (ddata->data->cells && ddata->data->ncells) { > + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, > + ddata->data->cells, > + ddata->data->ncells, > + NULL, 0, NULL); > + if (ret) > + return ret; > + } > > if (of_property_present(dev->of_node, "#cooling-cells")) > return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, > - khadas_mcu_fan_cells, > - ARRAY_SIZE(khadas_mcu_fan_cells), > + ddata->data->fan_cells, > + ddata->data->nfan_cells, > NULL, 0, NULL); > > return 0; > @@ -123,7 +206,8 @@ static int khadas_mcu_probe(struct i2c_client *client) > > #ifdef CONFIG_OF > static const struct of_device_id khadas_mcu_of_match[] = { > - { .compatible = "khadas,mcu", }, > + { .compatible = "khadas,mcu", .data = &khadas_mcu_data }, > + { .compatible = "khadas,vim4-mcu", .data = &khadas_vim4_mcu_data }, We don't allow data from one registration API (MFD) to be shoved through another (DT). Pass a value to match on instead, then use a switch() statement or similar to populate or register the devices. > {}, > }; > MODULE_DEVICE_TABLE(of, khadas_mcu_of_match); > > -- > 2.49.0 > > From sashiko-bot at kernel.org Thu May 14 05:06:00 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Thu, 14 May 2026 12:06:00 +0000 Subject: [PATCH RESEND v3 4/6] drm/msm: dp: handle the IRQ_HPD events reported by USB-C In-Reply-To: <20260513-hpd-irq-events-v3-4-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-4-086857017f16@oss.qualcomm.com> Message-ID: <20260514120601.54231C2BCB3@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Unbounded enqueueing of EV_IRQ_HPD_INT events can exhaust the small event queue, causing critical hotplug events to be dropped and deadlocking the DP driver. -- commit 03d05144a4cd57cfc0c6a88377ee764f4224e2b4 Author: Dmitry Baryshkov drm/msm: dp: handle the IRQ_HPD events reported by USB-C Let the MSM DisplayPort driver properly track and handle IRQ_HPD delivered over the OOB events (e.g. from the USB-C AltMode handler). > diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c > --- a/drivers/gpu/drm/msm/dp/dp_display.c > +++ b/drivers/gpu/drm/msm/dp/dp_display.c > @@ -1800,4 +1800,7 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, > msm_dp_add_event(dp, EV_HPD_PLUG_INT, 0, 0); > else if (msm_dp_display->link_ready && status == connector_status_disconnected) > msm_dp_add_event(dp, EV_HPD_UNPLUG_INT, 0, 0); > + > + if (extra_status == DRM_CONNECTOR_DP_IRQ_HPD) > + msm_dp_add_event(dp, EV_IRQ_HPD_INT, 0, 0); Could this unbounded enqueueing of EV_IRQ_HPD_INT events exhaust the event queue? The MSM DP driver's event queue has a fixed capacity of 7 items, as DP_EVENT_Q_MAX is 8. Adding EV_IRQ_HPD_INT events directly from out-of-band messages without deduplication could fill the queue if multiple events arrive quickly. If the display state is ST_MAINLINK_READY or ST_DISCONNECT_PENDING, msm_dp_irq_hpd_handle() defers the event by re-adding it to the queue with delay = 1. The hpd_event_thread handles delay = 1 events by converting them to delay = 0, placing them back in the queue, and sleeping for 100ms before calling msm_dp_irq_hpd_handle() again. This causes the event to cycle endlessly, perpetually occupying a slot. If a sink sends 7 rapid IRQ_HPD signals while the driver is parked in ST_MAINLINK_READY, the queue might become entirely full with 7 cycling, unconsumed events. If the queue is full, would critical events like EV_HPD_UNPLUG_INT be lost since msm_dp_add_event() returns -EPERM? Losing the unplug event could trap the DRM framework in a connected state despite physical disconnection. -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260513-hpd-irq-events-v3-0-086857017f16 at oss.qualcomm.com?part=4 From sashiko-bot at kernel.org Thu May 14 05:35:34 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Thu, 14 May 2026 12:35:34 +0000 Subject: [PATCH RESEND v3 5/6] soc: qcom: pmic-glink-altmode: pass down HPD_IRQ events In-Reply-To: <20260513-hpd-irq-events-v3-5-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-5-086857017f16@oss.qualcomm.com> Message-ID: <20260514123535.31AE7C2BCB3@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Use-After-Free on device unbind due to uncancelled workqueue items. -- commit c9a05fdb7ae95c5650ccfbbd12196c4169d2d1c4 Author: Dmitry Baryshkov soc: qcom: pmic-glink-altmode: pass down HPD_IRQ events This commit updates the PMIC GLINK altmode driver to pass IRQ_HPD events to the HPD bridge. This allows the events to be delivered to the DisplayPort driver. > diff --git a/drivers/soc/qcom/pmic_glink_altmode.c b/drivers/soc/qcom/pmic_glink_altmode.c > --- a/drivers/soc/qcom/pmic_glink_altmode.c > +++ b/drivers/soc/qcom/pmic_glink_altmode.c > @@ -373,7 +373,11 @@ static void pmic_glink_altmode_worker(struct work_struct *work) > else > conn_status = connector_status_disconnected; > > - drm_aux_hpd_bridge_notify(&alt_port->bridge->dev, conn_status); > + drm_aux_hpd_bridge_notify_extra(&alt_port->bridge->dev, > + conn_status, > + alt_port->hpd_irq ? > + DRM_CONNECTOR_DP_IRQ_HPD : > + DRM_CONNECTOR_NO_EXTRA_STATUS); While reviewing this change, I noticed a pre-existing issue with the lifecycle of this work item. The altmode structure is allocated using devm_kzalloc, and multiple work items like alt_port->work and altmode->enable_work are initialized, but they are never explicitly canceled during device teardown. When the device is unbound, devres automatically frees the altmode structure. If this work item is pending or executing concurrently on the system workqueue during unbind, could it dereference the freed altmode or alt_port structures and cause a use-after-free? Should a cancel_work_sync action be registered via devm_add_action_or_reset to ensure these work items are flushed before the memory is freed? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260513-hpd-irq-events-v3-0-086857017f16 at oss.qualcomm.com?part=5 From jbrunet at baylibre.com Thu May 14 08:11:01 2026 From: jbrunet at baylibre.com (Jerome Brunet) Date: Thu, 14 May 2026 17:11:01 +0200 Subject: [PATCH 07/10] clk: amlogic: Support POWER_OF_TWO for PLL pre-divider In-Reply-To: <20260511-b4-a9_clk-v1-7-41cb4071b7c9@amlogic.com> (Jian Hu via's message of "Mon, 11 May 2026 20:47:29 +0800") References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-7-41cb4071b7c9@amlogic.com> Message-ID: <1jy0hm6n7e.fsf@starbuckisacylon.baylibre.com> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > From: Jian Hu > > The A9 PLL pre-divider uses a division factor of 2^n to ensure a clock > duty cycle of 50% after predivision. > > Add flag 'CLK_MESON_PLL_N_POWER_OF_TWO' to indicate that the PLL > pre-divider division factor is 2^n. I understand what you are doing here but I have to ask why this can't be implemented with independent dividers that already supports power of 2 ? > > Signed-off-by: Jian Hu > --- > drivers/clk/meson/clk-pll.c | 28 +++++++++++++++++++++++----- > drivers/clk/meson/clk-pll.h | 2 ++ > 2 files changed, 25 insertions(+), 5 deletions(-) > > diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c > index 8568ad6ba7b6..49483e431d44 100644 > --- a/drivers/clk/meson/clk-pll.c > +++ b/drivers/clk/meson/clk-pll.c > @@ -66,6 +66,9 @@ static unsigned long __pll_params_to_rate(unsigned long parent_rate, > rate += DIV_ROUND_UP_ULL(frac_rate, frac_max); > } > > + if (pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO) > + n = 1 << n; > + > return DIV_ROUND_UP_ULL(rate, n); > } > > @@ -83,7 +86,7 @@ static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw, > * it would result in a division by zero. The rate can't be > * calculated in this case > */ > - if (n == 0) > + if (n == 0 && !(pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO)) > return 0; > > m = meson_parm_read(clk->map, &pll->m); > @@ -103,7 +106,12 @@ static unsigned int __pll_params_with_frac(unsigned long rate, > { > unsigned int frac_max = pll->frac_max ? pll->frac_max : > (1 << pll->frac.width); > - u64 val = (u64)rate * n; > + u64 val; > + > + if (pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO) > + n = 1 << n; > + > + val = (u64)rate * n; > > /* Bail out if we are already over the requested rate */ > if (rate < parent_rate * m / n) > @@ -142,7 +150,8 @@ static int meson_clk_get_pll_table_index(unsigned int index, > unsigned int *n, > struct meson_clk_pll_data *pll) > { > - if (!pll->table[index].n) > + if (!pll->table[index].n && > + !(pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO)) > return -EINVAL; > > *m = pll->table[index].m; > @@ -156,7 +165,12 @@ static unsigned int meson_clk_get_pll_range_m(unsigned long rate, > unsigned int n, > struct meson_clk_pll_data *pll) > { > - u64 val = (u64)rate * n; > + u64 val; > + > + if (pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO) > + n = 1 << n; > + > + val = (u64)rate * n; > > if (__pll_round_closest_mult(pll)) > return DIV_ROUND_CLOSEST_ULL(val, parent_rate); > @@ -173,11 +187,15 @@ static int meson_clk_get_pll_range_index(unsigned long rate, > { > *n = index + 1; > > + if ((pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO)) > + *n = index; > + > /* Check the predivider range */ > if (*n >= (1 << pll->n.width)) > return -EINVAL; > > - if (*n == 1) { > + if ((*n == 1 && !(pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO)) || > + (*n == 0 && (pll->flags & CLK_MESON_PLL_N_POWER_OF_TWO))) { > /* Get the boundaries out the way */ > if (rate <= pll->range->min * parent_rate) { > *m = pll->range->min; > diff --git a/drivers/clk/meson/clk-pll.h b/drivers/clk/meson/clk-pll.h > index 1be7e6e77631..60b2772a54c8 100644 > --- a/drivers/clk/meson/clk-pll.h > +++ b/drivers/clk/meson/clk-pll.h > @@ -33,6 +33,8 @@ struct pll_mult_range { > #define CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH BIT(2) > /* rst signal is active-low (Power-on reset) */ > #define CLK_MESON_PLL_RST_ACTIVE_LOW BIT(3) > +/* The division factor of the PLL pre-divider is 2^n */ > +#define CLK_MESON_PLL_N_POWER_OF_TWO BIT(4) > > struct meson_clk_pll_data { > struct parm en; -- Jerome From jbrunet at baylibre.com Thu May 14 08:13:13 2026 From: jbrunet at baylibre.com (Jerome Brunet) Date: Thu, 14 May 2026 17:13:13 +0200 Subject: [PATCH 05/10] clk: amlogic: PLL l_detect signal supports active-high configuration In-Reply-To: <20260511-b4-a9_clk-v1-5-41cb4071b7c9@amlogic.com> (Jian Hu via's message of "Mon, 11 May 2026 20:47:27 +0800") References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-5-41cb4071b7c9@amlogic.com> Message-ID: <1jse7u6n3q.fsf@starbuckisacylon.baylibre.com> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > From: Jian Hu > > l_detect controls the enable/disable of the PLL lock-detect module. > > For A9, the l_detect signal is active-high: > 0 -> Disable lock-detect module; > 1 -> Enable lock-detect module. > > Here, a flag CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH is added to handle cases > like A9, where the signal is active-high. > > Signed-off-by: Jian Hu > --- > drivers/clk/meson/clk-pll.c | 9 +++++++-- > drivers/clk/meson/clk-pll.h | 2 ++ > 2 files changed, 9 insertions(+), 2 deletions(-) > > diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c > index 1ea6579a760f..5a0bd75f85a9 100644 > --- a/drivers/clk/meson/clk-pll.c > +++ b/drivers/clk/meson/clk-pll.c > @@ -388,8 +388,13 @@ static int meson_clk_pll_enable(struct clk_hw *hw) > } > > if (MESON_PARM_APPLICABLE(&pll->l_detect)) { > - meson_parm_write(clk->map, &pll->l_detect, 1); > - meson_parm_write(clk->map, &pll->l_detect, 0); > + if (pll->flags & CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH) { > + meson_parm_write(clk->map, &pll->l_detect, 0); > + meson_parm_write(clk->map, &pll->l_detect, 1); > + } else { > + meson_parm_write(clk->map, &pll->l_detect, 1); > + meson_parm_write(clk->map, &pll->l_detect, 0); > + } I'm not a fan of this code duplication. Use the introduced CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH to compute the first value, then flip the bit. > } > > if (meson_clk_pll_wait_lock(hw)) > diff --git a/drivers/clk/meson/clk-pll.h b/drivers/clk/meson/clk-pll.h > index 949157fb7bf5..97b7c70376a3 100644 > --- a/drivers/clk/meson/clk-pll.h > +++ b/drivers/clk/meson/clk-pll.h > @@ -29,6 +29,8 @@ struct pll_mult_range { > > #define CLK_MESON_PLL_ROUND_CLOSEST BIT(0) > #define CLK_MESON_PLL_NOINIT_ENABLED BIT(1) > +/* l_detect signal is active-high */ > +#define CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH BIT(2) > > struct meson_clk_pll_data { > struct parm en; -- Jerome From jbrunet at baylibre.com Thu May 14 08:16:36 2026 From: jbrunet at baylibre.com (Jerome Brunet) Date: Thu, 14 May 2026 17:16:36 +0200 Subject: [PATCH 06/10] clk: amlogic: PLL reset signal supports active-low configuration In-Reply-To: <20260511-b4-a9_clk-v1-6-41cb4071b7c9@amlogic.com> (Jian Hu via's message of "Mon, 11 May 2026 20:47:28 +0800") References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-6-41cb4071b7c9@amlogic.com> Message-ID: <1jmry26my3.fsf@starbuckisacylon.baylibre.com> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > From: Jian Hu > > In the A9 design, the PLL reset signal is configured as active-low. > > Add the flag 'CLK_MESON_PLL_RST_N' to indicate that the PLL reset signal > is active-low. > > Signed-off-by: Jian Hu > --- > drivers/clk/meson/clk-pll.c | 42 +++++++++++++++++++++++++++++++----------- > drivers/clk/meson/clk-pll.h | 2 ++ > 2 files changed, 33 insertions(+), 11 deletions(-) > > diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c > index 5a0bd75f85a9..8568ad6ba7b6 100644 > --- a/drivers/clk/meson/clk-pll.c > +++ b/drivers/clk/meson/clk-pll.c > @@ -295,10 +295,14 @@ static int meson_clk_pll_is_enabled(struct clk_hw *hw) > { > struct clk_regmap *clk = to_clk_regmap(hw); > struct meson_clk_pll_data *pll = meson_clk_pll_data(clk); > + unsigned int rst; > > - if (MESON_PARM_APPLICABLE(&pll->rst) && > - meson_parm_read(clk->map, &pll->rst)) > - return 0; > + if (MESON_PARM_APPLICABLE(&pll->rst)) { > + rst = meson_parm_read(clk->map, &pll->rst); > + if ((rst && !(pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW)) || > + (!rst && (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW))) Again not a great usage of binary ops. What you've written above is the verbose version of a XOR. The code duplication remarks applies to the rest of the patch too > + return 0; > + } > > if (!meson_parm_read(clk->map, &pll->en) || > !meson_parm_read(clk->map, &pll->l)) > @@ -326,14 +330,22 @@ static int meson_clk_pll_init(struct clk_hw *hw) > return 0; > > if (pll->init_count) { > - if (MESON_PARM_APPLICABLE(&pll->rst)) > - meson_parm_write(clk->map, &pll->rst, 1); > + if (MESON_PARM_APPLICABLE(&pll->rst)) { > + if (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW) > + meson_parm_write(clk->map, &pll->rst, 0); > + else > + meson_parm_write(clk->map, &pll->rst, 1); > + } > > regmap_multi_reg_write(clk->map, pll->init_regs, > pll->init_count); > > - if (MESON_PARM_APPLICABLE(&pll->rst)) > - meson_parm_write(clk->map, &pll->rst, 0); > + if (MESON_PARM_APPLICABLE(&pll->rst)) { > + if (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW) > + meson_parm_write(clk->map, &pll->rst, 1); > + else > + meson_parm_write(clk->map, &pll->rst, 0); > + } > } > > return 0; > @@ -363,15 +375,23 @@ static int meson_clk_pll_enable(struct clk_hw *hw) > return 0; > > /* Make sure the pll is in reset */ > - if (MESON_PARM_APPLICABLE(&pll->rst)) > - meson_parm_write(clk->map, &pll->rst, 1); > + if (MESON_PARM_APPLICABLE(&pll->rst)) { > + if (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW) > + meson_parm_write(clk->map, &pll->rst, 0); > + else > + meson_parm_write(clk->map, &pll->rst, 1); > + } > > /* Enable the pll */ > meson_parm_write(clk->map, &pll->en, 1); > > /* Take the pll out reset */ > - if (MESON_PARM_APPLICABLE(&pll->rst)) > - meson_parm_write(clk->map, &pll->rst, 0); > + if (MESON_PARM_APPLICABLE(&pll->rst)) { > + if (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW) > + meson_parm_write(clk->map, &pll->rst, 1); > + else > + meson_parm_write(clk->map, &pll->rst, 0); > + } > > /* > * Compared with the previous SoCs, self-adaption current module > diff --git a/drivers/clk/meson/clk-pll.h b/drivers/clk/meson/clk-pll.h > index 97b7c70376a3..1be7e6e77631 100644 > --- a/drivers/clk/meson/clk-pll.h > +++ b/drivers/clk/meson/clk-pll.h > @@ -31,6 +31,8 @@ struct pll_mult_range { > #define CLK_MESON_PLL_NOINIT_ENABLED BIT(1) > /* l_detect signal is active-high */ > #define CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH BIT(2) > +/* rst signal is active-low (Power-on reset) */ > +#define CLK_MESON_PLL_RST_ACTIVE_LOW BIT(3) > > struct meson_clk_pll_data { > struct parm en; -- Jerome From tanmays at amd.com Thu May 14 08:18:19 2026 From: tanmays at amd.com (Shah, Tanmay) Date: Thu, 14 May 2026 10:18:19 -0500 Subject: [PATCH] dt-bindings: Consolidate "sram" property definition In-Reply-To: <20260511165942.2774868-1-robh@kernel.org> References: <20260511165942.2774868-1-robh@kernel.org> Message-ID: Reviewed-by: Tanmay Shah On 5/11/2026 11:59 AM, Rob Herring (Arm) wrote: > The "sram" property has become a de facto standard property, so create a > common schema for it and drop all the duplicated definitions. > > Signed-off-by: Rob Herring (Arm) > --- > .../imx/fsl,imx8qxp-dc-command-sequencer.yaml | 2 +- > .../devicetree/bindings/display/msm/gpu.yaml | 6 +---- > .../bindings/dma/stericsson,dma40.yaml | 8 ++---- > .../bindings/media/cnm,wave521c.yaml | 2 +- > .../bindings/media/nxp,imx8-jpeg.yaml | 6 ++--- > .../bindings/media/rockchip,vdec.yaml | 5 ++-- > .../bindings/media/st,stm32-dcmi.yaml | 6 ++--- > .../devicetree/bindings/net/mediatek,net.yaml | 3 +-- > .../bindings/net/ti,icssg-prueth.yaml | 2 +- > .../bindings/net/ti,icssm-prueth.yaml | 2 +- > .../remoteproc/amlogic,meson-mx-ao-arc.yaml | 7 +---- > .../bindings/remoteproc/ti,k3-dsp-rproc.yaml | 8 ------ > .../bindings/remoteproc/ti,k3-r5f-rproc.yaml | 8 ------ > .../remoteproc/xlnx,zynqmp-r5fss.yaml | 9 +------ > .../devicetree/bindings/spi/st,stm32-spi.yaml | 10 +++---- > .../bindings/sram/sram-consumer.yaml | 26 +++++++++++++++++++ > 16 files changed, 48 insertions(+), 62 deletions(-) > create mode 100644 Documentation/devicetree/bindings/sram/sram-consumer.yaml > > diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml > index 27118f4c0d28..fd095e5742c5 100644 > --- a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml > +++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml > @@ -41,7 +41,7 @@ properties: > - const: sw3 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > + maxItems: 1 > description: phandle pointing to the mmio-sram device node > > required: > diff --git a/Documentation/devicetree/bindings/display/msm/gpu.yaml b/Documentation/devicetree/bindings/display/msm/gpu.yaml > index 04b2328903ca..358759fad8dc 100644 > --- a/Documentation/devicetree/bindings/display/msm/gpu.yaml > +++ b/Documentation/devicetree/bindings/display/msm/gpu.yaml > @@ -84,13 +84,9 @@ properties: > maxItems: 64 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle-array > minItems: 1 > maxItems: 4 > - items: > - maxItems: 1 > - description: | > - phandles to one or more reserved on-chip SRAM regions. > + description: > phandle to the On Chip Memory (OCMEM) that's present on some a3xx and > a4xx Snapdragon SoCs. See > Documentation/devicetree/bindings/sram/qcom,ocmem.yaml > diff --git a/Documentation/devicetree/bindings/dma/stericsson,dma40.yaml b/Documentation/devicetree/bindings/dma/stericsson,dma40.yaml > index 607da11e7baa..d8f92838f4c9 100644 > --- a/Documentation/devicetree/bindings/dma/stericsson,dma40.yaml > +++ b/Documentation/devicetree/bindings/dma/stericsson,dma40.yaml > @@ -136,13 +136,9 @@ properties: > maxItems: 1 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle-array > - description: A phandle array with inner size 1 (no arg cells). > - First phandle is the LCPA (Logical Channel Parameter Address) memory. > - Second phandle is the LCLA (Logical Channel Link base Address) memory. > - maxItems: 2 > items: > - maxItems: 1 > + - description: LCPA (Logical Channel Parameter Address) memory. > + - description: LCLA (Logical Channel Link base Address) memory. > > memcpy-channels: > $ref: /schemas/types.yaml#/definitions/uint32-array > diff --git a/Documentation/devicetree/bindings/media/cnm,wave521c.yaml b/Documentation/devicetree/bindings/media/cnm,wave521c.yaml > index 6a11c1d11fb5..6cd33dfd095d 100644 > --- a/Documentation/devicetree/bindings/media/cnm,wave521c.yaml > +++ b/Documentation/devicetree/bindings/media/cnm,wave521c.yaml > @@ -37,7 +37,7 @@ properties: > maxItems: 1 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > + maxItems: 1 > description: > The VPU uses the SRAM to store some of the reference data instead of > storing it on DMA memory. It is mainly used for the purpose of reducing > diff --git a/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml b/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml > index 18cc6315a821..6ba668aa633d 100644 > --- a/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml > +++ b/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml > @@ -56,10 +56,10 @@ properties: > maxItems: 5 # Wrapper and 4 slots > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > + maxItems: 1 > description: > - Optional phandle to a reserved on-chip SRAM regions. The SRAM can > - be used for descriptor storage, which may improve bus utilization. > + The SRAM can be used for descriptor storage, which may improve bus > + utilization. > > required: > - compatible > diff --git a/Documentation/devicetree/bindings/media/rockchip,vdec.yaml b/Documentation/devicetree/bindings/media/rockchip,vdec.yaml > index 42022401d0ff..4f38a0ef29d8 100644 > --- a/Documentation/devicetree/bindings/media/rockchip,vdec.yaml > +++ b/Documentation/devicetree/bindings/media/rockchip,vdec.yaml > @@ -91,9 +91,8 @@ properties: > maxItems: 1 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > - description: | > - phandle to a reserved on-chip SRAM regions. > + maxItems: 1 > + description: > Some SoCs, like rk3588 provide on-chip SRAM to store temporary > buffers during decoding. > > diff --git a/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml b/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml > index d9fbb90b0977..7c2ddd27780f 100644 > --- a/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml > +++ b/Documentation/devicetree/bindings/media/st,stm32-dcmi.yaml > @@ -47,10 +47,10 @@ properties: > maxItems: 1 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > + maxItems: 1 > description: > - phandle to a reserved SRAM region which is used as temporary > - storage memory between DMA and MDMA engines. > + SRAM region which is used as temporary storage memory between DMA and > + MDMA engines. > > port: > $ref: /schemas/graph.yaml#/$defs/port-base > diff --git a/Documentation/devicetree/bindings/net/mediatek,net.yaml b/Documentation/devicetree/bindings/net/mediatek,net.yaml > index cc346946291a..6bbd83c6aaf7 100644 > --- a/Documentation/devicetree/bindings/net/mediatek,net.yaml > +++ b/Documentation/devicetree/bindings/net/mediatek,net.yaml > @@ -67,8 +67,7 @@ properties: > - const: ppe > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > - description: phandle to mmio SRAM > + maxItems: 1 > > mediatek,ethsys: > $ref: /schemas/types.yaml#/definitions/phandle > diff --git a/Documentation/devicetree/bindings/net/ti,icssg-prueth.yaml b/Documentation/devicetree/bindings/net/ti,icssg-prueth.yaml > index c296e5711848..883033b19b8f 100644 > --- a/Documentation/devicetree/bindings/net/ti,icssg-prueth.yaml > +++ b/Documentation/devicetree/bindings/net/ti,icssg-prueth.yaml > @@ -21,7 +21,7 @@ properties: > - ti,am654-sr1-icssg-prueth # for AM65x SoC family, SR1.0 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > + maxItems: 1 > description: > phandle to MSMC SRAM node > > diff --git a/Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml b/Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml > index a98ad45ca66f..9370c43bc66a 100644 > --- a/Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml > +++ b/Documentation/devicetree/bindings/net/ti,icssm-prueth.yaml > @@ -24,7 +24,7 @@ properties: > - ti,am3359-prueth # for AM33x SoC family > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > + maxItems: 1 > description: > phandle to OCMC SRAM node > > diff --git a/Documentation/devicetree/bindings/remoteproc/amlogic,meson-mx-ao-arc.yaml b/Documentation/devicetree/bindings/remoteproc/amlogic,meson-mx-ao-arc.yaml > index 76e8ca44906a..3f710433e937 100644 > --- a/Documentation/devicetree/bindings/remoteproc/amlogic,meson-mx-ao-arc.yaml > +++ b/Documentation/devicetree/bindings/remoteproc/amlogic,meson-mx-ao-arc.yaml > @@ -48,12 +48,7 @@ properties: > minItems: 1 > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > - description: > - phandles to a reserved SRAM region which is used as the memory of > - the ARC core. The region should be defined as child nodes of the > - AHB SRAM node as per the generic bindings in > - Documentation/devicetree/bindings/sram/sram.yaml > + maxItems: 1 > > amlogic,secbus2: > $ref: /schemas/types.yaml#/definitions/phandle > diff --git a/Documentation/devicetree/bindings/remoteproc/ti,k3-dsp-rproc.yaml b/Documentation/devicetree/bindings/remoteproc/ti,k3-dsp-rproc.yaml > index b51bb863d759..8b1ed384ef22 100644 > --- a/Documentation/devicetree/bindings/remoteproc/ti,k3-dsp-rproc.yaml > +++ b/Documentation/devicetree/bindings/remoteproc/ti,k3-dsp-rproc.yaml > @@ -75,16 +75,8 @@ properties: > # -------------------- > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle-array > minItems: 1 > maxItems: 4 > - items: > - maxItems: 1 > - description: | > - phandles to one or more reserved on-chip SRAM regions. The regions > - should be defined as child nodes of the respective SRAM node, and > - should be defined as per the generic bindings in, > - Documentation/devicetree/bindings/sram/sram.yaml > > allOf: > - if: > diff --git a/Documentation/devicetree/bindings/remoteproc/ti,k3-r5f-rproc.yaml b/Documentation/devicetree/bindings/remoteproc/ti,k3-r5f-rproc.yaml > index 775e9b3a1938..14e6b2f817b3 100644 > --- a/Documentation/devicetree/bindings/remoteproc/ti,k3-r5f-rproc.yaml > +++ b/Documentation/devicetree/bindings/remoteproc/ti,k3-r5f-rproc.yaml > @@ -224,16 +224,8 @@ patternProperties: > at 0x0) or 0 (BTCM at 0x0), default value is 1 if omitted. > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle-array > minItems: 1 > maxItems: 4 > - items: > - maxItems: 1 > - description: | > - phandles to one or more reserved on-chip SRAM regions. The regions > - should be defined as child nodes of the respective SRAM node, and > - should be defined as per the generic bindings in, > - Documentation/devicetree/bindings/sram/sram.yaml > > required: > - compatible > diff --git a/Documentation/devicetree/bindings/remoteproc/xlnx,zynqmp-r5fss.yaml b/Documentation/devicetree/bindings/remoteproc/xlnx,zynqmp-r5fss.yaml > index ee63c03949c9..c7d5e58330d6 100644 > --- a/Documentation/devicetree/bindings/remoteproc/xlnx,zynqmp-r5fss.yaml > +++ b/Documentation/devicetree/bindings/remoteproc/xlnx,zynqmp-r5fss.yaml > @@ -106,20 +106,13 @@ patternProperties: > - const: rx > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle-array > minItems: 1 > maxItems: 8 > - items: > - maxItems: 1 > - description: | > + description: > phandles to one or more reserved on-chip SRAM regions. Other than TCM, > the RPU can execute instructions and access data from the OCM memory, > the main DDR memory, and other system memories. > > - The regions should be defined as child nodes of the respective SRAM > - node, and should be defined as per the generic bindings in > - Documentation/devicetree/bindings/sram/sram.yaml > - > memory-region: > description: | > List of phandles to the reserved memory regions associated with the > diff --git a/Documentation/devicetree/bindings/spi/st,stm32-spi.yaml b/Documentation/devicetree/bindings/spi/st,stm32-spi.yaml > index 472e92974714..6d7d595e4ab3 100644 > --- a/Documentation/devicetree/bindings/spi/st,stm32-spi.yaml > +++ b/Documentation/devicetree/bindings/spi/st,stm32-spi.yaml > @@ -89,12 +89,10 @@ properties: > - const: rxm2m > > sram: > - $ref: /schemas/types.yaml#/definitions/phandle > - description: | > - Phandles to a reserved SRAM region which is used as temporary > - storage memory between DMA and MDMA engines. > - The region should be defined as child node of the AHB SRAM node > - as per the generic bindings in Documentation/devicetree/bindings/sram/sram.yaml > + maxItems: 1 > + description: > + SRAM region which is used as temporary storage memory between DMA and > + MDMA engines. > > power-domains: > maxItems: 1 > diff --git a/Documentation/devicetree/bindings/sram/sram-consumer.yaml b/Documentation/devicetree/bindings/sram/sram-consumer.yaml > new file mode 100644 > index 000000000000..f00087bd2879 > --- /dev/null > +++ b/Documentation/devicetree/bindings/sram/sram-consumer.yaml > @@ -0,0 +1,26 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/sram/sram-consumer.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: SRAM Consumers > + > +maintainers: > + - Rob Herring > + > +select: true > + > +properties: > + sram: > + description: > + Phandles to one or more reserved on-chip SRAM regions. The regions > + should be defined as child nodes of the respective SRAM node, and > + should be defined as per the generic bindings in, > + Documentation/devicetree/bindings/sram/sram.yaml > + $ref: /schemas/types.yaml#/definitions/phandle-array > + items: > + maxItems: 1 > + > +additionalProperties: true > +... From jbrunet at baylibre.com Thu May 14 09:12:41 2026 From: jbrunet at baylibre.com (Jerome Brunet) Date: Thu, 14 May 2026 18:12:41 +0200 Subject: [PATCH 08/10] clk: amlogic: Add A9 PLL clock controller driver In-Reply-To: <20260511-b4-a9_clk-v1-8-41cb4071b7c9@amlogic.com> (Jian Hu via's message of "Mon, 11 May 2026 20:47:30 +0800") References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-8-41cb4071b7c9@amlogic.com> Message-ID: <1jh5oa6kcm.fsf@starbuckisacylon.baylibre.com> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > From: Jian Hu > > Add the PLL clock controller driver for the Amlogic A9 SoC family. > > Signed-off-by: Jian Hu > --- > drivers/clk/meson/Kconfig | 13 + > drivers/clk/meson/Makefile | 1 + > drivers/clk/meson/a9-pll.c | 831 +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 845 insertions(+) > > diff --git a/drivers/clk/meson/Kconfig b/drivers/clk/meson/Kconfig > index cf8cf3f9e4ee..3549e67d6988 100644 > --- a/drivers/clk/meson/Kconfig > +++ b/drivers/clk/meson/Kconfig > @@ -132,6 +132,19 @@ config COMMON_CLK_A1_PERIPHERALS > device, A1 SoC Family. Say Y if you want A1 Peripherals clock > controller to work. > > +config COMMON_CLK_A9_PLL > + tristate "Amlogic A9 SoC PLL controller support" > + depends on ARM64 > + default ARCH_MESON > + select COMMON_CLK_MESON_REGMAP > + select COMMON_CLK_MESON_CLKC_UTILS > + select COMMON_CLK_MESON_PLL > + imply COMMON_CLK_SCMI > + help > + Support for the PLL clock controller on Amlogic A311Y3 based > + device, AKA A9. PLLs are required by most peripheral to operate. > + Say Y if you want A9 PLL clock controller to work. > + > config COMMON_CLK_C3_PLL > tristate "Amlogic C3 PLL clock controller" > depends on ARM64 > diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile > index c6719694a242..77636033061f 100644 > --- a/drivers/clk/meson/Makefile > +++ b/drivers/clk/meson/Makefile > @@ -19,6 +19,7 @@ obj-$(CONFIG_COMMON_CLK_AXG) += axg.o axg-aoclk.o > obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o > obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o > obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o > +obj-$(CONFIG_COMMON_CLK_A9_PLL) += a9-pll.o > obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o > obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o > obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o > diff --git a/drivers/clk/meson/a9-pll.c b/drivers/clk/meson/a9-pll.c > new file mode 100644 > index 000000000000..84b591c3afff > --- /dev/null > +++ b/drivers/clk/meson/a9-pll.c > @@ -0,0 +1,831 @@ > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) > +/* > + * Copyright (C) 2026 Amlogic, Inc. All rights reserved > + */ > + > +#include > +#include > +#include > +#include "clk-regmap.h" > +#include "clk-pll.h" > +#include "meson-clkc-utils.h" > + > +#define GP0PLL_CTRL0 0x00 > +#define GP0PLL_CTRL1 0x04 > +#define GP0PLL_CTRL2 0x08 > +#define GP0PLL_CTRL3 0x0c > +#define GP0PLL_CTRL4 0x10 > + > +/* HIFI0 and HIFI1 share the same IP and register offset layout. */ > +#define HIFIPLL_CTRL0 0x00 > +#define HIFIPLL_CTRL1 0x04 > +#define HIFIPLL_CTRL2 0x08 > +#define HIFIPLL_CTRL3 0x0c > +#define HIFIPLL_CTRL4 0x10 > + > +/* MCLK0 and MCLK1 share the same IP and register offset layout. */ > +#define MCLKPLL_CTRL0 0x00 > +#define MCLKPLL_CTRL1 0x04 > +#define MCLKPLL_CTRL2 0x08 > +#define MCLKPLL_CTRL3 0x0c > +#define MCLKPLL_CTRL4 0x10 > + > +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata) \ > + MESON_COMP_SEL(a9_, _name, _reg, _shift, _mask, _pdata, NULL, 0, 0) > + > +#define A9_COMP_DIV(_name, _reg, _shift, _width) \ > + MESON_COMP_DIV(a9_, _name, _reg, _shift, _width, 0, CLK_SET_RATE_PARENT) > + > +#define A9_COMP_GATE(_name, _reg, _bit) \ > + MESON_COMP_GATE(a9_, _name, _reg, _bit, CLK_SET_RATE_PARENT) > + > +/* > + * Compared with previous SoC PLLs, the A9 PLL input path has an inherent > + * 2-divider. The N pre-divider follows the same calculation rule as OD, > + * where the pre-divider ratio equals 2^N. > + * > + * A9 PLL is composed as follows: > + * > + * PLL > + * +---------------------------------+ > + * | | > + * | +--+ | > + * in/2 >>---[ /2^N ]-->| | +-----+ | > + * | | |------| DCO |----->> out > + * | +--------->| | +--v--+ | > + * | | +--+ | | > + * | | | | > + * | +--[ *(M + (F/Fmax) ]<--+ | > + * | | > + * +---------------------------------+ > + * > + * out = in / 2 * (m + frac / frac_max) / 2^n > + */ > + > +static struct clk_fixed_factor a9_gp0_in_div2_div = { > + .mult = 1, > + .div = 2, > + .hw.init = &(struct clk_init_data){ > + .name = "gp0_in_div2_div", > + .ops = &clk_fixed_factor_ops, > + .parent_data = &(const struct clk_parent_data) { > + .fw_name = "in0", > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_gp0_in_div2 = { > + .data = &(struct clk_regmap_gate_data) { > + .offset = GP0PLL_CTRL0, > + .bit_idx = 27, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "gp0_in_div2", > + .ops = &clk_regmap_gate_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_gp0_in_div2_div.hw > + }, > + .num_parents = 1, > + }, > +}; When document something, be sure it matches what you are doing afterward. It is confusing otherwise. Your comments above clearly miss this gate. A fixed 2 divider followed by a power of 2 divider ? Is it actually how the HW works or your modelisation power of 2 that's shifted by 1, mapping : * 0 -> 2 * 1 -> 4 * etc ... ? > + > +/* The output frequency range of the A9 PLL_DCO is 1.4 GHz to 2.8 GHz. */ > +static const struct pll_mult_range a9_pll_mult_range = { > + .min = 117, > + .max = 233, > +}; If PLL restriction is actually the DCO output rate, and only the reason to keep the pre-devider in the range above, I would definitely welcome a rework to express the constraints properly and split the pre-divider out. > + > +static const struct reg_sequence a9_gp0_pll_init_regs[] = { > + { .reg = GP0PLL_CTRL0, .def = 0x00010000 }, > + { .reg = GP0PLL_CTRL1, .def = 0x11480000 }, > + { .reg = GP0PLL_CTRL2, .def = 0x1219b010 }, > + { .reg = GP0PLL_CTRL3, .def = 0x00008010 } > +}; > + > +static struct clk_regmap a9_gp0_pll_dco = { > + .data = &(struct meson_clk_pll_data) { > + .en = { > + .reg_off = GP0PLL_CTRL0, > + .shift = 28, > + .width = 1, > + }, > + .m = { > + .reg_off = GP0PLL_CTRL0, > + .shift = 0, > + .width = 9, > + }, > + .n = { > + .reg_off = GP0PLL_CTRL0, > + .shift = 12, > + .width = 3, > + }, > + .frac = { > + .reg_off = GP0PLL_CTRL1, > + .shift = 0, > + .width = 17, > + }, > + .l = { > + .reg_off = GP0PLL_CTRL0, > + .shift = 31, > + .width = 1, > + }, > + .rst = { > + .reg_off = GP0PLL_CTRL0, > + .shift = 29, > + .width = 1, > + }, > + .l_detect = { > + .reg_off = GP0PLL_CTRL0, > + .shift = 30, > + .width = 1, > + }, > + .range = &a9_pll_mult_range, > + .init_regs = a9_gp0_pll_init_regs, > + .init_count = ARRAY_SIZE(a9_gp0_pll_init_regs), > + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | > + CLK_MESON_PLL_N_POWER_OF_TWO | > + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "gp0_pll_dco", > + .ops = &meson_clk_pll_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_gp0_in_div2.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +/* For gp0, hifi and mclk pll, the maximum value of od is 4. */ > +static const struct clk_div_table a9_pll_od_table[] = { > + { 0, 1 }, > + { 1, 2 }, > + { 2, 4 }, > + { 3, 8 }, > + { 4, 16 }, > + { /* sentinel */ } > +}; > + > +static struct clk_regmap a9_gp0_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = GP0PLL_CTRL0, > + .shift = 20, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "gp0_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_gp0_pll_dco.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static struct clk_fixed_factor a9_hifi0_in_div2_div = { > + .mult = 1, > + .div = 2, > + .hw.init = &(struct clk_init_data){ > + .name = "hifi0_in_div2_div", > + .ops = &clk_fixed_factor_ops, > + .parent_data = &(const struct clk_parent_data) { > + .fw_name = "in0", > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_hifi0_in_div2 = { > + .data = &(struct clk_regmap_gate_data) { > + .offset = HIFIPLL_CTRL0, > + .bit_idx = 27, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "hifi0_in_div2", > + .ops = &clk_regmap_gate_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_hifi0_in_div2_div.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static const struct reg_sequence a9_hifi0_pll_init_regs[] = { > + { .reg = HIFIPLL_CTRL0, .def = 0x00010000 }, > + { .reg = HIFIPLL_CTRL1, .def = 0x11480000 }, > + { .reg = HIFIPLL_CTRL2, .def = 0x1219b010 }, > + { .reg = HIFIPLL_CTRL3, .def = 0x00008010 } > +}; It look like GP0 and HIFI PLL are exactly the same IP, you've even documented it as such. Yet all the code is duplicated. That's not OK. I understand that way we statically declared the clocks so far pushed you in that direction. That's something I'd like to fix properly someday. In the meantime, you could at least duplicate the memory at runtime to avoid copy/pasting the code. A minor change to clkc utils as suggested at the end of this message could help you do so. Same probably applies to mclks. > + > +static struct clk_regmap a9_hifi0_pll_dco = { > + .data = &(struct meson_clk_pll_data) { > + .en = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 28, > + .width = 1, > + }, > + .m = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 0, > + .width = 9, > + }, > + .n = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 12, > + .width = 3, > + }, > + .frac = { > + .reg_off = HIFIPLL_CTRL1, > + .shift = 0, > + .width = 17, > + }, > + .l = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 31, > + .width = 1, > + }, > + .rst = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 29, > + .width = 1, > + }, > + .l_detect = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 30, > + .width = 1, > + }, > + .range = &a9_pll_mult_range, > + .init_regs = a9_hifi0_pll_init_regs, > + .init_count = ARRAY_SIZE(a9_hifi0_pll_init_regs), > + .frac_max = 100000, > + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | > + CLK_MESON_PLL_N_POWER_OF_TWO | > + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "hifi0_pll_dco", > + .ops = &meson_clk_pll_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_hifi0_in_div2.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_hifi0_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = HIFIPLL_CTRL0, > + .shift = 20, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "hifi0_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_hifi0_pll_dco.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static struct clk_fixed_factor a9_hifi1_in_div2_div = { > + .mult = 1, > + .div = 2, > + .hw.init = &(struct clk_init_data){ > + .name = "hifi1_in_div2_div", > + .ops = &clk_fixed_factor_ops, > + .parent_data = &(const struct clk_parent_data) { > + .fw_name = "in0", > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_hifi1_in_div2 = { > + .data = &(struct clk_regmap_gate_data) { > + .offset = HIFIPLL_CTRL0, > + .bit_idx = 27, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "hifi1_in_div2", > + .ops = &clk_regmap_gate_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_hifi1_in_div2_div.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static const struct reg_sequence a9_hifi1_pll_init_regs[] = { > + { .reg = HIFIPLL_CTRL0, .def = 0x00010000 }, > + { .reg = HIFIPLL_CTRL1, .def = 0x11480000 }, > + { .reg = HIFIPLL_CTRL2, .def = 0x1219b011 }, > + { .reg = HIFIPLL_CTRL3, .def = 0x00008010 } > +}; > + > +static struct clk_regmap a9_hifi1_pll_dco = { > + .data = &(struct meson_clk_pll_data) { > + .en = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 28, > + .width = 1, > + }, > + .m = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 0, > + .width = 9, > + }, > + .n = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 12, > + .width = 3, > + }, > + .frac = { > + .reg_off = HIFIPLL_CTRL1, > + .shift = 0, > + .width = 17, > + }, > + .l = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 31, > + .width = 1, > + }, > + .rst = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 29, > + .width = 1, > + }, > + .l_detect = { > + .reg_off = HIFIPLL_CTRL0, > + .shift = 30, > + .width = 1, > + }, > + .range = &a9_pll_mult_range, > + .init_regs = a9_hifi1_pll_init_regs, > + .init_count = ARRAY_SIZE(a9_hifi1_pll_init_regs), > + .frac_max = 100000, > + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | > + CLK_MESON_PLL_N_POWER_OF_TWO | > + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "hifi1_pll_dco", > + .ops = &meson_clk_pll_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_hifi1_in_div2.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_hifi1_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = HIFIPLL_CTRL0, > + .shift = 20, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "hifi1_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_hifi1_pll_dco.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +/* > + * Unlike GP0 and HIFI PLLs, the input divider 2 of MCLK PLL is > + * enabled by default and has no enable control bit. > + */ > +static struct clk_fixed_factor a9_mclk0_in_div2 = { > + .mult = 1, > + .div = 2, > + .hw.init = &(struct clk_init_data){ > + .name = "mclk0_in_div2_div", > + .ops = &clk_fixed_factor_ops, > + .parent_data = &(const struct clk_parent_data) { > + .fw_name = "in0", > + }, > + .num_parents = 1, > + }, > +}; > + > +static const struct reg_sequence a9_mclk0_pll_init_regs[] = { > + { .reg = MCLKPLL_CTRL1, .def = 0x00422000 }, > + { .reg = MCLKPLL_CTRL2, .def = 0x60000100 }, > + { .reg = MCLKPLL_CTRL3, .def = 0x02000200 }, > + { .reg = MCLKPLL_CTRL4, .def = 0xd616d616 } > +}; > + > +static struct clk_regmap a9_mclk0_pll_dco = { > + .data = &(struct meson_clk_pll_data) { > + .en = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 28, > + .width = 1, > + }, > + .m = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 0, > + .width = 9, > + }, > + .n = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 12, > + .width = 3, > + }, > + .l = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 31, > + .width = 1, > + }, > + .rst = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 29, > + .width = 1, > + }, > + .l_detect = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 30, > + .width = 1, > + }, > + .range = &a9_pll_mult_range, > + .init_regs = a9_mclk0_pll_init_regs, > + .init_count = ARRAY_SIZE(a9_mclk0_pll_init_regs), > + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | > + CLK_MESON_PLL_N_POWER_OF_TWO | > + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk0_pll_dco", > + .ops = &meson_clk_pll_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk0_in_div2.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk0_0_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 0, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk0_0_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk0_pll_dco.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk0_0_pre = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 3, > + .width = 5, > + .flags = CLK_DIVIDER_MAX_AT_ZERO, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk0_0_pre", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk0_0_pll.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static const struct clk_parent_data a9_mclk0_0_parents[] = { > + { .hw = &a9_mclk0_0_pre.hw }, > + { .fw_name = "in0" }, > + { .fw_name = "in1" }, > + { .fw_name = "in2" } > +}; > + > +static A9_COMP_SEL(mclk0_0, MCLKPLL_CTRL3, 12, 0x3, a9_mclk0_0_parents); > +static A9_COMP_DIV(mclk0_0, MCLKPLL_CTRL3, 10, 1); > +static A9_COMP_GATE(mclk0_0, MCLKPLL_CTRL3, 8); > + > +static struct clk_regmap a9_mclk0_1_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 16, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk0_1_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk0_pll_dco.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk0_1_pre = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 19, > + .width = 5, > + .flags = CLK_DIVIDER_MAX_AT_ZERO, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk0_1_pre", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk0_1_pll.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static const struct clk_parent_data a9_mclk0_1_parents[] = { > + { .hw = &a9_mclk0_1_pre.hw }, > + { .fw_name = "in0" }, > + { .fw_name = "in1" }, > + { .fw_name = "in2" } > +}; > + > +static A9_COMP_SEL(mclk0_1, MCLKPLL_CTRL3, 28, 0x3, a9_mclk0_1_parents); > +static A9_COMP_DIV(mclk0_1, MCLKPLL_CTRL3, 26, 1); > +static A9_COMP_GATE(mclk0_1, MCLKPLL_CTRL3, 24); > + > +static struct clk_fixed_factor a9_mclk1_in_div2 = { > + .mult = 1, > + .div = 2, > + .hw.init = &(struct clk_init_data){ > + .name = "mclk1_in_div2", > + .ops = &clk_fixed_factor_ops, > + .parent_data = &(const struct clk_parent_data) { > + .fw_name = "in0", > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk1_pll_dco = { > + .data = &(struct meson_clk_pll_data) { > + .en = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 28, > + .width = 1, > + }, > + .m = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 0, > + .width = 9, > + }, > + .n = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 12, > + .width = 3, > + }, > + .l = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 31, > + .width = 1, > + }, > + .rst = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 29, > + .width = 1, > + }, > + .l_detect = { > + .reg_off = MCLKPLL_CTRL0, > + .shift = 30, > + .width = 1, > + }, > + .range = &a9_pll_mult_range, > + .init_regs = a9_mclk0_pll_init_regs, > + .init_count = ARRAY_SIZE(a9_mclk0_pll_init_regs), > + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | > + CLK_MESON_PLL_N_POWER_OF_TWO | > + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk1_pll_dco", > + .ops = &meson_clk_pll_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk1_in_div2.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk1_0_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 0, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk1_0_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk1_pll_dco.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk1_0_pre = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 3, > + .width = 5, > + .flags = CLK_DIVIDER_MAX_AT_ZERO, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk1_0_pre", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk1_0_pll.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static const struct clk_parent_data a9_mclk1_0_parents[] = { > + { .hw = &a9_mclk1_0_pre.hw }, > + { .fw_name = "in0" }, > + { .fw_name = "in1" }, > + { .fw_name = "in2" } > +}; > + > +static A9_COMP_SEL(mclk1_0, MCLKPLL_CTRL3, 12, 0x3, a9_mclk1_0_parents); > +static A9_COMP_DIV(mclk1_0, MCLKPLL_CTRL3, 10, 1); > +static A9_COMP_GATE(mclk1_0, MCLKPLL_CTRL3, 8); > + > +static struct clk_regmap a9_mclk1_1_pll = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 16, > + .width = 3, > + .table = a9_pll_od_table, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk1_1_pll", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk1_pll_dco.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_mclk1_1_pre = { > + .data = &(struct clk_regmap_div_data) { > + .offset = MCLKPLL_CTRL3, > + .shift = 19, > + .width = 5, > + .flags = CLK_DIVIDER_MAX_AT_ZERO, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "mclk1_1_pre", > + .ops = &clk_regmap_divider_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_mclk1_1_pll.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static const struct clk_parent_data a9_mclk1_1_parents[] = { > + { .hw = &a9_mclk1_1_pre.hw }, > + { .fw_name = "in0" }, > + { .fw_name = "in1" }, > + { .fw_name = "in2" } > +}; > + > +static A9_COMP_SEL(mclk1_1, MCLKPLL_CTRL3, 28, 0x3, a9_mclk1_1_parents); > +static A9_COMP_DIV(mclk1_1, MCLKPLL_CTRL3, 26, 1); > +static A9_COMP_GATE(mclk1_1, MCLKPLL_CTRL3, 24); > + > +static struct clk_hw *a9_gp0_hw_clks[] = { > + [CLKID_GP0_IN_DIV2_DIV] = &a9_gp0_in_div2_div.hw, > + [CLKID_GP0_IN_DIV2] = &a9_gp0_in_div2.hw, > + [CLKID_GP0_PLL_DCO] = &a9_gp0_pll_dco.hw, > + [CLKID_GP0_PLL] = &a9_gp0_pll.hw, > +}; > + > +static struct clk_hw *a9_hifi0_hw_clks[] = { > + [CLKID_HIFI0_IN_DIV2_DIV] = &a9_hifi0_in_div2_div.hw, > + [CLKID_HIFI0_IN_DIV2] = &a9_hifi0_in_div2.hw, > + [CLKID_HIFI0_PLL_DCO] = &a9_hifi0_pll_dco.hw, > + [CLKID_HIFI0_PLL] = &a9_hifi0_pll.hw, > +}; > + > +static struct clk_hw *a9_hifi1_hw_clks[] = { > + [CLKID_HIFI1_IN_DIV2_DIV] = &a9_hifi1_in_div2_div.hw, > + [CLKID_HIFI1_IN_DIV2] = &a9_hifi1_in_div2.hw, > + [CLKID_HIFI1_PLL_DCO] = &a9_hifi1_pll_dco.hw, > + [CLKID_HIFI1_PLL] = &a9_hifi1_pll.hw, > +}; > + > +static struct clk_hw *a9_mclk0_hw_clks[] = { > + [CLKID_MCLK0_IN_DIV2] = &a9_mclk0_in_div2.hw, > + [CLKID_MCLK0_PLL_DCO] = &a9_mclk0_pll_dco.hw, > + [CLKID_MCLK0_0_PLL] = &a9_mclk0_0_pll.hw, > + [CLKID_MCLK0_0_PRE] = &a9_mclk0_0_pre.hw, > + [CLKID_MCLK0_0_SEL] = &a9_mclk0_0_sel.hw, > + [CLKID_MCLK0_0_DIV] = &a9_mclk0_0_div.hw, > + [CLKID_MCLK0_0] = &a9_mclk0_0.hw, > + [CLKID_MCLK0_1_PLL] = &a9_mclk0_1_pll.hw, > + [CLKID_MCLK0_1_PRE] = &a9_mclk0_1_pre.hw, > + [CLKID_MCLK0_1_SEL] = &a9_mclk0_1_sel.hw, > + [CLKID_MCLK0_1_DIV] = &a9_mclk0_1_div.hw, > + [CLKID_MCLK0_1] = &a9_mclk0_1.hw, > +}; > + > +static struct clk_hw *a9_mclk1_hw_clks[] = { > + [CLKID_MCLK1_IN_DIV2] = &a9_mclk1_in_div2.hw, > + [CLKID_MCLK1_PLL_DCO] = &a9_mclk1_pll_dco.hw, > + [CLKID_MCLK1_0_PLL] = &a9_mclk1_0_pll.hw, > + [CLKID_MCLK1_0_PRE] = &a9_mclk1_0_pre.hw, > + [CLKID_MCLK1_0_SEL] = &a9_mclk1_0_sel.hw, > + [CLKID_MCLK1_0_DIV] = &a9_mclk1_0_div.hw, > + [CLKID_MCLK1_0] = &a9_mclk1_0.hw, > + [CLKID_MCLK1_1_PLL] = &a9_mclk1_1_pll.hw, > + [CLKID_MCLK1_1_PRE] = &a9_mclk1_1_pre.hw, > + [CLKID_MCLK1_1_SEL] = &a9_mclk1_1_sel.hw, > + [CLKID_MCLK1_1_DIV] = &a9_mclk1_1_div.hw, > + [CLKID_MCLK1_1] = &a9_mclk1_1.hw, > +}; > + > +static const struct meson_clkc_data a9_gp0_data = { > + .hw_clks = { > + .hws = a9_gp0_hw_clks, > + .num = ARRAY_SIZE(a9_gp0_hw_clks), > + }, > +}; > + > +static const struct meson_clkc_data a9_hifi0_data = { > + .hw_clks = { > + .hws = a9_hifi0_hw_clks, > + .num = ARRAY_SIZE(a9_hifi0_hw_clks), > + }, > +}; > + > +static const struct meson_clkc_data a9_hifi1_data = { > + .hw_clks = { > + .hws = a9_hifi1_hw_clks, > + .num = ARRAY_SIZE(a9_hifi1_hw_clks), > + }, > +}; > + > +static const struct meson_clkc_data a9_mclk0_data = { > + .hw_clks = { > + .hws = a9_mclk0_hw_clks, > + .num = ARRAY_SIZE(a9_mclk0_hw_clks), > + }, > +}; > + > +static const struct meson_clkc_data a9_mclk1_data = { > + .hw_clks = { > + .hws = a9_mclk1_hw_clks, > + .num = ARRAY_SIZE(a9_mclk1_hw_clks), > + }, > +}; > + > +static const struct of_device_id a9_pll_clkc_match_table[] = { > + { .compatible = "amlogic,a9-gp0-pll", .data = &a9_gp0_data, }, > + { .compatible = "amlogic,a9-hifi0-pll", .data = &a9_hifi0_data, }, > + { .compatible = "amlogic,a9-hifi1-pll", .data = &a9_hifi1_data, }, > + { .compatible = "amlogic,a9-mclk0-pll", .data = &a9_mclk0_data, }, > + { .compatible = "amlogic,a9-mclk1-pll", .data = &a9_mclk1_data, }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, a9_pll_clkc_match_table); > + > +static struct platform_driver a9_pll_clkc_driver = { > + .probe = meson_clkc_mmio_probe, > + .driver = { > + .name = "a9-pll-clkc", > + .of_match_table = a9_pll_clkc_match_table, > + }, > +}; > +module_platform_driver(a9_pll_clkc_driver); > + > +MODULE_DESCRIPTION("Amlogic A9 PLL Clock Controller Driver"); > +MODULE_AUTHOR("Jian Hu "); > +MODULE_LICENSE("GPL"); > +MODULE_IMPORT_NS("CLK_MESON"); -- >8 -- diff --git a/drivers/clk/meson/meson-clkc-utils.c b/drivers/clk/meson/meson-clkc-utils.c index 870f50548e26..f95a0d9212fa 100644 --- a/drivers/clk/meson/meson-clkc-utils.c +++ b/drivers/clk/meson/meson-clkc-utils.c @@ -26,16 +26,12 @@ struct clk_hw *meson_clk_hw_get(struct of_phandle_args *clkspec, void *clk_hw_da } EXPORT_SYMBOL_NS_GPL(meson_clk_hw_get, "CLK_MESON"); -static int meson_clkc_init(struct device *dev, struct regmap *map) +static int meson_clkc_init(struct device *dev, struct regmap *map, + const struct meson_clkc_data *data) { - const struct meson_clkc_data *data; struct clk_hw *hw; int ret, i; - data = of_device_get_match_data(dev); - if (!data) - return -EINVAL; - if (data->init_count) regmap_multi_reg_write(map, data->init_regs, data->init_count); @@ -59,6 +55,7 @@ static int meson_clkc_init(struct device *dev, struct regmap *map) int meson_clkc_syscon_probe(struct platform_device *pdev) { + const struct meson_clkc_data *data; struct device *dev = &pdev->dev; struct device_node *np; struct regmap *map; @@ -71,7 +68,11 @@ int meson_clkc_syscon_probe(struct platform_device *pdev) return PTR_ERR(map); } - return meson_clkc_init(dev, map); + data = of_device_get_match_data(dev); + if (!data) + return -EINVAL; + + return meson_clkc_init(dev, map, data); } EXPORT_SYMBOL_NS_GPL(meson_clkc_syscon_probe, "CLK_MESON"); @@ -102,7 +103,7 @@ int meson_clkc_mmio_probe(struct platform_device *pdev) if (IS_ERR(map)) return PTR_ERR(map); - return meson_clkc_init(dev, map); + return meson_clkc_init(dev, map, data); } EXPORT_SYMBOL_NS_GPL(meson_clkc_mmio_probe, "CLK_MESON"); From jbrunet at baylibre.com Thu May 14 09:15:58 2026 From: jbrunet at baylibre.com (Jerome Brunet) Date: Thu, 14 May 2026 18:15:58 +0200 Subject: [PATCH 03/10] dt-bindings: clock: Add Amlogic A9 peripherals clock controller In-Reply-To: <20260511-b4-a9_clk-v1-3-41cb4071b7c9@amlogic.com> (Jian Hu via's message of "Mon, 11 May 2026 20:47:25 +0800") References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-3-41cb4071b7c9@amlogic.com> Message-ID: <1jbjei6k75.fsf@starbuckisacylon.baylibre.com> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > From: Jian Hu > > Add the peripherals clock controller dt-bindings for the Amlogic A9 > SoC family. > > Signed-off-by: Jian Hu > --- > .../clock/amlogic,a9-peripherals-clkc.yaml | 150 +++++++++ > .../clock/amlogic,a9-peripherals-clkc.h | 352 +++++++++++++++++++++ > 2 files changed, 502 insertions(+) > > diff --git > a/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml > b/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml > new file mode 100644 > index 000000000000..97e2c44d8630 > --- /dev/null > +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml > @@ -0,0 +1,150 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +# Copyright (C) 2026 Amlogic, Inc. All rights reserved > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/clock/amlogic,a9-peripherals-clkc.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Amlogic A9 Series Peripherals Clock Controller > + > +maintainers: > + - Neil Armstrong > + - Jerome Brunet > + - Jian Hu > + - Xianwei Zhao > + > +properties: > + compatible: > + const: amlogic,a9-peripherals-clkc > + > + reg: > + maxItems: 1 > + > + '#clock-cells': > + const: 1 > + > + clocks: > + minItems: 20 > + items: > + - description: input oscillator > + - description: input fclk div 2 > + - description: input fclk div 3 > + - description: input fclk div 4 > + - description: input fclk div 5 > + - description: input fclk div 7 > + - description: input fclk div 2p5 > + - description: input sys clk > + - description: input gp1 pll > + - description: input gp2 pll > + - description: input sys pll div 16 > + - description: input cpu clk div 16 > + - description: input a78 clk div 16 > + - description: input dsu clk div 16 > + - description: input rtc clk > + - description: input gp0 pll > + - description: input hifi0 pll > + - description: input hifi1 pll > + - description: input mclk0 pll > + - description: input mclk1 pll > + - description: input video1 pll (optional) > + - description: input video2 pll (optional) > + - description: input hdmi out2 clk (optional) > + - description: input hdmi pixel clk (optional) > + - description: input pixel0 pll (optional) > + - description: input pixel1 pll (optional) > + - description: input usb2 drd clk (optional) Why are those optional ? they seem internal to the SoC. If so, they don't have a reason to be optional > + - description: external input rmii oscillator (optional) > + > + clock-names: > + minItems: 20 > + items: > + - const: xtal > + - const: fdiv2 > + - const: fdiv3 > + - const: fdiv4 > + - const: fdiv5 > + - const: fdiv7 > + - const: fdiv2p5 > + - const: sys > + - const: gp1 > + - const: gp2 > + - const: sysplldiv16 > + - const: cpudiv16 > + - const: a78div16 > + - const: dsudiv16 > + - const: rtc > + - const: gp0 > + - const: hifi0 > + - const: hifi1 > + - const: mclk0 > + - const: mclk1 > + - const: vid1 > + - const: vid2 > + - const: hdmiout2 > + - const: hdmipix > + - const: pix0 > + - const: pix1 > + - const: u2drd > + - const: ext_rmii > + > +required: > + - compatible > + - reg > + - '#clock-cells' > + - clocks > + - clock-names > + > +additionalProperties: false > + > +examples: > + - | > + apb4 { > + #address-cells = <2>; > + #size-cells = <2>; > + > + clock-controller at 200 { > + compatible = "amlogic,a9-peripherals-clkc"; > + reg = <0x0 0x200 0x0 0x2f8>; > + #clock-cells = <1>; > + clocks = <&xtal>, > + <&scmi_clk 10>, > + <&scmi_clk 12>, > + <&scmi_clk 14>, > + <&scmi_clk 16>, > + <&scmi_clk 18>, > + <&scmi_clk 20>, > + <&scmi_clk 21>, > + <&scmi_clk 33>, > + <&scmi_clk 34>, > + <&scmi_clk 35>, > + <&scmi_clk 36>, > + <&scmi_clk 37>, > + <&scmi_clk 38>, > + <&scmi_clk 40>, > + <&gp0 3>, > + <&hifi0 3>, > + <&hifi1 3>, > + <&mclk0 3>, > + <&mclk1 3>; > + clock-names = "xtal", > + "fdiv2", > + "fdiv3", > + "fdiv4", > + "fdiv5", > + "fdiv7", > + "fdiv2p5", > + "sys", > + "gp1", > + "gp2", > + "sysplldiv16", > + "cpudiv16", > + "a78div16", > + "dsudiv16", > + "rtc", > + "gp0", > + "hifi0", > + "hifi1", > + "mclk0", > + "mclk1"; > + }; > + }; > diff --git a/include/dt-bindings/clock/amlogic,a9-peripherals-clkc.h b/include/dt-bindings/clock/amlogic,a9-peripherals-clkc.h > new file mode 100644 > index 000000000000..bca69771d728 > --- /dev/null > +++ b/include/dt-bindings/clock/amlogic,a9-peripherals-clkc.h > @@ -0,0 +1,352 @@ > +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ > +/* > + * Copyright (C) 2026 Amlogic, Inc. All rights reserved. > + */ > + > +#ifndef __AMLOGIC_A9_PERIPHERALS_CLKC_H > +#define __AMLOGIC_A9_PERIPHERALS_CLKC_H > + > +#define CLKID_SYS_AM_AXI 0 > +#define CLKID_SYS_DOS 1 > +#define CLKID_SYS_MIPI_DSI 2 > +#define CLKID_SYS_ETH_PHY 3 > +#define CLKID_SYS_AMFC 4 > +#define CLKID_SYS_MALI 5 > +#define CLKID_SYS_NNA 6 > +#define CLKID_SYS_ETH_AXI 7 > +#define CLKID_SYS_DP_APB 8 > +#define CLKID_SYS_EDPTX_APB 9 > +#define CLKID_SYS_U3HSG 10 > +#define CLKID_SYS_AUCPU 11 > +#define CLKID_SYS_GLB 12 > +#define CLKID_SYS_COMBO_DPHY_APB 13 > +#define CLKID_SYS_HDMIRX_APB 14 > +#define CLKID_SYS_HDMIRX_PCLK 15 > +#define CLKID_SYS_MIPI_DSI_PHY 16 > +#define CLKID_SYS_CAN0 17 > +#define CLKID_SYS_CAN1 18 > +#define CLKID_SYS_SD_EMMC_A 19 > +#define CLKID_SYS_SD_EMMC_B 20 > +#define CLKID_SYS_SD_EMMC_C 21 > +#define CLKID_SYS_SC 22 > +#define CLKID_SYS_ACODEC 23 > +#define CLKID_SYS_MIPI_ISP 24 > +#define CLKID_SYS_MSR 25 > +#define CLKID_SYS_AUDIO 26 > +#define CLKID_SYS_MIPI_DSI_B 27 > +#define CLKID_SYS_MIPI_DSI1_PHY 28 > +#define CLKID_SYS_ETH 29 > +#define CLKID_SYS_ETH_1G_MAC 30 > +#define CLKID_SYS_UART_A 31 > +#define CLKID_SYS_UART_F 32 > +#define CLKID_SYS_TS_A55 33 > +#define CLKID_SYS_ETH_1G_AXI 34 > +#define CLKID_SYS_TS_DOS 35 > +#define CLKID_SYS_U3DRD_B 36 > +#define CLKID_SYS_TS_CORE 37 > +#define CLKID_SYS_TS_PLL 38 > +#define CLKID_SYS_CSI_DIG_CLKIN 39 > +#define CLKID_SYS_CVE 40 > +#define CLKID_SYS_GE2D 41 > +#define CLKID_SYS_SPISG 42 > +#define CLKID_SYS_U3DRD_1 43 > +#define CLKID_SYS_U2H 44 > +#define CLKID_SYS_PCIE_MAC_A 45 > +#define CLKID_SYS_U3DRD_A 46 > +#define CLKID_SYS_U2DRD 47 > +#define CLKID_SYS_PCIE_PHY 48 > +#define CLKID_SYS_PCIE_MAC_B 49 > +#define CLKID_SYS_PERIPH 50 > +#define CLKID_SYS_PIO 51 > +#define CLKID_SYS_I3C 52 > +#define CLKID_SYS_I2C_M_E 53 > +#define CLKID_SYS_I2C_M_F 54 > +#define CLKID_SYS_HDMITX_APB 55 > +#define CLKID_SYS_I2C_M_I 56 > +#define CLKID_SYS_I2C_M_G 57 > +#define CLKID_SYS_I2C_M_H 58 > +#define CLKID_SYS_HDMI20_AES 59 > +#define CLKID_SYS_CSI2_HOST 60 > +#define CLKID_SYS_CSI2_ADAPT 61 > +#define CLKID_SYS_DSPA 62 > +#define CLKID_SYS_PP_DMA 63 > +#define CLKID_SYS_PP_WRAPPER 64 > +#define CLKID_SYS_VPU_INTR 65 > +#define CLKID_SYS_CSI2_PHY 66 > +#define CLKID_SYS_SARADC 67 > +#define CLKID_SYS_PWM_J 68 > +#define CLKID_SYS_PWM_I 69 > +#define CLKID_SYS_PWM_H 70 > +#define CLKID_SYS_PWM_N 71 > +#define CLKID_SYS_PWM_M 72 > +#define CLKID_SYS_PWM_L 73 > +#define CLKID_SYS_PWM_K 74 > +#define CLKID_SD_EMMC_A_SEL 75 > +#define CLKID_SD_EMMC_A_DIV 76 > +#define CLKID_SD_EMMC_A 77 > +#define CLKID_SD_EMMC_B_SEL 78 > +#define CLKID_SD_EMMC_B_DIV 79 > +#define CLKID_SD_EMMC_B 80 > +#define CLKID_SD_EMMC_C_SEL 81 > +#define CLKID_SD_EMMC_C_DIV 82 > +#define CLKID_SD_EMMC_C 83 > +#define CLKID_PWM_H_SEL 84 > +#define CLKID_PWM_H_DIV 85 > +#define CLKID_PWM_H 86 > +#define CLKID_PWM_I_SEL 87 > +#define CLKID_PWM_I_DIV 88 > +#define CLKID_PWM_I 89 > +#define CLKID_PWM_J_SEL 90 > +#define CLKID_PWM_J_DIV 91 > +#define CLKID_PWM_J 92 > +#define CLKID_PWM_K_SEL 93 > +#define CLKID_PWM_K_DIV 94 > +#define CLKID_PWM_K 95 > +#define CLKID_PWM_L_SEL 96 > +#define CLKID_PWM_L_DIV 97 > +#define CLKID_PWM_L 98 > +#define CLKID_PWM_M_SEL 99 > +#define CLKID_PWM_M_DIV 100 > +#define CLKID_PWM_M 101 > +#define CLKID_PWM_N_SEL 102 > +#define CLKID_PWM_N_DIV 103 > +#define CLKID_PWM_N 104 > +#define CLKID_SPISG_SEL 105 > +#define CLKID_SPISG_DIV 106 > +#define CLKID_SPISG 107 > +#define CLKID_SPISG1_SEL 108 > +#define CLKID_SPISG1_DIV 109 > +#define CLKID_SPISG1 110 > +#define CLKID_SPISG2_SEL 111 > +#define CLKID_SPISG2_DIV 112 > +#define CLKID_SPISG2 113 > +#define CLKID_SARADC_SEL 114 > +#define CLKID_SARADC_DIV 115 > +#define CLKID_SARADC 116 > +#define CLKID_AMFC_SEL 117 > +#define CLKID_AMFC_DIV 118 > +#define CLKID_AMFC 119 > +#define CLKID_NNA_SEL 120 > +#define CLKID_NNA_DIV 121 > +#define CLKID_NNA 122 > +#define CLKID_USB_250M_SEL 123 > +#define CLKID_USB_250M_DIV 124 > +#define CLKID_USB_250M 125 > +#define CLKID_USB_48M_PRE_SEL 126 > +#define CLKID_USB_48M_PRE_DIV 127 > +#define CLKID_USB_48M_PRE 128 > +#define CLKID_PCIE_TL_SEL 129 > +#define CLKID_PCIE_TL_DIV 130 > +#define CLKID_PCIE_TL 131 > +#define CLKID_PCIE1_TL_SEL 132 > +#define CLKID_PCIE1_TL_DIV 133 > +#define CLKID_PCIE1_TL 134 > +#define CLKID_CMPR_SEL 135 > +#define CLKID_CMPR_DIV 136 > +#define CLKID_CMPR 137 > +#define CLKID_DEWARPA_SEL 138 > +#define CLKID_DEWARPA_DIV 139 > +#define CLKID_DEWARPA 140 > +#define CLKID_SC_PRE_SEL 141 > +#define CLKID_SC_PRE_DIV 142 > +#define CLKID_SC_PRE 143 > +#define CLKID_SC 144 > +#define CLKID_DPTX_APB2_SEL 145 > +#define CLKID_DPTX_APB2_DIV 146 > +#define CLKID_DPTX_APB2 147 > +#define CLKID_DPTX_AUD_SEL 148 > +#define CLKID_DPTX_AUD_DIV 149 > +#define CLKID_DPTX_AUD 150 > +#define CLKID_ISP_SEL 151 > +#define CLKID_ISP_DIV 152 > +#define CLKID_ISP 153 > +#define CLKID_CVE_SEL 154 > +#define CLKID_CVE_DIV 155 > +#define CLKID_CVE 156 > +#define CLKID_VGE_SEL 157 > +#define CLKID_VGE_DIV 158 > +#define CLKID_VGE 159 > +#define CLKID_PP_SEL 160 > +#define CLKID_PP_DIV 161 > +#define CLKID_PP 162 > +#define CLKID_GLB_SEL 163 > +#define CLKID_GLB_DIV 164 > +#define CLKID_GLB 165 > +#define CLKID_USB_48M_DUALDIV_IN 166 > +#define CLKID_USB_48M_DUALDIV_DIV 167 > +#define CLKID_USB_48M_DUALDIV_SEL 168 > +#define CLKID_USB_48M_DUALDIV 169 > +#define CLKID_USB_48M 170 > +#define CLKID_CAN_PE_SEL 171 > +#define CLKID_CAN_PE_DIV 172 > +#define CLKID_CAN_PE 173 > +#define CLKID_CAN1_PE_SEL 174 > +#define CLKID_CAN1_PE_DIV 175 > +#define CLKID_CAN1_PE 176 > +#define CLKID_CAN_FILTER_SEL 177 > +#define CLKID_CAN_FILTER_DIV 178 > +#define CLKID_CAN_FILTER 179 > +#define CLKID_CAN1_FILTER_SEL 180 > +#define CLKID_CAN1_FILTER_DIV 181 > +#define CLKID_CAN1_FILTER 182 > +#define CLKID_I3C_SEL 183 > +#define CLKID_I3C_DIV 184 > +#define CLKID_I3C 185 > +#define CLKID_TS_DIV 186 > +#define CLKID_TS 187 > +#define CLKID_ETH_125M_DIV 188 > +#define CLKID_ETH_125M 189 > +#define CLKID_ETH_RMII_SEL 190 > +#define CLKID_ETH_RMII_DIV 191 > +#define CLKID_ETH_RMII 192 > +#define CLKID_GEN_SEL 193 > +#define CLKID_GEN_DIV 194 > +#define CLKID_GEN 195 > +#define CLKID_CLK24M_IN 196 > +#define CLKID_CLK12_24M 197 > +#define CLKID_MALI_0_SEL 198 > +#define CLKID_MALI_0_DIV 199 > +#define CLKID_MALI_0 200 > +#define CLKID_MALI_1_SEL 201 > +#define CLKID_MALI_1_DIV 202 > +#define CLKID_MALI_1 203 > +#define CLKID_MALI 204 > +#define CLKID_MALI_STACK_0_SEL 205 > +#define CLKID_MALI_STACK_0_DIV 206 > +#define CLKID_MALI_STACK_0 207 > +#define CLKID_MALI_STACK_1_SEL 208 > +#define CLKID_MALI_STACK_1_DIV 209 > +#define CLKID_MALI_STACK_1 210 > +#define CLKID_MALI_STACK 211 > +#define CLKID_DSPA_0_SEL 212 > +#define CLKID_DSPA_0_DIV 213 > +#define CLKID_DSPA_0 214 > +#define CLKID_DSPA_1_SEL 215 > +#define CLKID_DSPA_1_DIV 216 > +#define CLKID_DSPA_1 217 > +#define CLKID_DSPA 218 > +#define CLKID_HEVCF_0_SEL 219 > +#define CLKID_HEVCF_0_DIV 220 > +#define CLKID_HEVCF_0 221 > +#define CLKID_HEVCF_1_SEL 222 > +#define CLKID_HEVCF_1_DIV 223 > +#define CLKID_HEVCF_1 224 > +#define CLKID_HEVCF 225 > +#define CLKID_HCODEC_0_SEL 226 > +#define CLKID_HCODEC_0_DIV 227 > +#define CLKID_HCODEC_0 228 > +#define CLKID_HCODEC_1_SEL 229 > +#define CLKID_HCODEC_1_DIV 230 > +#define CLKID_HCODEC_1 231 > +#define CLKID_HCODEC 232 > +#define CLKID_VPU_0_SEL 233 > +#define CLKID_VPU_0_DIV 234 > +#define CLKID_VPU_0 235 > +#define CLKID_VPU_1_SEL 236 > +#define CLKID_VPU_1_DIV 237 > +#define CLKID_VPU_1 238 > +#define CLKID_VPU 239 > +#define CLKID_VAPB_0_SEL 240 > +#define CLKID_VAPB_0_DIV 241 > +#define CLKID_VAPB_0 242 > +#define CLKID_VAPB_1_SEL 243 > +#define CLKID_VAPB_1_DIV 244 > +#define CLKID_VAPB_1 245 > +#define CLKID_VAPB 246 > +#define CLKID_GE2D 247 > +#define CLKID_VPU_CLKB_TMP_SEL 248 > +#define CLKID_VPU_CLKB_TMP_DIV 249 > +#define CLKID_VPU_CLKB_TMP 250 > +#define CLKID_VPU_CLKB_DIV 251 > +#define CLKID_VPU_CLKB 252 > +#define CLKID_HDMITX_SYS_SEL 253 > +#define CLKID_HDMITX_SYS_DIV 254 > +#define CLKID_HDMITX_SYS 255 > +#define CLKID_HDMITX_PRIF_SEL 256 > +#define CLKID_HDMITX_PRIF_DIV 257 > +#define CLKID_HDMITX_PRIF 258 > +#define CLKID_HDMITX_200M_SEL 259 > +#define CLKID_HDMITX_200M_DIV 260 > +#define CLKID_HDMITX_200M 261 > +#define CLKID_HDMITX_AUD_SEL 262 > +#define CLKID_HDMITX_AUD_DIV 263 > +#define CLKID_HDMITX_AUD 264 > +#define CLKID_HDMIRX_5M_SEL 265 > +#define CLKID_HDMIRX_5M_DIV 266 > +#define CLKID_HDMIRX_5M 267 > +#define CLKID_HDMIRX_2M_SEL 268 > +#define CLKID_HDMIRX_2M_DIV 269 > +#define CLKID_HDMIRX_2M 270 > +#define CLKID_HDMIRX_CFG_SEL 271 > +#define CLKID_HDMIRX_CFG_DIV 272 > +#define CLKID_HDMIRX_CFG 273 > +#define CLKID_HDMIRX_HDCP2X_SEL 274 > +#define CLKID_HDMIRX_HDCP2X_DIV 275 > +#define CLKID_HDMIRX_HDCP2X 276 > +#define CLKID_HDMIRX_ACR_REF_SEL 277 > +#define CLKID_HDMIRX_ACR_REF_DIV 278 > +#define CLKID_HDMIRX_ACR_REF 279 > +#define CLKID_HDMIRX_METER_SEL 280 > +#define CLKID_HDMIRX_METER_DIV 281 > +#define CLKID_HDMIRX_METER 282 > +#define CLKID_VID_LOCK_SEL 283 > +#define CLKID_VID_LOCK_DIV 284 > +#define CLKID_VID_LOCK 285 > +#define CLKID_VDIN_MEAS_SEL 286 > +#define CLKID_VDIN_MEAS_DIV 287 > +#define CLKID_VDIN_MEAS 288 > +#define CLKID_VID_PLL_DIV 289 > +#define CLKID_VID_PLL_SEL 290 > +#define CLKID_VID_PLL 291 > +#define CLKID_VID_PLL_VCLK 292 > +#define CLKID_VCLK_SEL 293 > +#define CLKID_VCLK_IN 294 > +#define CLKID_VCLK_DIV 295 > +#define CLKID_VCLK 296 > +#define CLKID_VCLK_DIV1_EN 297 > +#define CLKID_VCLK_DIV2_EN 298 > +#define CLKID_VCLK_DIV2 299 > +#define CLKID_VCLK_DIV4_EN 300 > +#define CLKID_VCLK_DIV4 301 > +#define CLKID_VCLK_DIV6_EN 302 > +#define CLKID_VCLK_DIV6 303 > +#define CLKID_VCLK_DIV12_EN 304 > +#define CLKID_VCLK_DIV12 305 > +#define CLKID_VCLK2_SEL 306 > +#define CLKID_VCLK2_IN 307 > +#define CLKID_VCLK2_DIV 308 > +#define CLKID_VCLK2 309 > +#define CLKID_VCLK2_DIV1_EN 310 > +#define CLKID_VCLK2_DIV2_EN 311 > +#define CLKID_VCLK2_DIV2 312 > +#define CLKID_VCLK2_DIV4_EN 313 > +#define CLKID_VCLK2_DIV4 314 > +#define CLKID_VCLK2_DIV6_EN 315 > +#define CLKID_VCLK2_DIV6 316 > +#define CLKID_VCLK2_DIV12_EN 317 > +#define CLKID_VCLK2_DIV12 318 > +#define CLKID_VDAC_SEL 319 > +#define CLKID_VDAC 320 > +#define CLKID_ENC_SEL 321 > +#define CLKID_ENC 322 > +#define CLKID_ENC1_SEL 323 > +#define CLKID_ENC1 324 > +#define CLKID_HDMITX_PIXEL_SEL 325 > +#define CLKID_HDMITX_PIXEL 326 > +#define CLKID_HDMITX_FE_SEL 327 > +#define CLKID_HDMITX_FE 328 > +#define CLKID_HDMITX1_PIXEL_SEL 329 > +#define CLKID_HDMITX1_PIXEL 330 > +#define CLKID_HDMITX1_FE_SEL 331 > +#define CLKID_HDMITX1_FE 332 > +#define CLKID_CSI_PHY_SEL 333 > +#define CLKID_CSI_PHY_DIV 334 > +#define CLKID_CSI_PHY 335 > +#define CLKID_DSI_MEAS_SEL 336 > +#define CLKID_DSI_MEAS_DIV 337 > +#define CLKID_DSI_MEAS 338 > +#define CLKID_DSI_B_MEAS_SEL 339 > +#define CLKID_DSI_B_MEAS_DIV 340 > +#define CLKID_DSI_B_MEAS 341 > + > +#endif /* __AMLOGIC_A9_PERIPHERALS_CLKC_H */ -- Jerome From jbrunet at baylibre.com Thu May 14 09:27:42 2026 From: jbrunet at baylibre.com (Jerome Brunet) Date: Thu, 14 May 2026 18:27:42 +0200 Subject: [PATCH 10/10] clk: amlogic: Add A9 AO clock controller driver In-Reply-To: <20260511-b4-a9_clk-v1-10-41cb4071b7c9@amlogic.com> (Jian Hu via's message of "Mon, 11 May 2026 20:47:32 +0800") References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-10-41cb4071b7c9@amlogic.com> Message-ID: <1j33zu6jnl.fsf@starbuckisacylon.baylibre.com> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > From: Jian Hu > > Add the Always-on clock controller driver for the Amlogic A9 SoC family. > > Signed-off-by: Jian Hu > --- > drivers/clk/meson/Makefile | 2 +- > drivers/clk/meson/a9-aoclk.c | 494 +++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 495 insertions(+), 1 deletion(-) > > diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile > index 2b5b67b14efc..91af609ce815 100644 > --- a/drivers/clk/meson/Makefile > +++ b/drivers/clk/meson/Makefile > @@ -20,7 +20,7 @@ obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o > obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o > obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o > obj-$(CONFIG_COMMON_CLK_A9_PLL) += a9-pll.o > -obj-$(CONFIG_COMMON_CLK_A9_PERIPHERALS) += a9-peripherals.o > +obj-$(CONFIG_COMMON_CLK_A9_PERIPHERALS) += a9-peripherals.o a9-aoclk.o > obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o > obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o > obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o > diff --git a/drivers/clk/meson/a9-aoclk.c b/drivers/clk/meson/a9-aoclk.c > new file mode 100644 > index 000000000000..3c42eaf585d2 > --- /dev/null > +++ b/drivers/clk/meson/a9-aoclk.c > @@ -0,0 +1,494 @@ > +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) > +/* > + * Copyright (C) 2026 Amlogic, Inc. All rights reserved > + */ > + > +#include > +#include > +#include > +#include "clk-regmap.h" > +#include "clk-dualdiv.h" > +#include "meson-clkc-utils.h" > + > +#define AO_OSCIN_CTRL 0x00 > +#define AO_SYS_CLK0 0x04 > +#define AO_PWM_CLK_A_CTRL 0x1c > +#define AO_PWM_CLK_B_CTRL 0x20 > +#define AO_PWM_CLK_C_CTRL 0x24 > +#define AO_PWM_CLK_D_CTRL 0x28 > +#define AO_PWM_CLK_E_CTRL 0x2c > +#define AO_PWM_CLK_F_CTRL 0x30 > +#define AO_PWM_CLK_G_CTRL 0x34 > +#define AO_CEC_CTRL0 0x38 > +#define AO_CEC_CTRL1 0x3c > +#define AO_RTC_BY_OSCIN_CTRL0 0x50 > +#define AO_RTC_BY_OSCIN_CTRL1 0x54 > + > +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata) \ > + MESON_COMP_SEL(a9_, _name, _reg, _shift, _mask, _pdata, NULL, 0, 0) a9_ao_ ? > + > +#define A9_COMP_DIV(_name, _reg, _shift, _width) \ > + MESON_COMP_DIV(a9_, _name, _reg, _shift, _width, 0, CLK_SET_RATE_PARENT) > + > +#define A9_COMP_GATE(_name, _reg, _bit) \ > + MESON_COMP_GATE(a9_, _name, _reg, _bit, CLK_SET_RATE_PARENT) > + > +static struct clk_regmap a9_ao_xtal_in = { > + .data = &(struct clk_regmap_gate_data){ > + .offset = AO_OSCIN_CTRL, > + .bit_idx = 3, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "ao_xtal_in", > + .ops = &clk_regmap_gate_ops, > + .parent_data = &(const struct clk_parent_data) { > + .fw_name = "xtal", > + }, > + .num_parents = 1, > + /* > + * It may be ao_sys's parent clock, its child clocks mark > + * CLK_IS_CRITICAL, So mark CLK_IS_CRITICAL for it. > + */ > + .flags = CLK_IS_CRITICAL, > + }, > +}; > + > +static struct clk_regmap a9_ao_xtal = { > + .data = &(struct clk_regmap_mux_data) { > + .offset = AO_OSCIN_CTRL, > + .mask = 0x1, > + .shift = 0, > + }, > + .hw.init = &(struct clk_init_data){ > + .name = "ao_xtal", > + .ops = &clk_regmap_mux_ops, > + /* ext_32k is from external PAD, do not automatically reparent */ > + .parent_data = (const struct clk_parent_data []) { > + { .hw = &a9_ao_xtal_in.hw }, > + { .fw_name = "ext_32k", }, > + }, > + .num_parents = 2, > + .flags = CLK_SET_RATE_NO_REPARENT, > + }, > +}; > + > +static struct clk_regmap a9_ao_sys = { > + .data = &(struct clk_regmap_mux_data) { > + .offset = AO_OSCIN_CTRL, > + .mask = 0x1, > + .shift = 1, > + }, > + .hw.init = &(struct clk_init_data){ > + .name = "ao_sys", > + .ops = &clk_regmap_mux_ops, > + .parent_data = (const struct clk_parent_data []) { > + { .hw = &a9_ao_xtal.hw }, > + { .fw_name = "sys", }, > + }, > + .num_parents = 2, > + .flags = CLK_SET_PARENT_GATE, > + }, > +}; > + > +static const struct clk_parent_data a9_ao_pclk_parents = { .hw = &a9_ao_sys.hw }; > + > +#define A9_AO_PCLK(_name, _bit, _flags) \ > + MESON_PCLK(a9_ao_sys_##_name, AO_SYS_CLK0, _bit, \ > + &a9_ao_pclk_parents, _flags) > + > +/* > + * A9 integrates a low-power microprocessor (Always-on CPU: AOCPU). Some AO sys > + * clocks control the AOCPU modules. Mark the AOCPU-related clocks with > + * CLK_IS_CRITICAL to avoid them being disabled and impacting AOCPU functionality. > + * AOCPU-related clocks list: > + * - clktree > + * - rst_ctrl > + * - pad > + * - irq > + * - pwrctrl > + * - aocpu > + * - sram > + */ > +static A9_AO_PCLK(i2c3, 0, 0); > +static A9_AO_PCLK(rtc_reg, 1, 0); > +static A9_AO_PCLK(clktree, 2, CLK_IS_CRITICAL); > +static A9_AO_PCLK(rst_ctrl, 3, CLK_IS_CRITICAL); > +static A9_AO_PCLK(pad, 4, CLK_IS_CRITICAL); > +static A9_AO_PCLK(rtc_dig, 5, 0); > +static A9_AO_PCLK(irq, 6, CLK_IS_CRITICAL); > +static A9_AO_PCLK(pwrctrl, 7, CLK_IS_CRITICAL); > +static A9_AO_PCLK(pwm_a, 8, 0); > +static A9_AO_PCLK(pwm_b, 9, 0); > +static A9_AO_PCLK(pwm_c, 10, 0); > +static A9_AO_PCLK(pwm_d, 11, 0); > +static A9_AO_PCLK(pwm_e, 12, 0); > +static A9_AO_PCLK(pwm_f, 13, 0); > +static A9_AO_PCLK(pwm_g, 14, 0); > +static A9_AO_PCLK(i2c_a, 15, 0); > +static A9_AO_PCLK(i2c_b, 16, 0); > +static A9_AO_PCLK(i2c_c, 17, 0); > +static A9_AO_PCLK(i2c_d, 18, 0); > +static A9_AO_PCLK(sed, 19, 0); > +static A9_AO_PCLK(ir_ctrl, 20, 0); > +static A9_AO_PCLK(uart_b, 21, 0); > +static A9_AO_PCLK(uart_c, 22, 0); > +static A9_AO_PCLK(uart_d, 23, 0); > +static A9_AO_PCLK(uart_e, 24, 0); > +static A9_AO_PCLK(spisg_0, 25, 0); > +static A9_AO_PCLK(rtc_secure, 26, 0); > +static A9_AO_PCLK(cec, 27, 0); > +static A9_AO_PCLK(aocpu, 28, CLK_IS_CRITICAL); > +static A9_AO_PCLK(sram, 29, CLK_IS_CRITICAL); > +static A9_AO_PCLK(spisg_1, 30, 0); > +static A9_AO_PCLK(spisg_2, 31, 0); > + > +static const struct clk_parent_data a9_ao_pwm_parents[] = { > + { .hw = &a9_ao_xtal.hw }, > + { .fw_name = "fdiv5", }, > + { .fw_name = "fdiv4", }, > + { .fw_name = "fdiv3", } > +}; > + > +static A9_COMP_SEL(ao_pwm_a, AO_PWM_CLK_A_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_a, AO_PWM_CLK_A_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_a, AO_PWM_CLK_A_CTRL, 8); > + > +static A9_COMP_SEL(ao_pwm_b, AO_PWM_CLK_B_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_b, AO_PWM_CLK_B_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_b, AO_PWM_CLK_A_CTRL, 8); > + > +static A9_COMP_SEL(ao_pwm_c, AO_PWM_CLK_C_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_c, AO_PWM_CLK_C_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_c, AO_PWM_CLK_C_CTRL, 8); > + > +static A9_COMP_SEL(ao_pwm_d, AO_PWM_CLK_D_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_d, AO_PWM_CLK_D_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_d, AO_PWM_CLK_D_CTRL, 8); > + > +static A9_COMP_SEL(ao_pwm_e, AO_PWM_CLK_E_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_e, AO_PWM_CLK_E_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_e, AO_PWM_CLK_E_CTRL, 8); > + > +static A9_COMP_SEL(ao_pwm_f, AO_PWM_CLK_F_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_f, AO_PWM_CLK_F_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_f, AO_PWM_CLK_F_CTRL, 8); > + > +static A9_COMP_SEL(ao_pwm_g, AO_PWM_CLK_G_CTRL, 9, 0x7, a9_ao_pwm_parents); > +static A9_COMP_DIV(ao_pwm_g, AO_PWM_CLK_G_CTRL, 0, 8); > +static A9_COMP_GATE(ao_pwm_g, AO_PWM_CLK_G_CTRL, 8); > + > +static struct clk_regmap a9_ao_rtc_dualdiv_in = { > + .data = &(struct clk_regmap_gate_data){ > + .offset = AO_RTC_BY_OSCIN_CTRL0, > + .bit_idx = 31, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "ao_rtc_duandiv_in", > + .ops = &clk_regmap_gate_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_ao_xtal.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static const struct meson_clk_dualdiv_param a9_ao_dualdiv_table[] = { > + { 733, 732, 8, 11, 1 }, > + { /* sentinel */ } > +}; > + > +static struct clk_regmap a9_ao_rtc_dualdiv_div = { > + .data = &(struct meson_clk_dualdiv_data){ > + .n1 = { > + .reg_off = AO_RTC_BY_OSCIN_CTRL0, > + .shift = 0, > + .width = 12, > + }, > + .n2 = { > + .reg_off = AO_RTC_BY_OSCIN_CTRL0, > + .shift = 12, > + .width = 12, > + }, > + .m1 = { > + .reg_off = AO_RTC_BY_OSCIN_CTRL1, > + .shift = 0, > + .width = 12, > + }, > + .m2 = { > + .reg_off = AO_RTC_BY_OSCIN_CTRL1, > + .shift = 12, > + .width = 12, > + }, > + .dual = { > + .reg_off = AO_RTC_BY_OSCIN_CTRL0, > + .shift = 28, > + .width = 1, > + }, > + .table = a9_ao_dualdiv_table, > + }, > + .hw.init = &(struct clk_init_data){ > + .name = "a9_ao_rtc_dualdiv_div", > + .ops = &meson_clk_dualdiv_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_ao_rtc_dualdiv_in.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_ao_rtc_dualdiv_sel = { > + .data = &(struct clk_regmap_mux_data) { > + .offset = AO_RTC_BY_OSCIN_CTRL1, > + .mask = 0x1, > + .shift = 24, > + }, > + .hw.init = &(struct clk_init_data){ > + .name = "ao_rtc_dualdiv_sel", > + .ops = &clk_regmap_mux_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_ao_rtc_dualdiv_div.hw, > + &a9_ao_rtc_dualdiv_in.hw, > + }, > + .num_parents = 2, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static struct clk_regmap a9_ao_rtc_dualdiv = { > + .data = &(struct clk_regmap_gate_data){ > + .offset = AO_RTC_BY_OSCIN_CTRL0, > + .bit_idx = 30, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "ao_rtc_dualdiv", > + .ops = &clk_regmap_gate_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_ao_rtc_dualdiv_sel.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static struct clk_regmap a9_ao_rtc = { > + .data = &(struct clk_regmap_mux_data) { > + .offset = AO_RTC_BY_OSCIN_CTRL1, > + .mask = 0x1, > + .shift = 30, > + }, > + .hw.init = &(struct clk_init_data){ > + .name = "ao_rtc", > + .ops = &clk_regmap_mux_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_ao_xtal.hw, > + &a9_ao_rtc_dualdiv.hw, > + }, > + .num_parents = 2, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static struct clk_regmap a9_ao_cec_dualdiv_in = { > + .data = &(struct clk_regmap_gate_data){ > + .offset = AO_CEC_CTRL0, > + .bit_idx = 31, > + }, > + .hw.init = &(struct clk_init_data) { > + .name = "ao_cec_dualdiv_in", > + .ops = &clk_regmap_gate_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_ao_xtal.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_ao_cec_dualdiv_div = { > + .data = &(struct meson_clk_dualdiv_data){ > + .n1 = { > + .reg_off = AO_CEC_CTRL0, > + .shift = 0, > + .width = 12, > + }, > + .n2 = { > + .reg_off = AO_CEC_CTRL0, > + .shift = 12, > + .width = 12, > + }, > + .m1 = { > + .reg_off = AO_CEC_CTRL1, > + .shift = 0, > + .width = 12, > + }, > + .m2 = { > + .reg_off = AO_CEC_CTRL1, > + .shift = 12, > + .width = 12, > + }, > + .dual = { > + .reg_off = AO_CEC_CTRL0, > + .shift = 28, > + .width = 1, > + }, > + .table = a9_ao_dualdiv_table, > + }, > + .hw.init = &(struct clk_init_data){ > + .name = "ao_cec_dualdiv_div", > + .ops = &meson_clk_dualdiv_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_ao_cec_dualdiv_in.hw > + }, > + .num_parents = 1, > + }, > +}; > + > +static struct clk_regmap a9_ao_cec_dualdiv_sel = { > + .data = &(struct clk_regmap_mux_data) { > + .offset = AO_CEC_CTRL1, > + .mask = 0x1, > + .shift = 24, > + }, > + .hw.init = &(struct clk_init_data){ > + .name = "ao_cec_dualdiv_sel", > + .ops = &clk_regmap_mux_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_ao_cec_dualdiv_div.hw, > + &a9_ao_cec_dualdiv_in.hw, > + }, > + .num_parents = 2, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static struct clk_regmap a9_ao_cec_dualdiv = { > + .data = &(struct clk_regmap_gate_data){ > + .offset = AO_CEC_CTRL0, > + .bit_idx = 30, > + }, > + .hw.init = &(struct clk_init_data){ > + .name = "ao_cec_dualdiv", > + .ops = &clk_regmap_gate_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_ao_cec_dualdiv_sel.hw > + }, > + .num_parents = 1, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static struct clk_regmap a9_ao_cec = { > + .data = &(struct clk_regmap_mux_data) { > + .offset = AO_CEC_CTRL1, > + .mask = 0x1, > + .shift = 30, > + }, > + .hw.init = &(struct clk_init_data){ > + .name = "ao_cec", > + .ops = &clk_regmap_mux_ops, > + .parent_hws = (const struct clk_hw *[]) { > + &a9_ao_cec_dualdiv.hw, > + &a9_ao_rtc.hw, > + }, > + .num_parents = 2, > + .flags = CLK_SET_RATE_PARENT, > + }, > +}; > + > +static struct clk_hw *a9_ao_hw_clks[] = { > + [CLKID_AO_XTAL_IN] = &a9_ao_xtal_in.hw, > + [CLKID_AO_XTAL] = &a9_ao_xtal.hw, > + [CLKID_AO_SYS] = &a9_ao_sys.hw, > + [CLKID_AO_SYS_I3C] = &a9_ao_sys_i2c3.hw, > + [CLKID_AO_SYS_RTC_REG] = &a9_ao_sys_rtc_reg.hw, > + [CLKID_AO_SYS_CLKTREE] = &a9_ao_sys_clktree.hw, > + [CLKID_AO_SYS_RST_CTRL] = &a9_ao_sys_rst_ctrl.hw, > + [CLKID_AO_SYS_PAD] = &a9_ao_sys_pad.hw, > + [CLKID_AO_SYS_RTC_DIG] = &a9_ao_sys_rtc_dig.hw, > + [CLKID_AO_SYS_IRQ] = &a9_ao_sys_irq.hw, > + [CLKID_AO_SYS_PWRCTRL] = &a9_ao_sys_pwrctrl.hw, > + [CLKID_AO_SYS_PWM_A] = &a9_ao_sys_pwm_a.hw, > + [CLKID_AO_SYS_PWM_B] = &a9_ao_sys_pwm_b.hw, > + [CLKID_AO_SYS_PWM_C] = &a9_ao_sys_pwm_c.hw, > + [CLKID_AO_SYS_PWM_D] = &a9_ao_sys_pwm_d.hw, > + [CLKID_AO_SYS_PWM_E] = &a9_ao_sys_pwm_e.hw, > + [CLKID_AO_SYS_PWM_F] = &a9_ao_sys_pwm_f.hw, > + [CLKID_AO_SYS_PWM_G] = &a9_ao_sys_pwm_g.hw, > + [CLKID_AO_SYS_I2C_A] = &a9_ao_sys_i2c_a.hw, > + [CLKID_AO_SYS_I2C_B] = &a9_ao_sys_i2c_b.hw, > + [CLKID_AO_SYS_I2C_C] = &a9_ao_sys_i2c_c.hw, > + [CLKID_AO_SYS_I2C_D] = &a9_ao_sys_i2c_d.hw, > + [CLKID_AO_SYS_SED] = &a9_ao_sys_sed.hw, > + [CLKID_AO_SYS_IR_CTRL] = &a9_ao_sys_ir_ctrl.hw, > + [CLKID_AO_SYS_UART_B] = &a9_ao_sys_uart_b.hw, > + [CLKID_AO_SYS_UART_C] = &a9_ao_sys_uart_c.hw, > + [CLKID_AO_SYS_UART_D] = &a9_ao_sys_uart_d.hw, > + [CLKID_AO_SYS_UART_E] = &a9_ao_sys_uart_e.hw, > + [CLKID_AO_SYS_SPISG_0] = &a9_ao_sys_spisg_0.hw, > + [CLKID_AO_SYS_RTC_SECURE] = &a9_ao_sys_rtc_secure.hw, > + [CLKID_AO_SYS_CEC] = &a9_ao_sys_cec.hw, > + [CLKID_AO_SYS_AOCPU] = &a9_ao_sys_aocpu.hw, > + [CLKID_AO_SYS_SRAM] = &a9_ao_sys_sram.hw, > + [CLKID_AO_SYS_SPISG_1] = &a9_ao_sys_spisg_1.hw, > + [CLKID_AO_SYS_SPISG_2] = &a9_ao_sys_spisg_2.hw, > + [CLKID_AO_PWM_A_SEL] = &a9_ao_pwm_a_sel.hw, > + [CLKID_AO_PWM_A_DIV] = &a9_ao_pwm_a_div.hw, > + [CLKID_AO_PWM_A] = &a9_ao_pwm_a.hw, > + [CLKID_AO_PWM_B_SEL] = &a9_ao_pwm_b_sel.hw, > + [CLKID_AO_PWM_B_DIV] = &a9_ao_pwm_b_div.hw, > + [CLKID_AO_PWM_B] = &a9_ao_pwm_b.hw, > + [CLKID_AO_PWM_C_SEL] = &a9_ao_pwm_c_sel.hw, > + [CLKID_AO_PWM_C_DIV] = &a9_ao_pwm_c_div.hw, > + [CLKID_AO_PWM_C] = &a9_ao_pwm_c.hw, > + [CLKID_AO_PWM_D_SEL] = &a9_ao_pwm_d_sel.hw, > + [CLKID_AO_PWM_D_DIV] = &a9_ao_pwm_d_div.hw, > + [CLKID_AO_PWM_D] = &a9_ao_pwm_d.hw, > + [CLKID_AO_PWM_E_SEL] = &a9_ao_pwm_e_sel.hw, > + [CLKID_AO_PWM_E_DIV] = &a9_ao_pwm_e_div.hw, > + [CLKID_AO_PWM_E] = &a9_ao_pwm_e.hw, > + [CLKID_AO_PWM_F_SEL] = &a9_ao_pwm_f_sel.hw, > + [CLKID_AO_PWM_F_DIV] = &a9_ao_pwm_f_div.hw, > + [CLKID_AO_PWM_F] = &a9_ao_pwm_f.hw, > + [CLKID_AO_PWM_G_SEL] = &a9_ao_pwm_g_sel.hw, > + [CLKID_AO_PWM_G_DIV] = &a9_ao_pwm_g_div.hw, > + [CLKID_AO_PWM_G] = &a9_ao_pwm_g.hw, > + [CLKID_AO_RTC_DUALDIV_IN] = &a9_ao_rtc_dualdiv_in.hw, > + [CLKID_AO_RTC_DUALDIV_DIV] = &a9_ao_rtc_dualdiv_div.hw, > + [CLKID_AO_RTC_DUALDIV_SEL] = &a9_ao_rtc_dualdiv_sel.hw, > + [CLKID_AO_RTC_DUALDIV] = &a9_ao_rtc_dualdiv.hw, > + [CLKID_AO_RTC] = &a9_ao_rtc.hw, > + [CLKID_AO_CEC_DUALDIV_IN] = &a9_ao_cec_dualdiv_in.hw, > + [CLKID_AO_CEC_DUALDIV_DIV] = &a9_ao_cec_dualdiv_div.hw, > + [CLKID_AO_CEC_DUALDIV_SEL] = &a9_ao_cec_dualdiv_sel.hw, > + [CLKID_AO_CEC_DUALDIV] = &a9_ao_cec_dualdiv.hw, > + [CLKID_AO_CEC] = &a9_ao_cec.hw, > +}; > + > +static const struct meson_clkc_data a9_ao_clkc_data = { > + .hw_clks = { > + .hws = a9_ao_hw_clks, > + .num = ARRAY_SIZE(a9_ao_hw_clks), > + }, > +}; > + > +static const struct of_device_id a9_ao_clkc_match_table[] = { > + { > + .compatible = "amlogic,a9-aoclkc", > + .data = &a9_ao_clkc_data, > + }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, a9_ao_clkc_match_table); > + > +static struct platform_driver a9_ao_clkc_driver = { > + .probe = meson_clkc_mmio_probe, > + .driver = { > + .name = "a9-aoclkc", > + .of_match_table = a9_ao_clkc_match_table, > + }, > +}; > +module_platform_driver(a9_ao_clkc_driver); > + > +MODULE_DESCRIPTION("Amlogic A9 Always-ON Clock Controller driver"); > +MODULE_AUTHOR("Jian Hu "); > +MODULE_LICENSE("GPL"); > +MODULE_IMPORT_NS("CLK_MESON"); -- Jerome From krzk at kernel.org Fri May 15 01:09:03 2026 From: krzk at kernel.org (Krzysztof Kozlowski) Date: Fri, 15 May 2026 10:09:03 +0200 Subject: [PATCH 02/10] dt-bindings: clock: Add Amlogic A9 PLL clock controller In-Reply-To: <20260511-b4-a9_clk-v1-2-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-2-41cb4071b7c9@amlogic.com> Message-ID: <20260515-subtle-sepia-tuatara-cfee3d@quoll> On Mon, May 11, 2026 at 08:47:24PM +0800, Jian Hu wrote: > Add the PLL clock controller dt-bindings for the Amlogic A9 SoC family. > > Signed-off-by: Jian Hu > --- > .../bindings/clock/amlogic,a9-pll-clkc.yaml | 110 +++++++++++++++++++++ > include/dt-bindings/clock/amlogic,a9-pll-clkc.h | 55 +++++++++++ > 2 files changed, 165 insertions(+) > > diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml > new file mode 100644 > index 000000000000..4ee6013ba1a1 > --- /dev/null > +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml > @@ -0,0 +1,110 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +# Copyright (C) 2026 Amlogic, Inc. All rights reserved > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/clock/amlogic,a9-pll-clkc.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Amlogic A9 Series PLL Clock Controller > + > +maintainers: > + - Neil Armstrong > + - Jerome Brunet > + - Jian Hu > + - Xianwei Zhao > + > +properties: > + compatible: > + enum: > + - amlogic,a9-gp0-pll > + - amlogic,a9-hifi0-pll > + - amlogic,a9-hifi1-pll > + - amlogic,a9-mclk0-pll > + - amlogic,a9-mclk1-pll > + > + reg: > + maxItems: 1 > + > + '#clock-cells': > + const: 1 > + > + clocks: > + items: > + - description: pll input oscillator gate > + - description: fixed input clock source for mclk_sel_0 > + - description: u3p2pll input clock source for mclk_sel_0 (optional) Second clock is also optional. Drop "(optional)" comment, just confusing. > + minItems: 1 > + > + clock-names: > + items: > + - const: in0 > + - const: in1 > + - const: in2 Pretty pointless names, drop property. > + minItems: 1 > + > +required: > + - compatible > + - '#clock-cells' > + - reg > + - clocks > + - clock-names > + > +allOf: > + - if: > + properties: > + compatible: > + contains: > + enum: > + - amlogic,a9-mclk0-pll > + - amlogic,a9-mclk1-pll > + > + then: > + properties: > + clocks: > + maxItems: 3 No, minItems instead. maxItems is already 3, so what is the point of redefining it? > + > + clock-names: > + maxItems: 3 > + > + - if: > + properties: > + compatible: > + contains: > + enum: > + - amlogic,a9-gp0-pll > + - amlogic,a9-hifi0-pll > + - amlogic,a9-hifi1-pll > + > + then: > + properties: > + clocks: > + maxItems: 1 > + > + clock-names: > + maxItems: 1 > + > +additionalProperties: false > + > +examples: > + - | > + apb4 { soc > + #address-cells = <2>; > + #size-cells = <2>; > + > + clock-controller at 8200 { > + compatible = "amlogic,a9-gp0-pll"; > + reg = <0x0 0x8200 0x0 0x20>; > + #clock-cells = <1>; > + clocks = <&scmi_clk 0>; > + clock-names = "in0"; > + }; > + > + clock-controller at 8330 { > + compatible = "amlogic,a9-mclk0-pll"; > + reg = <0x0 0x8330 0x0 0x14>; > + #clock-cells = <1>; > + clocks = <&scmi_clk 4>, > + <&scmi_clk 8>; > + clock-names = "in0", "in1"; One example is enough, you have exactly the same properties. Best regards, Krzysztof From krzk at kernel.org Fri May 15 01:10:37 2026 From: krzk at kernel.org (Krzysztof Kozlowski) Date: Fri, 15 May 2026 10:10:37 +0200 Subject: [PATCH 03/10] dt-bindings: clock: Add Amlogic A9 peripherals clock controller In-Reply-To: <20260511-b4-a9_clk-v1-3-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-3-41cb4071b7c9@amlogic.com> Message-ID: <20260515-augmented-cyber-puffin-4db20f@quoll> On Mon, May 11, 2026 at 08:47:25PM +0800, Jian Hu wrote: > Add the peripherals clock controller dt-bindings for the Amlogic A9 > SoC family. > > Signed-off-by: Jian Hu > --- > .../clock/amlogic,a9-peripherals-clkc.yaml | 150 +++++++++ > .../clock/amlogic,a9-peripherals-clkc.h | 352 +++++++++++++++++++++ > 2 files changed, 502 insertions(+) > > diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml > new file mode 100644 > index 000000000000..97e2c44d8630 > --- /dev/null > +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml > @@ -0,0 +1,150 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +# Copyright (C) 2026 Amlogic, Inc. All rights reserved > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/clock/amlogic,a9-peripherals-clkc.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Amlogic A9 Series Peripherals Clock Controller > + > +maintainers: > + - Neil Armstrong > + - Jerome Brunet > + - Jian Hu > + - Xianwei Zhao > + > +properties: > + compatible: > + const: amlogic,a9-peripherals-clkc > + > + reg: > + maxItems: 1 > + > + '#clock-cells': > + const: 1 > + > + clocks: > + minItems: 20 I don't think so. How they could be optional in silicon? How does exactly work from silicon point of view? > + items: > + - description: input oscillator > + - description: input fclk div 2 > + - description: input fclk div 3 > + - description: input fclk div 4 > + - description: input fclk div 5 > + - description: input fclk div 7 > + - description: input fclk div 2p5 > + - description: input sys clk > + - description: input gp1 pll > + - description: input gp2 pll > + - description: input sys pll div 16 > + - description: input cpu clk div 16 > + - description: input a78 clk div 16 > + - description: input dsu clk div 16 > + - description: input rtc clk > + - description: input gp0 pll > + - description: input hifi0 pll > + - description: input hifi1 pll > + - description: input mclk0 pll > + - description: input mclk1 pll > + - description: input video1 pll (optional) > + - description: input video2 pll (optional) > + - description: input hdmi out2 clk (optional) > + - description: input hdmi pixel clk (optional) > + - description: input pixel0 pll (optional) > + - description: input pixel1 pll (optional) > + - description: input usb2 drd clk (optional) > + - description: external input rmii oscillator (optional) > + > + clock-names: > + minItems: 20 > + items: > + - const: xtal > + - const: fdiv2 > + - const: fdiv3 > + - const: fdiv4 > + - const: fdiv5 > + - const: fdiv7 > + - const: fdiv2p5 > + - const: sys > + - const: gp1 > + - const: gp2 > + - const: sysplldiv16 > + - const: cpudiv16 > + - const: a78div16 > + - const: dsudiv16 > + - const: rtc > + - const: gp0 > + - const: hifi0 > + - const: hifi1 > + - const: mclk0 > + - const: mclk1 > + - const: vid1 > + - const: vid2 > + - const: hdmiout2 > + - const: hdmipix > + - const: pix0 > + - const: pix1 > + - const: u2drd > + - const: ext_rmii > + > +required: > + - compatible > + - reg > + - '#clock-cells' > + - clocks > + - clock-names > + > +additionalProperties: false > + > +examples: > + - | > + apb4 { Same comments as other patches. Do not come with your own style, but adjust to mainline. Do you see this anywhere? git grep apb4 -- Documentation/devicetree/bindings/clock/ So why coming with something COMPLETELY different? Best regards, Krzysztof From krzk at kernel.org Fri May 15 01:10:59 2026 From: krzk at kernel.org (Krzysztof Kozlowski) Date: Fri, 15 May 2026 10:10:59 +0200 Subject: [PATCH 04/10] dt-bindings: clock: Add Amlogic A9 AO clock controller In-Reply-To: <20260511-b4-a9_clk-v1-4-41cb4071b7c9@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-4-41cb4071b7c9@amlogic.com> Message-ID: <20260515-resourceful-diligent-hound-b666e5@quoll> On Mon, May 11, 2026 at 08:47:26PM +0800, Jian Hu wrote: > Add the Always-On clock controller dt-bindings for the Amlogic A9 > SoC family. > > Signed-off-by: Jian Hu > --- > .../bindings/clock/amlogic,a9-aoclkc.yaml | 76 ++++++++++++++++++++++ > include/dt-bindings/clock/amlogic,a9-aoclkc.h | 76 ++++++++++++++++++++++ > 2 files changed, 152 insertions(+) All comments apply. Best regards, Krzysztof From diederik at cknow-tech.com Fri May 15 02:27:01 2026 From: diederik at cknow-tech.com (Diederik de Haas) Date: Fri, 15 May 2026 11:27:01 +0200 Subject: [PATCH v5 00/21] drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup In-Reply-To: <20260510124111.1226584-1-jonas@kwiboo.se> References: <20260510124111.1226584-1-jonas@kwiboo.se> Message-ID: Hi Jonas, On Sun May 10, 2026 at 2:42 PM CEST, Jonas Karlman wrote: > This is a revival of an old dw-hdmi series and is the first series part > of a new effort to upstream old LibreELEC HDMI 2.0 patches for Rockchip > RK33xx devices. > > This series ensure poweron/poweroff and CEC phys addr invalidation is > happening under drm mode_config mutex lock, and also ensure EDID is > updated after a HPD low voltage pulse by changing to debounce hotplug > processing. > > These changes have mainly been tested on Rockchip RK3328, RK3399 and > RK3568 devices using both the dw-hdmi connector and also using a basic > convert to use a bridge connector. Yesterday I made a test run and the TL;DR is this: Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B The longer version ... Test setup: I have a 1080p monitor capable of HDMI 1.4 (I think) and a 4K TV capable of HDMI 2.0 (but not 2.1). I'm running Debian Testing/Sid on them with Sway 1.12-rc3-1 (from Experimental) and see if that works (at all) and then I try to play one or more of my 'test' videos with mpv and using v4l2request HW accel. I used 2 kernels: 7.1-rc3 and 7.1-rc3 with this patch set. One of the possible issues that may have been fixed with this patch set is doing hotplugging of the HDMI connection, especially going from 4K to 1080p. So when testing I unplugged my 4K cable and plugged in the 1080p cable and looked if it has adjusted things properly. I also checked dmesg to see if there were interesting msgs. > Testing with a Rock Pi 4 (RK3399) using a Raspberry Pi Monitor with > Linux kms client console using drm.debug=0xe should log something like > following: > > Power cycle monitor using the power button: > [CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a > [CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz > [CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156 > [CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 > [CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1 > [CONNECTOR:68:HDMI-A-1] Same epoch counter 10 > > Cable unplugged: > [CONNECTOR:68:HDMI-A-1] EDID changed, epoch counter 11 > [CONNECTOR:68:HDMI-A-1] status updated from connected to disconnected > [CONNECTOR:68:HDMI-A-1] Changed epoch counter 10 => 12 > [CONNECTOR:68:HDMI-A-1] generating connector hotplug event > [CONNECTOR:68:HDMI-A-1] Sent hotplug event When I was preparing my testing session the 'Sent hotplug event' caught my eye as it seemed like a clear differentiator between the kernel with and without this patch set. Unfortunately during my 'real' testing session I did not see that message ... > Cable connected: > [CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a > [CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz > [CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156 > [CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 > [CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1 > [CONNECTOR:68:HDMI-A-1] status updated from disconnected to connected > [CONNECTOR:68:HDMI-A-1] Changed epoch counter 12 => 13 > [CONNECTOR:68:HDMI-A-1] generating connector hotplug event > [CONNECTOR:68:HDMI-A-1] Sent hotplug event ... but I did see all the others. With my 4K TV I got ``max TMDS clock 300000 kHz`` but on my 1080p monitor I got 0 kHz just like quoted above. I also checked CEC and EDID with the following commands: - ``cat /sys/kernel/debug/cec/cec0/status`` - ``di-edid-decode /sys/devices/platform/display-subsystem/drm/card0/card0-HDMI-A-1/edid`` And in all cases they produced identical outputs. Rock64 testing: On the Rock64 I got a stack trace: ```sh root at rock64-test:~# dmesg --level 4 [ 2.302852] dw-apb-uart ff130000.serial: forbid DMA for kernel console [ 5.068434] dw_wdt ff1a0000.watchdog: No valid TOPs array specified [ 5.841772] dwc2 ff580000.usb: supply vusb_d not found, using dummy regulator [ 5.842752] dwc2 ff580000.usb: supply vusb_a not found, using dummy regulator [ 7.392797] ------------[ cut here ]------------ [ 7.393236] WARNING: drivers/media/cec/core/cec-adap.c:1116 at cec_received_msg_ts+0x530/0xc10 [cec], CPU#0: irq/50-dw-hdmi-/186 [ 7.394811] Modules linked in: dw_hdmi_cec(+) hid_generic usbhid rockchipdrm hid dw_hdmi_qp dw_mipi_dsi realtek dw_hdmi phy_package analogix_dp drm_dp_aux_bus dwmac_rk drm_display_helper stmmac_platform xhci_plat_hcd stmmac cec rc_core xhci_hcd pcs_xpcs drm_client_lib phylink rtc_rk808 drm_dma_helper ohci_platform rk808_regulator mdio_devres dwc2 of_mdio drm_kms_helper ehci_platform dwc3 ehci_hcd fixed_phy udc_core ohci_hcd drm fwnode_mdio usbcore libphy syscon_reboot_mode ulpi gpio_rockchip phy_rockchip_inno_usb2 dw_mmc_rockchip reboot_mode io_domain gpio_syscon mdio_bus dw_mmc_pltfm fixed usb_common dw_wdt dw_mmc phy_rockchip_inno_hdmi spi_rockchip nvmem_rockchip_efuse i2c_rk3x pl330 [ 7.404613] CPU: 0 UID: 0 PID: 186 Comm: irq/50-dw-hdmi- Not tainted 7.1-rc3+unreleased-arm64-cknow #1 PREEMPTLAZY Debian 7.1~rc3-2 [ 7.406138] Hardware name: Pine64 Rock64 (DT) [ 7.406725] pstate: a0000005 (NzCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 7.407634] pc : cec_received_msg_ts+0x530/0xc10 [cec] [ 7.408344] lr : dw_hdmi_cec_thread+0x98/0xb8 [dw_hdmi_cec] [ 7.409095] sp : ffff8000809e3cc0 [ 7.409552] x29: ffff8000809e3d30 x28: 0000000000000000 x27: 0000000000000000 [ 7.410500] x26: ffff0000faae44ac x25: ffffa02b5edaf370 x24: ffff00000bd9f6a0 [ 7.411447] x23: ffffa02b60e3e000 x22: ffff0000faae4400 x21: 0000000000000001 [ 7.412392] x20: ffff0000faae4400 x19: ffff00000a887800 x18: ffff5fd59d9c7000 [ 7.413333] x17: 0000000061dcb992 x16: ffffa02b5edf4d88 x15: ffffa02b60e421d0 [ 7.414256] x14: ffffa02b60d5a100 x13: 00000000000000c0 x12: ffff0000fe721100 [ 7.415175] x11: 00000000000000c0 x10: 0000000000000db0 x9 : ffffa02af03675c8 [ 7.416095] x8 : ffff0000fa3f0e10 x7 : 0000000000000000 x6 : 0000000000000010 [ 7.417011] x5 : ffff5fd59d9c7000 x4 : ffff000000c7ccc0 x3 : ffff00000bd9f680 [ 7.417927] x2 : 00000001b8300c79 x1 : 00000000ffffffff x0 : 0000000000000000 [ 7.418845] Call trace: [ 7.419174] cec_received_msg_ts+0x530/0xc10 [cec] (P) [ 7.419855] dw_hdmi_cec_thread+0x98/0xb8 [dw_hdmi_cec] [ 7.420536] irq_thread_fn+0x30/0xc0 [ 7.421021] irq_thread+0x19c/0x3e8 [ 7.421491] kthread+0x134/0x150 [ 7.421925] ret_from_fork+0x10/0x20 [ 7.422405] ---[ end trace 0000000000000000 ]--- [ 23.252709] rockchip-i2s ff000000.i2s: using zero-initialized flat cache, this may cause unexpected behavior ``` This was with the kernel *with* this patch set (and connected to 4K TV; no idea if that's relevant though). But I did not see any adverse affects from it and when I tried again today, I did NOT get a/that stack trace. The Rock64 was the device which showed most clearly the difference between having this patch set applied or not. As always, I started with the kernel without this patch set and booted it up connected to my 4K TV. Then I logged into a Sway session via greetd (thus ~ also Sway) and everything looked fine*. I then tried hotplugging the HDMI from my 4K TV to my 1080p monitor ... and I did NOT get any output on my monitor. I didn't even see any message in dmesg that it noticed that first a cable got unplugged and later another cable got plugged. My monitor itself did notice when I then unplugged the cable again. When I then plugged the 4K cable back in, my TV showed Sway like before and AFAICT everything was working again. When I booted into the kernel WITH this patch set, hotplugging back and forth worked absolutely fine :-D The screen resolution got adjusted to the new one and dmesg noticed the unplugging and plugging actions and it showed similar messages as Jonas showed in his cover page (and quoted above). With the exception of 'Sent hotplug event' as mentioned above. *) There are pretty substantial artifacts which are AFAIUI due to the not-exactly-fast GPU and memory, but that's unrelated to this patch set. Playing a HW accelerated video full-screen often works pretty decently. RockPro64 testing: I can be much shorter about the RockPro64. Everything seemed to work just fine with and without this patch set applied. So no improvement, but also no regressions. Quartz64-B testing: Same story as with RockPro64: no improvements, but also no regressions. I did notice that ``drm.debug=0xe`` was *quite* verbose on this device. On Rock64 and RockPro64 I saw various drm related msgs in dmesg _when I actually did something_ but on Q64-B I got several ~ identical lines per second, whether I did sth or not. But that was with and without this patch set, so I guess that's a general RK356X 'issue' (if at all an issue). Cheers, Diederik > Jonas Karlman (21): > drm: bridge: dw_hdmi: Disable scrambler feature when not supported > drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt > drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable > drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode > drm: bridge: dw_hdmi: Fold poweron and setup functions > drm: bridge: dw_hdmi: Remove previous_mode and mode_set > drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup > drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup > drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect > drm: bridge: dw_hdmi: Remove cec_notifier_mutex > drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update() > drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() > drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio > drm: bridge: dw_hdmi: Use generic CEC notifier helpers > drm: bridge: dw_hdmi: Add common suspend helper > drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event > drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling > drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense() > drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd() > drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers > drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() > > drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 5 +- > drivers/gpu/drm/bridge/synopsys/Kconfig | 1 + > drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 483 +++++++------------- > drivers/gpu/drm/meson/meson_dw_hdmi.c | 5 +- > drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 13 +- > drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 2 - > include/drm/bridge/dw_hdmi.h | 7 +- > 7 files changed, 194 insertions(+), 322 deletions(-) From herbert at gondor.apana.org.au Fri May 15 03:23:55 2026 From: herbert at gondor.apana.org.au (Herbert Xu) Date: Fri, 15 May 2026 18:23:55 +0800 Subject: [PATCH] crypto: amlogic - avoid double cleanup in meson_crypto_probe() In-Reply-To: <20260508042416.419216-1-dawei.feng@seu.edu.cn> References: <20260508042416.419216-1-dawei.feng@seu.edu.cn> Message-ID: On Fri, May 08, 2026 at 12:24:16PM +0800, Dawei Feng wrote: > When meson_allocate_chanlist() fails after a partial allocation, it already > unwinds the allocated chanlist state through its local error path. > meson_crypto_probe() then jump to error_flow and calls > meson_free_chanlist() again, causing the same per-flow resources to be torn > down twice. In the reproduced failure path, the second teardown > re-entered crypto_engine_exit() on an already destroyed worker and KASAN > reported a slab-use-after-free in kthread_destroy_worker(). > > Prevent double-free by handling partial allocation failures locally within > meson_allocate_chanlist() and skipping the outer cleanup path. > > The bug was first flagged by an experimental analysis tool we are > developing for kernel memory-management bugs while analyzing > v6.13-rc1. The tool is still under development and is not yet publicly > available. > > The bug was reproduced in a QEMU x86_64 guest booted with KASAN on v7.1, > using the reproducer under tools/testing/meson_crypto_probe. The reproducer > forces the second dma_alloc_attrs() call in the gxl-crypto probe path to > return NULL, making meson_allocate_chanlist() fail after partial > initialization. On the unpatched kernel this reliably triggered a > slab-use-after-free. With this fix applied, the same reproducer no longer > emits any KASAN report and the probe fails cleanly with -ENOMEM. > > ================================================================== > BUG: KASAN: slab-use-after-free in kthread_destroy_worker+0xb2/0xd0 > Read of size 8 at addr ff1100010c057a68 by task insmod/265 > > CPU: 1 UID: 0 PID: 265 Comm: insmod Tainted: G O 7.1.0-rc2-00376-g810af9adc907-dirty #10 PREEMPT(lazy) > Tainted: [O]=OOT_MODULE > Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.15.0-1 04/01/2014 > Call Trace: > > dump_stack_lvl+0x68/0xa0 > print_report+0xcb/0x5e0 > ? __virt_addr_valid+0x21d/0x3f0 > ? kthread_destroy_worker+0xb2/0xd0 > ? kthread_destroy_worker+0xb2/0xd0 > kasan_report+0xca/0x100 > ? kthread_destroy_worker+0xb2/0xd0 > kthread_destroy_worker+0xb2/0xd0 > meson_crypto_probe+0x4d0/0xc10 [amlogic_gxl_crypto] > platform_probe+0x99/0x140 > really_probe+0x1c6/0x6a0 > ? __pfx___device_attach_driver+0x10/0x10 > __driver_probe_device+0x248/0x310 > ? acpi_driver_match_device+0xb0/0x100 > driver_probe_device+0x48/0x210 > ? __pfx___device_attach_driver+0x10/0x10 > __device_attach_driver+0x160/0x320 > bus_for_each_drv+0x104/0x190 > ? __pfx_bus_for_each_drv+0x10/0x10 > ? _raw_spin_unlock_irqrestore+0x2c/0x50 > __device_attach+0x19d/0x3b0 > ? __pfx___device_attach+0x10/0x10 > ? do_raw_spin_unlock+0x53/0x220 > device_initial_probe+0x78/0xa0 > bus_probe_device+0x5b/0x130 > device_add+0xcfd/0x1430 > ? __pfx_device_add+0x10/0x10 > ? insert_resource+0x34/0x50 > ? lock_release+0xc9/0x290 > platform_device_add+0x24e/0x590 > ? __pfx_meson_crypto_probe_repro_init+0x10/0x10 [meson_crypto_probe_repro] > meson_crypto_probe_repro_init+0x330/0xff0 [meson_crypto_probe_repro] > do_one_initcall+0xc0/0x450 > ? __pfx_do_one_initcall+0x10/0x10 > ? _raw_spin_unlock_irqrestore+0x2c/0x50 > ? __create_object+0x59/0x80 > ? kasan_unpoison+0x27/0x60 > do_init_module+0x27b/0x7d0 > ? __pfx_do_init_module+0x10/0x10 > ? kasan_quarantine_put+0x84/0x1d0 > ? kfree+0x32c/0x510 > ? load_module+0x561e/0x5ff0 > load_module+0x54fe/0x5ff0 > ? __pfx_load_module+0x10/0x10 > ? security_file_permission+0x20/0x40 > ? kernel_read_file+0x23d/0x6e0 > ? mmap_region+0x235/0x4a0 > ? __pfx_kernel_read_file+0x10/0x10 > ? __file_has_perm+0x2c0/0x3e0 > init_module_from_file+0x158/0x180 > ? __pfx_init_module_from_file+0x10/0x10 > ? __lock_acquire+0x45a/0x1ba0 > ? idempotent_init_module+0x315/0x610 > ? lock_release+0xc9/0x290 > ? lockdep_init_map_type+0x4b/0x220 > ? do_raw_spin_unlock+0x53/0x220 > idempotent_init_module+0x330/0x610 > ? __pfx_idempotent_init_module+0x10/0x10 > ? __pfx_cred_has_capability.isra.0+0x10/0x10 > ? ksys_mmap_pgoff+0x385/0x520 > __x64_sys_finit_module+0xbe/0x120 > do_syscall_64+0x115/0x690 > entry_SYSCALL_64_after_hwframe+0x77/0x7f > RIP: 0033:0x7f7d6d31690d > Code: 5b 41 5c c3 66 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d f3 b4 0f 00 f7 d8 > > RSP: 002b:00007fffc027ac68 EFLAGS: 00000246 ORIG_RAX: 0000000000000139 > RAX: ffffffffffffffda RBX: 000055f7b81967c0 RCX: 00007f7d6d31690d > RDX: 0000000000000000 RSI: 000055f79a0d6cd2 RDI: 0000000000000003 > RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000 > R10: 0000000000000003 R11: 0000000000000246 R12: 000055f79a0d6cd2 > R13: 000055f7b8196790 R14: 000055f79a0d5888 R15: 000055f7b81968e0 > > > Fixes: 48fe583fe541 ("crypto: amlogic - Add crypto accelerator for amlogic GXL") > Cc: stable at vger.kernel.org > Signed-off-by: Zilin Guan > Signed-off-by: Dawei Feng > --- > drivers/crypto/amlogic/amlogic-gxl-core.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) Patch applied. Thanks. -- Email: Herbert Xu Home Page: http://gondor.apana.org.au/~herbert/ PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt From nikitamalco203 at gmail.com Fri May 15 07:38:00 2026 From: nikitamalco203 at gmail.com (Nikita Maslo) Date: Fri, 15 May 2026 17:38:00 +0300 Subject: Broken bitbang 1-wire on Amlogic after kernel update Message-ID: <20260515143801.2132974-1-nikitamalco203@gmail.com> Hi, I was running linux-6.12.67 on a hardkernel odroid-c2 (Amlogic S905) Amlogic board with DS18B20 on w1-gpio (1-Wire bitbang), everything worked fine. After updating the kernel, 1-wire stopped working the DS18B20 fails to enumerate or reads back the broken-bus, and dmesg gets flooded with WARNINGs from gpiod_set_value/gpiod_get_value called from w1_gpio_write_bit/w1_gpio_read_bit. Hardware: Hardkernel ODROID-C2 (Amlogic S905). Kernel: linux-6.18.15 (also reproduces on linux-6.12.69; last known-good for me was linux-6.12.67). Device tree fragment used: / { onewire { compatible = "w1-gpio"; gpios = <&gpio GPIOX_17 GPIO_ACTIVE_HIGH>; }; }; dmesg, captured on ODROID-C2 / linux-6.18.15: [ 59.102855] ------------[ cut here ]------------ [ 59.102891] WARNING: CPU: 0 PID: 1145 at drivers/gpio/gpiolib.c:3880 gpiod_set_value+0x78/0x88 [ 59.103569] CPU: 0 UID: 0 PID: 1145 Comm: w1_bus_master1 Tainted: G WC 6.18.15-current-meson64 #8 PREEMPT [ 59.103618] Hardware name: Hardkernel ODROID-C2 (DT) [ 59.103658] pc : gpiod_set_value+0x78/0x88 [ 59.103687] lr : w1_gpio_write_bit+0x20/0x38 [w1_gpio] [ 59.104161] Call trace: [ 59.104175] gpiod_set_value+0x78/0x88 (P) [ 59.104210] w1_gpio_write_bit+0x20/0x38 [w1_gpio] [ 59.104238] w1_reset_bus+0x84/0xe0 [wire] [ 59.104272] w1_search+0x88/0x2a0 [wire] [ 59.104304] w1_search_devices+0x54/0x90 [wire] [ 59.104336] w1_search_process_cb+0x84/0x170 [wire] [ 59.104368] w1_process+0x1c0/0x238 [wire] [ 59.104400] kthread+0x14c/0x218 [ 59.104432] ret_from_fork+0x10/0x20 [ 59.104464] ---[ end trace 0000000000000000 ]--- A symmetric WARN at gpiolib.c:3501 fires from gpiod_get_value via w1_gpio_read_bit on every read slot. After a little researching, the suspect commit looks like: 28f240683871 ("pinctrl: meson: mark the GPIO controller as sleeping") I also tested bitbang i2c-gpio on the same hardware after the update, it seems to keep working. From vsetti at baylibre.com Fri May 15 08:10:38 2026 From: vsetti at baylibre.com (Valerio Setti) Date: Fri, 15 May 2026 17:10:38 +0200 Subject: [PATCH 2/4] ASoC: meson: aiu-encoder-i2s: use gx_iface and gx_stream structures In-Reply-To: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> References: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> Message-ID: <20260515-reshape-aiu-as-axg-v1-2-53b457784ff3@baylibre.com> Start using gx_iface and gx_stream to store interface and stream info, respectively. probe()/remove() functions are added to allocate/free the gx_stream structures for each PCM stream. Clock-wise instead of bulk enabling all the clocks on startup and disabling them on shutdown, only the peripheral's internal ones are enabled/disabled in those functions, whereas MCLK and I2S clock divider are handled in hw_params/hw_free. Interface wide rate symmetry is also enforced here. This is useful when the interface is used for playback and capture at the same time. Finally a trigger() callback is also added to start/stop the associated I2S data formatter. Signed-off-by: Valerio Setti --- sound/soc/meson/aiu-encoder-i2s.c | 200 ++++++++++++++++++++++++++++++++------ sound/soc/meson/aiu.h | 3 + 2 files changed, 174 insertions(+), 29 deletions(-) diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c index 3b4061508c18047fe8d6f3f98061720f8ce238f2..39accd396affb8beb49fa7cca394244730b24574 100644 --- a/sound/soc/meson/aiu-encoder-i2s.c +++ b/sound/soc/meson/aiu-encoder-i2s.c @@ -10,6 +10,8 @@ #include #include "aiu.h" +#include "gx-formatter.h" +#include "gx-interface.h" #define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) #define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) @@ -79,7 +81,7 @@ static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component, } static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, - struct snd_pcm_hw_params *params, + struct gx_stream *ts, unsigned int bs) { switch (bs) { @@ -109,7 +111,7 @@ static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, } static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, - struct snd_pcm_hw_params *params, + struct gx_stream *ts, unsigned int bs) { /* @@ -119,7 +121,7 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, * increased by 50% to get the correct output rate. * No idea why ! */ - if (params_width(params) == 16 && params_channels(params) == 8) { + if (ts->width == 16 && ts->channels == 8) { if (bs % 2) { dev_err(component->dev, "Cannot increase i2s divider by 50%%\n"); @@ -142,24 +144,18 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, } static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, - struct snd_pcm_hw_params *params) + struct gx_stream *ts) { struct aiu *aiu = snd_soc_component_get_drvdata(component); - unsigned int srate = params_rate(params); unsigned int fs, bs; int ret; /* Get the oversampling factor */ - fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate); + fs = DIV_ROUND_CLOSEST(ts->iface->mclk_rate, ts->iface->rate); if (fs % 64) return -EINVAL; - /* Send data MSB first */ - snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG, - AIU_I2S_DAC_CFG_MSB_FIRST, - AIU_I2S_DAC_CFG_MSB_FIRST); - /* Set bclk to lrlck ratio */ snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL, AIU_CODEC_DAC_LRCLK_CTRL_DIV, @@ -169,9 +165,9 @@ static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, bs = fs / 64; if (aiu->platform->has_clk_ctrl_more_i2s_div) - ret = aiu_encoder_i2s_set_more_div(component, params, bs); + ret = aiu_encoder_i2s_set_more_div(component, ts, bs); else - ret = aiu_encoder_i2s_set_legacy_div(component, params, bs); + ret = aiu_encoder_i2s_set_legacy_div(component, ts, bs); if (ret) return ret; @@ -188,25 +184,55 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct gx_iface *iface = ts->iface; struct snd_soc_component *component = dai->component; int ret; - /* Disable the clock while changing the settings */ - aiu_encoder_i2s_divider_enable(component, false); + /* Enforce interface wide rate symmetry. */ + if (iface->rate && (iface->rate != params_rate(params))) { + dev_err(dai->dev, "can't set iface rate (%d != %d)\n", + iface->rate, params_rate(params)); + return -EINVAL; + } + + iface->rate = params_rate(params); + ts->physical_width = params_physical_width(params); + ts->width = params_width(params); + ts->channels = params_channels(params); ret = aiu_encoder_i2s_setup_desc(component, params); if (ret) { - dev_err(dai->dev, "setting i2s desc failed\n"); + dev_err(dai->dev, "setting i2s desc failed: %d\n", ret); return ret; } - ret = aiu_encoder_i2s_set_clocks(component, params); + ret = aiu_encoder_i2s_set_clocks(component, ts); if (ret) { - dev_err(dai->dev, "setting i2s clocks failed\n"); + dev_err(dai->dev, "setting i2s clocks failed: %d\n", ret); return ret; } - aiu_encoder_i2s_divider_enable(component, true); + return 0; +} + +static int aiu_encoder_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct snd_soc_component *component = dai->component; + int ret; + + if (ts->clk_enabled) + return 0; + + ret = clk_prepare_enable(ts->iface->mclk); + if (ret) + return ret; + + ts->clk_enabled = true; + + aiu_encoder_i2s_divider_enable(component, 1); return 0; } @@ -214,9 +240,20 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); struct snd_soc_component *component = dai->component; - aiu_encoder_i2s_divider_enable(component, false); + /* + * Disable the i2s clock divider only if this is the last substream + * being closed. + */ + if (snd_soc_dai_active(dai) <= 1) + aiu_encoder_i2s_divider_enable(component, 0); + + if (ts->clk_enabled) { + clk_disable_unprepare(ts->iface->mclk); + ts->clk_enabled = false; + } return 0; } @@ -224,6 +261,8 @@ static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream, static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_component *component = dai->component; + struct aiu *aiu = snd_soc_component_get_drvdata(component); + struct gx_iface *iface = &aiu->i2s.iface; unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK; unsigned int val = 0; unsigned int skew; @@ -255,9 +294,12 @@ static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) skew = 0; break; default: + dev_err(dai->dev, "unsupported dai format\n"); return -EINVAL; } + iface->fmt = fmt; + val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew); snd_soc_component_update_bits(component, AIU_CLK_CTRL, AIU_CLK_CTRL_LRCLK_INVERT | @@ -281,10 +323,14 @@ static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, return 0; ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq); - if (ret) - dev_err(dai->dev, "Failed to set sysclk to %uHz", freq); + if (ret) { + dev_err(dai->dev, "Failed to set sysclk to %uHz: %d", freq, ret); + return ret; + } - return ret; + aiu->i2s.iface.mclk_rate = freq; + + return 0; } static const unsigned int hw_channels[] = {2, 8}; @@ -305,30 +351,126 @@ static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream, SNDRV_PCM_HW_PARAM_CHANNELS, &hw_channel_constraints); if (ret) { - dev_err(dai->dev, "adding channels constraints failed\n"); + dev_err(dai->dev, "adding channels constraints failed: %d\n", ret); return ret; } - ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks); - if (ret) - dev_err(dai->dev, "failed to enable i2s clocks\n"); + /* + * Enable only clocks which are required for the interface internal + * logic. MCLK is enabled/disabled from the formatter and the I2S + * divider is enabled/disabled in "hw_params"/"hw_free", respectively. + */ + ret = clk_prepare_enable(aiu->i2s.clks[PCLK].clk); + if (ret) { + dev_err(dai->dev, "failed to enable PCLK: %d\n", ret); + return ret; + } + ret = clk_prepare_enable(aiu->i2s.clks[MIXER].clk); + if (ret) { + dev_err(dai->dev, "failed to enable MIXER: %d\n", ret); + clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); + return ret; + } + ret = clk_prepare_enable(aiu->i2s.clks[AOCLK].clk); + if (ret) { + dev_err(dai->dev, "failed to enable AOCLK: %d\n", ret); + clk_disable_unprepare(aiu->i2s.clks[MIXER].clk); + clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); + return ret; + } - return ret; + return 0; } static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct gx_iface *iface = ts->iface; + + if (!snd_soc_dai_active(dai)) + iface->rate = 0; + + clk_disable_unprepare(aiu->i2s.clks[AOCLK].clk); + clk_disable_unprepare(aiu->i2s.clks[MIXER].clk); + clk_disable_unprepare(aiu->i2s.clks[PCLK].clk); +} - clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks); +static int aiu_encoder_i2s_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = gx_stream_start(ts); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + gx_stream_stop(ts); + ret = 0; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int aiu_encoder_i2s_remove_dai(struct snd_soc_dai *dai) +{ + int stream; + + for_each_pcm_streams(stream) { + struct gx_stream *ts = snd_soc_dai_dma_data_get(dai, stream); + + if (ts) + gx_stream_free(ts); + } + + return 0; +} + +static int aiu_encoder_i2s_probe_dai(struct snd_soc_dai *dai) +{ + struct aiu *aiu = snd_soc_dai_get_drvdata(dai); + struct gx_iface *iface = &aiu->i2s.iface; + int stream; + + for_each_pcm_streams(stream) { + struct gx_stream *ts; + + if (!snd_soc_dai_get_widget(dai, stream)) + continue; + + ts = gx_stream_alloc(iface); + if (!ts) { + aiu_encoder_i2s_remove_dai(dai); + return -ENOMEM; + } + snd_soc_dai_dma_data_set(dai, stream, ts); + } + + iface->mclk = aiu->i2s.clks[MCLK].clk; + + return 0; } const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = { + .probe = aiu_encoder_i2s_probe_dai, + .remove = aiu_encoder_i2s_remove_dai, .hw_params = aiu_encoder_i2s_hw_params, + .prepare = aiu_encoder_i2s_prepare, .hw_free = aiu_encoder_i2s_hw_free, .set_fmt = aiu_encoder_i2s_set_fmt, .set_sysclk = aiu_encoder_i2s_set_sysclk, .startup = aiu_encoder_i2s_startup, .shutdown = aiu_encoder_i2s_shutdown, + .trigger = aiu_encoder_i2s_trigger, }; diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h index 0f94c8bf608181112d78402532b832eb50c2d409..68310de0bdf7a97d8de2ff306c159248ee9b0ede 100644 --- a/sound/soc/meson/aiu.h +++ b/sound/soc/meson/aiu.h @@ -7,6 +7,8 @@ #ifndef _MESON_AIU_H #define _MESON_AIU_H +#include "gx-formatter.h" + struct clk; struct clk_bulk_data; struct device; @@ -25,6 +27,7 @@ struct aiu_interface { struct clk_bulk_data *clks; unsigned int clk_num; int irq; + struct gx_iface iface; }; struct aiu_platform_data { -- 2.39.5 From vsetti at baylibre.com Fri May 15 08:10:36 2026 From: vsetti at baylibre.com (Valerio Setti) Date: Fri, 15 May 2026 17:10:36 +0200 Subject: [PATCH 0/4] ASoC: meson: aiu: align I2S design to the AXG one Message-ID: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> This is the first follow-up patch series based on RFC [1]. The goal here is simply to reshape Amlogic GX's AIU implementation for I2S to follow the same design as in AXG's TDM. Keeping the same design allows for unifying the two platform implementations in the future. The first commit introduces gx-formatter as the basic block which takes care of properly formatting audio data. Formatters are DAPM widgets (c.f. axg-tdm-formatter in AXG) which are dynamically attached/detached to the streams when the latters starts/stop, respectively. aiu-formatter-i2s is introduced as formatter implementation for the i2s output. By the end aiu-encoder-i2s will only need to handle interface clocks and enforce interface wide rate symmetry (c.f axg-tdm-interface on the AXG platform). Right now rate symmetry is not relevant because only i2s output is supported, but it will become useful when following patch series will introduce the i2s input part. This series was tested on an OdroidC2 board (Amlogic S905 SOC) with an NXP SGTL5000 codec connected to its I2S input port. Changes from RFC: - Use devm_ variant of snd_soc_register_component in gx_formatte_probe. - Return on failures in aiu_encoder_i2s_set_sysclk. - MCLK and i2s_divider clocks enablement moved from hw_params() to prepare(). Stream's "clk_enabled" is used as simple refcounting to prevent multiple enable/disable. - Improved the mechanism to ensure rate symmetry on the interface. Previous implementation had issues with alsaloop when both capture and playback streams are opened before any hw_params is set on any of them. Thanks to Mark Brown and Jerome Brunet for the review of the RFC. [1]: https://lore.kernel.org/linux-sound/20260411-audin-rfc-v2-0-4c8a6ec5fcab at baylibre.com/ Signed-off-by: Valerio Setti --- Valerio Setti (4): ASoC: meson: gx: add gx-formatter and gx-interface ASoC: meson: aiu-encoder-i2s: use gx_iface and gx_stream structures ASoC: meson: aiu: introduce I2S output formatter ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data sound/soc/meson/Makefile | 2 + sound/soc/meson/aiu-encoder-i2s.c | 250 +++++++++++++++++++++----------- sound/soc/meson/aiu-formatter-i2s.c | 106 ++++++++++++++ sound/soc/meson/aiu.c | 30 +++- sound/soc/meson/aiu.h | 4 + sound/soc/meson/gx-formatter.c | 277 ++++++++++++++++++++++++++++++++++++ sound/soc/meson/gx-formatter.h | 47 ++++++ sound/soc/meson/gx-interface.h | 45 ++++++ 8 files changed, 675 insertions(+), 86 deletions(-) --- base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731 change-id: 20260515-reshape-aiu-as-axg-1dac9037cad3 Best regards, -- Valerio Setti From vsetti at baylibre.com Fri May 15 08:10:37 2026 From: vsetti at baylibre.com (Valerio Setti) Date: Fri, 15 May 2026 17:10:37 +0200 Subject: [PATCH 1/4] ASoC: meson: gx: add gx-formatter and gx-interface In-Reply-To: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> References: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> Message-ID: <20260515-reshape-aiu-as-axg-v1-1-53b457784ff3@baylibre.com> These files are the basic block which allow to shape I2S in GX devices the same as the AXG ones: the DAI backend only controls the interface (i.e. clocks and pins) whereas a formatter takes care of properly formatting the data. gx-formatter and gx-interface are strongly inspired to axg-tdm-formatter and axg-tdm, respectively. The long term plan is to join the two platforms to use the same formatter solution. There is only a minor addition here compared to what has been done for AXG and it's "gx_formatter_create()" which is required in order to let already existing AIU code to make use of this formatter without making any devicetree change. Signed-off-by: Valerio Setti --- sound/soc/meson/Makefile | 1 + sound/soc/meson/gx-formatter.c | 277 +++++++++++++++++++++++++++++++++++++++++ sound/soc/meson/gx-formatter.h | 47 +++++++ sound/soc/meson/gx-interface.h | 45 +++++++ 4 files changed, 370 insertions(+) diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile index 24078e4396b02d545d8ba4bcb1632979001354e3..146ec81526ba091a174a113ce3d8412ddbbfd9dd 100644 --- a/sound/soc/meson/Makefile +++ b/sound/soc/meson/Makefile @@ -4,6 +4,7 @@ snd-soc-meson-aiu-y := aiu.o snd-soc-meson-aiu-y += aiu-acodec-ctrl.o snd-soc-meson-aiu-y += aiu-codec-ctrl.o snd-soc-meson-aiu-y += aiu-encoder-i2s.o +snd-soc-meson-aiu-y += gx-formatter.o snd-soc-meson-aiu-y += aiu-encoder-spdif.o snd-soc-meson-aiu-y += aiu-fifo.o snd-soc-meson-aiu-y += aiu-fifo-i2s.o diff --git a/sound/soc/meson/gx-formatter.c b/sound/soc/meson/gx-formatter.c new file mode 100644 index 0000000000000000000000000000000000000000..db93546ed9217f711fcdbeddbd815ce21f27bab5 --- /dev/null +++ b/sound/soc/meson/gx-formatter.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +// +// Copyright (c) 2026 BayLibre, SAS. +// Author: Valerio Setti + +#include +#include +#include +#include + +#include "gx-formatter.h" + +struct gx_formatter { + struct list_head list; + struct gx_stream *stream; + const struct gx_formatter_driver *drv; + bool enabled; + struct regmap *map; +}; + +static int gx_formatter_enable(struct gx_formatter *formatter) +{ + int ret; + + /* Do nothing if the formatter is already enabled */ + if (formatter->enabled) + return 0; + + /* Setup the stream parameter in the formatter */ + if (formatter->drv->ops->prepare) { + ret = formatter->drv->ops->prepare(formatter->map, + formatter->drv->quirks, + formatter->stream); + if (ret) + return ret; + } + + /* Finally, actually enable the formatter */ + if (formatter->drv->ops->enable) + formatter->drv->ops->enable(formatter->map); + + formatter->enabled = true; + + return 0; +} + +static void gx_formatter_disable(struct gx_formatter *formatter) +{ + /* Do nothing if the formatter is already disabled */ + if (!formatter->enabled) + return; + + if (formatter->drv->ops->disable) + formatter->drv->ops->disable(formatter->map); + + formatter->enabled = false; +} + +static int gx_formatter_attach(struct gx_formatter *formatter) +{ + struct gx_stream *ts = formatter->stream; + int ret = 0; + + mutex_lock(&ts->lock); + + /* Catch up if the stream is already running when we attach */ + if (ts->ready) { + ret = gx_formatter_enable(formatter); + if (ret) { + pr_err("failed to enable formatter\n"); + goto out; + } + } + + list_add_tail(&formatter->list, &ts->formatter_list); +out: + mutex_unlock(&ts->lock); + return ret; +} + +static void gx_formatter_detach(struct gx_formatter *formatter) +{ + struct gx_stream *ts = formatter->stream; + + mutex_lock(&ts->lock); + list_del(&formatter->list); + mutex_unlock(&ts->lock); + + gx_formatter_disable(formatter); +} + +static int gx_formatter_power_up(struct gx_formatter *formatter, + struct snd_soc_dapm_widget *w) +{ + struct gx_stream *ts = formatter->drv->ops->get_stream(w); + int ret; + + /* + * If we don't get a stream at this stage, it would mean that the + * widget is powering up but is not attached to any backend DAI. + * It should not happen, ever ! + */ + if (WARN_ON(!ts)) + return -ENODEV; + + formatter->stream = ts; + ret = gx_formatter_attach(formatter); + if (ret) + return ret; + + return 0; +} + +static void gx_formatter_power_down(struct gx_formatter *formatter) +{ + gx_formatter_detach(formatter); + formatter->stream = NULL; +} + +int gx_formatter_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, + int event) +{ + struct snd_soc_component *c; + struct gx_formatter *formatter; + int ret = 0; + + c = snd_soc_dapm_to_component(w->dapm); + + if (w->priv) + formatter = w->priv; + else + formatter = snd_soc_component_get_drvdata(c); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + ret = gx_formatter_power_up(formatter, w); + break; + + case SND_SOC_DAPM_PRE_PMD: + gx_formatter_power_down(formatter); + break; + + default: + dev_err(c->dev, "Unexpected event %d\n", event); + return -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(gx_formatter_event); + +int gx_formatter_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct gx_formatter_driver *drv; + struct gx_formatter *formatter; + void __iomem *regs; + + drv = of_device_get_match_data(dev); + if (!drv) { + dev_err(dev, "failed to match device\n"); + return -ENODEV; + } + + formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); + if (!formatter) + return -ENOMEM; + platform_set_drvdata(pdev, formatter); + formatter->drv = drv; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + formatter->map = devm_regmap_init_mmio(dev, regs, drv->regmap_cfg); + if (IS_ERR(formatter->map)) { + dev_err(dev, "failed to init regmap: %ld\n", + PTR_ERR(formatter->map)); + return PTR_ERR(formatter->map); + } + + return devm_snd_soc_register_component(dev, drv->component_drv, + NULL, 0); +} +EXPORT_SYMBOL_GPL(gx_formatter_probe); + +int gx_formatter_create(struct device *dev, + struct snd_soc_dapm_widget *w, + const struct gx_formatter_driver *drv, + struct regmap *regmap) +{ + struct gx_formatter *formatter; + + formatter = devm_kzalloc(dev, sizeof(*formatter), GFP_KERNEL); + if (!formatter) + return -ENOMEM; + + formatter->drv = drv; + formatter->map = regmap; + + w->priv = formatter; + + return 0; +} + +int gx_stream_start(struct gx_stream *ts) +{ + struct gx_formatter *formatter; + int ret = 0; + + mutex_lock(&ts->lock); + + /* Start all the formatters attached to the stream */ + list_for_each_entry(formatter, &ts->formatter_list, list) { + ret = gx_formatter_enable(formatter); + if (ret) { + pr_err("failed to start tdm stream\n"); + goto out; + } + } + + ts->ready = true; + +out: + mutex_unlock(&ts->lock); + return ret; +} +EXPORT_SYMBOL_GPL(gx_stream_start); + +void gx_stream_stop(struct gx_stream *ts) +{ + struct gx_formatter *formatter; + + mutex_lock(&ts->lock); + ts->ready = false; + + /* Stop all the formatters attached to the stream */ + list_for_each_entry(formatter, &ts->formatter_list, list) { + gx_formatter_disable(formatter); + } + + mutex_unlock(&ts->lock); +} +EXPORT_SYMBOL_GPL(gx_stream_stop); + +struct gx_stream *gx_stream_alloc(struct gx_iface *iface) +{ + struct gx_stream *ts; + + ts = kzalloc(sizeof(*ts), GFP_KERNEL); + if (ts) { + INIT_LIST_HEAD(&ts->formatter_list); + mutex_init(&ts->lock); + ts->iface = iface; + } + + return ts; +} +EXPORT_SYMBOL_GPL(gx_stream_alloc); + +void gx_stream_free(struct gx_stream *ts) +{ + /* + * If the list is not empty, it would mean that one of the formatter + * widget is still powered and attached to the interface while we + * are removing the TDM DAI. It should not be possible + */ + WARN_ON(!list_empty(&ts->formatter_list)); + mutex_destroy(&ts->lock); + kfree(ts); +} +EXPORT_SYMBOL_GPL(gx_stream_free); + +MODULE_DESCRIPTION("Amlogic GX formatter driver"); +MODULE_AUTHOR("Valerio Setti "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/meson/gx-formatter.h b/sound/soc/meson/gx-formatter.h new file mode 100644 index 0000000000000000000000000000000000000000..05670c3dfb9f43ac3ee959f1d3d11bacee020c43 --- /dev/null +++ b/sound/soc/meson/gx-formatter.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ +/* + * Copyright (c) 2026 Baylibre SAS. + * Author: Valerio Setti + */ + +#ifndef _MESON_GX_FORMATTER_H +#define _MESON_GX_FORMATTER_H + +#include "gx-interface.h" + +struct platform_device; +struct regmap; +struct snd_soc_dapm_widget; +struct snd_kcontrol; + +struct gx_formatter_hw { + unsigned int skew_offset; +}; + +struct gx_formatter_ops { + struct gx_stream *(*get_stream)(struct snd_soc_dapm_widget *w); + void (*enable)(struct regmap *map); + void (*disable)(struct regmap *map); + int (*prepare)(struct regmap *map, + const struct gx_formatter_hw *quirks, + struct gx_stream *ts); +}; + +struct gx_formatter_driver { + const struct snd_soc_component_driver *component_drv; + const struct regmap_config *regmap_cfg; + const struct gx_formatter_ops *ops; + const struct gx_formatter_hw *quirks; +}; + +int gx_formatter_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, + int event); +int gx_formatter_probe(struct platform_device *pdev); + +int gx_formatter_create(struct device *dev, + struct snd_soc_dapm_widget *w, + const struct gx_formatter_driver *drv, + struct regmap *regmap); + +#endif /* _MESON_GX_FORMATTER_H */ diff --git a/sound/soc/meson/gx-interface.h b/sound/soc/meson/gx-interface.h new file mode 100644 index 0000000000000000000000000000000000000000..d9ab894589fa039f7fdd76765390630a2c6d8fbf --- /dev/null +++ b/sound/soc/meson/gx-interface.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ +/* + * Copyright (c) 2026 Baylibre SAS. + * Author: Valerio Setti + */ + +#ifndef _MESON_GX_INTERFACE_H +#define _MESON_GX_INTERFACE_H + +#include +#include +#include +#include +#include + +struct gx_iface { + struct clk *mclk; + unsigned long mclk_rate; + + /* format is common to all the DAIs of the iface */ + unsigned int fmt; + + /* For component wide symmetry */ + int rate; +}; + +struct gx_stream { + struct gx_iface *iface; + struct list_head formatter_list; + struct mutex lock; + unsigned int channels; + unsigned int width; + unsigned int physical_width; + bool ready; + + /* For continuous clock tracking */ + bool clk_enabled; +}; + +struct gx_stream *gx_stream_alloc(struct gx_iface *iface); +void gx_stream_free(struct gx_stream *ts); +int gx_stream_start(struct gx_stream *ts); +void gx_stream_stop(struct gx_stream *ts); + +#endif /* _MESON_GX_INTERFACE_H */ -- 2.39.5 From vsetti at baylibre.com Fri May 15 08:10:39 2026 From: vsetti at baylibre.com (Valerio Setti) Date: Fri, 15 May 2026 17:10:39 +0200 Subject: [PATCH 3/4] ASoC: meson: aiu: introduce I2S output formatter In-Reply-To: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> References: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> Message-ID: <20260515-reshape-aiu-as-axg-v1-3-53b457784ff3@baylibre.com> Introduce aiu-formatter-i2s, a gx_formatter implementation for the AIU I2S playback path. This is going to replace data formatting tasks that are currently being implemented in aiu-encoder-i2s. This should ideally follow the same design pattern used on the AXG platform (see axg-tdmout), but the problem here is that all playback features (including data formatting) so far are implemented in the AIU component. Getting the full AXG design would mean introducing incompatible device-tree changes. Therefore aiu-formatter-i2s is kept very simple and it only implements the bare minimum functionalities to provide I2S playback formatting. It's not a standalone component though because this is still implemented by AIU. Signed-off-by: Valerio Setti --- sound/soc/meson/Makefile | 1 + sound/soc/meson/aiu-formatter-i2s.c | 106 ++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile index 146ec81526ba091a174a113ce3d8412ddbbfd9dd..f9ec0ebb01f048728b8f85fd8e58fb90df990470 100644 --- a/sound/soc/meson/Makefile +++ b/sound/soc/meson/Makefile @@ -5,6 +5,7 @@ snd-soc-meson-aiu-y += aiu-acodec-ctrl.o snd-soc-meson-aiu-y += aiu-codec-ctrl.o snd-soc-meson-aiu-y += aiu-encoder-i2s.o snd-soc-meson-aiu-y += gx-formatter.o +snd-soc-meson-aiu-y += aiu-formatter-i2s.o snd-soc-meson-aiu-y += aiu-encoder-spdif.o snd-soc-meson-aiu-y += aiu-fifo.o snd-soc-meson-aiu-y += aiu-fifo-i2s.o diff --git a/sound/soc/meson/aiu-formatter-i2s.c b/sound/soc/meson/aiu-formatter-i2s.c new file mode 100644 index 0000000000000000000000000000000000000000..c7eff04521de3c282f7f79864143e073ff1b2f27 --- /dev/null +++ b/sound/soc/meson/aiu-formatter-i2s.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2026 BayLibre, SAS. +// Author: Valerio Setti + +#include +#include +#include + +#include "aiu.h" +#include "gx-formatter.h" + +#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) +#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) +#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) +#define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11) +#define AIU_RST_SOFT_I2S_FAST BIT(0) + +#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2) + +static struct snd_soc_dai * +aiu_formatter_i2s_get_be(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_path *p; + struct snd_soc_dai *be; + + snd_soc_dapm_widget_for_each_sink_path(w, p) { + if (!p->connect) + continue; + + if (p->sink->id == snd_soc_dapm_dai_in) + return (struct snd_soc_dai *)p->sink->priv; + + be = aiu_formatter_i2s_get_be(p->sink); + if (be) + return be; + } + + return NULL; +} + +static struct gx_stream * +aiu_formatter_i2s_get_stream(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dai *be = aiu_formatter_i2s_get_be(w); + + if (!be) + return NULL; + + return snd_soc_dai_dma_data_get_playback(be); +} + +static int aiu_formatter_i2s_prepare(struct regmap *map, + const struct gx_formatter_hw *quirks, + struct gx_stream *ts) +{ + /* Always operate in split (classic interleaved) mode */ + unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT; + unsigned int tmp; + + /* Reset required to update the pipeline */ + regmap_write(map, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST); + regmap_read(map, AIU_I2S_SYNC, &tmp); + + switch (ts->physical_width) { + case 16: /* Nothing to do */ + break; + + case 32: + desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | + AIU_I2S_SOURCE_DESC_MODE_32BIT); + break; + + default: + return -EINVAL; + } + + switch (ts->channels) { + case 2: /* Nothing to do */ + break; + case 8: + desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; + break; + default: + return -EINVAL; + } + + regmap_update_bits(map, AIU_I2S_SOURCE_DESC, + AIU_I2S_SOURCE_DESC_MODE_8CH | + AIU_I2S_SOURCE_DESC_MODE_24BIT | + AIU_I2S_SOURCE_DESC_MODE_32BIT | + AIU_I2S_SOURCE_DESC_MODE_SPLIT, + desc); + + /* Send data MSB first */ + regmap_update_bits(map, AIU_I2S_DAC_CFG, + AIU_I2S_DAC_CFG_MSB_FIRST, + AIU_I2S_DAC_CFG_MSB_FIRST); + + return 0; +} + +const struct gx_formatter_ops aiu_formatter_i2s_ops = { + .get_stream = aiu_formatter_i2s_get_stream, + .prepare = aiu_formatter_i2s_prepare, +}; -- 2.39.5 From vsetti at baylibre.com Fri May 15 08:10:40 2026 From: vsetti at baylibre.com (Valerio Setti) Date: Fri, 15 May 2026 17:10:40 +0200 Subject: [PATCH 4/4] ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data In-Reply-To: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> References: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> Message-ID: <20260515-reshape-aiu-as-axg-v1-4-53b457784ff3@baylibre.com> Create a new DAPM widget for "I2S formatter" and place it on the path between FIFO and output DAI interface. Remove I2S output formatting code from aiu-encoder-i2s since it's now implemented from aiu-formatter-i2s. Signed-off-by: Valerio Setti --- sound/soc/meson/aiu-encoder-i2s.c | 56 --------------------------------------- sound/soc/meson/aiu.c | 30 ++++++++++++++++++--- sound/soc/meson/aiu.h | 1 + 3 files changed, 27 insertions(+), 60 deletions(-) diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c index 39accd396affb8beb49fa7cca394244730b24574..9f935a93aeab7a27f880fbde2d29041a4802e3a3 100644 --- a/sound/soc/meson/aiu-encoder-i2s.c +++ b/sound/soc/meson/aiu-encoder-i2s.c @@ -13,13 +13,6 @@ #include "gx-formatter.h" #include "gx-interface.h" -#define AIU_I2S_SOURCE_DESC_MODE_8CH BIT(0) -#define AIU_I2S_SOURCE_DESC_MODE_24BIT BIT(5) -#define AIU_I2S_SOURCE_DESC_MODE_32BIT BIT(9) -#define AIU_I2S_SOURCE_DESC_MODE_SPLIT BIT(11) -#define AIU_RST_SOFT_I2S_FAST BIT(0) - -#define AIU_I2S_DAC_CFG_MSB_FIRST BIT(2) #define AIU_CLK_CTRL_I2S_DIV_EN BIT(0) #define AIU_CLK_CTRL_I2S_DIV GENMASK(3, 2) #define AIU_CLK_CTRL_AOCLK_INVERT BIT(6) @@ -37,49 +30,6 @@ static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component, enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0); } -static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component, - struct snd_pcm_hw_params *params) -{ - /* Always operate in split (classic interleaved) mode */ - unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT; - - /* Reset required to update the pipeline */ - snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST); - snd_soc_component_read(component, AIU_I2S_SYNC); - - switch (params_physical_width(params)) { - case 16: /* Nothing to do */ - break; - - case 32: - desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT | - AIU_I2S_SOURCE_DESC_MODE_32BIT); - break; - - default: - return -EINVAL; - } - - switch (params_channels(params)) { - case 2: /* Nothing to do */ - break; - case 8: - desc |= AIU_I2S_SOURCE_DESC_MODE_8CH; - break; - default: - return -EINVAL; - } - - snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC, - AIU_I2S_SOURCE_DESC_MODE_8CH | - AIU_I2S_SOURCE_DESC_MODE_24BIT | - AIU_I2S_SOURCE_DESC_MODE_32BIT | - AIU_I2S_SOURCE_DESC_MODE_SPLIT, - desc); - - return 0; -} - static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component, struct gx_stream *ts, unsigned int bs) @@ -201,12 +151,6 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, ts->width = params_width(params); ts->channels = params_channels(params); - ret = aiu_encoder_i2s_setup_desc(component, params); - if (ret) { - dev_err(dai->dev, "setting i2s desc failed: %d\n", ret); - return ret; - } - ret = aiu_encoder_i2s_set_clocks(component, ts); if (ret) { dev_err(dai->dev, "setting i2s clocks failed: %d\n", ret); diff --git a/sound/soc/meson/aiu.c b/sound/soc/meson/aiu.c index f2890111c1d2cfa2213bf01849957a796744b9ae..b09c2058eacaf2998d0d3cd6682910f94ec89912 100644 --- a/sound/soc/meson/aiu.c +++ b/sound/soc/meson/aiu.c @@ -29,13 +29,22 @@ static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC, static const struct snd_kcontrol_new aiu_spdif_encode_mux = SOC_DAPM_ENUM("SPDIF Buffer Src", aiu_spdif_encode_sel_enum); -static const struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { - SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, - &aiu_spdif_encode_mux), +#define AIU_WIDGET_SPDIF_SRC_SEL 0 +#define AIU_WIDGET_I2S_FORMATTER 1 + +static struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { + [AIU_WIDGET_SPDIF_SRC_SEL] = + SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, + &aiu_spdif_encode_mux), + [AIU_WIDGET_I2S_FORMATTER] = + SND_SOC_DAPM_PGA_E("I2S Formatter", SND_SOC_NOPM, 0, 0, NULL, 0, + gx_formatter_event, + (SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD)), }; static const struct snd_soc_dapm_route aiu_cpu_dapm_routes[] = { - { "I2S Encoder Playback", NULL, "I2S FIFO Playback" }, + { "I2S Formatter", NULL, "I2S FIFO Playback" }, + { "I2S Encoder Playback", NULL, "I2S Formatter" }, { "SPDIF SRC SEL", "SPDIF", "SPDIF FIFO Playback" }, { "SPDIF SRC SEL", "I2S", "I2S FIFO Playback" }, { "SPDIF Encoder Playback", NULL, "SPDIF SRC SEL" }, @@ -172,6 +181,11 @@ static const struct regmap_config aiu_regmap_cfg = { .max_register = 0x2ac, }; +const struct gx_formatter_driver aiu_formatter_i2s_drv = { + .regmap_cfg = &aiu_regmap_cfg, + .ops = &aiu_formatter_i2s_ops, +}; + static int aiu_clk_bulk_get(struct device *dev, const char * const *ids, unsigned int num, @@ -291,6 +305,14 @@ static int aiu_probe(struct platform_device *pdev) return ret; } + /* Allocate the aiu-formatter into its widget */ + ret = gx_formatter_create(dev, &aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER], + &aiu_formatter_i2s_drv, map); + if (ret) { + dev_err(dev, "Failed to allocate aiu formatter\n"); + goto err; + } + /* Register the hdmi codec control component */ ret = aiu_hdmi_ctrl_register_component(dev); if (ret) { diff --git a/sound/soc/meson/aiu.h b/sound/soc/meson/aiu.h index 68310de0bdf7a97d8de2ff306c159248ee9b0ede..7d0b98c1f351b3c526ca06c43a4c04ee5f4b6dfa 100644 --- a/sound/soc/meson/aiu.h +++ b/sound/soc/meson/aiu.h @@ -61,6 +61,7 @@ extern const struct snd_soc_dai_ops aiu_fifo_i2s_dai_ops; extern const struct snd_soc_dai_ops aiu_fifo_spdif_dai_ops; extern const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops; extern const struct snd_soc_dai_ops aiu_encoder_spdif_dai_ops; +extern const struct gx_formatter_ops aiu_formatter_i2s_ops; #define AIU_IEC958_BPF 0x000 #define AIU_958_MISC 0x010 -- 2.39.5 From sashiko-bot at kernel.org Fri May 15 08:36:51 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Fri, 15 May 2026 15:36:51 +0000 Subject: [PATCH 1/4] ASoC: meson: gx: add gx-formatter and gx-interface In-Reply-To: <20260515-reshape-aiu-as-axg-v1-1-53b457784ff3@baylibre.com> References: <20260515-reshape-aiu-as-axg-v1-1-53b457784ff3@baylibre.com> Message-ID: <20260515153651.965F8C2BCB7@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Critical] Unhandled error paths during widget power-up leave the formatter in an incomplete state, causing deterministic kernel panics during power-down. - [High] Missing rollback of enabled formatters in `gx_stream_start` failure path leads to hardware state leaks. -- commit 37f14cf52933f802dadd48fb50c3876ad1e0e2c5 Author: Valerio Setti Subject: ASoC: meson: gx: add gx-formatter and gx-interface This commit adds basic formatter and interface blocks to shape I2S in GX devices. It introduces gx_formatter_create() to allow existing AIU code to utilize the formatter without devicetree modifications. > diff --git a/sound/soc/meson/gx-formatter.c b/sound/soc/meson/gx-formatter.c > new file mode 100644 > index 0000000000000..db93546ed9217 > --- /dev/null > +++ b/sound/soc/meson/gx-formatter.c [ ... ] > +static int gx_formatter_power_up(struct gx_formatter *formatter, > + struct snd_soc_dapm_widget *w) > +{ > + struct gx_stream *ts = formatter->drv->ops->get_stream(w); > + int ret; > + > + /* > + * If we don't get a stream at this stage, it would mean that the > + * widget is powering up but is not attached to any backend DAI. > + * It should not happen, ever ! > + */ > + if (WARN_ON(!ts)) > + return -ENODEV; Will this cause a NULL pointer dereference later if the warning triggers? Since ASoC DAPM core ignores error codes returned by widget PRE_PMU events, the widget will still be considered powered up and the PRE_PMD event will eventually execute when the stream stops. If this returns -ENODEV here, formatter->stream remains NULL. When PRE_PMD triggers gx_formatter_power_down() and then gx_formatter_detach(), the detach function will attempt to lock ts->lock where ts is NULL. > + > + formatter->stream = ts; > + ret = gx_formatter_attach(formatter); > + if (ret) > + return ret; Could this error path also lead to a crash during the PRE_PMD teardown? If gx_formatter_attach() fails, it skips adding formatter->list to the stream's formatter_list. Later, when the stream stops and PRE_PMD is invoked, gx_formatter_detach() unconditionally calls list_del(&formatter->list). Since the list was never initialized with INIT_LIST_HEAD() or added to the list, calling list_del() on it will dereference uninitialized pointers. > + > + return 0; > +} [ ... ] > +int gx_stream_start(struct gx_stream *ts) > +{ > + struct gx_formatter *formatter; > + int ret = 0; > + > + mutex_lock(&ts->lock); > + > + /* Start all the formatters attached to the stream */ > + list_for_each_entry(formatter, &ts->formatter_list, list) { > + ret = gx_formatter_enable(formatter); > + if (ret) { > + pr_err("failed to start tdm stream\n"); > + goto out; Do we need to disable the formatters that were already successfully enabled in previous iterations of this loop before returning? The ALSA core does not automatically invoke the STOP trigger when a START trigger fails. Returning an error here without rolling back the earlier formatters could leave the hardware clocks and registers permanently enabled, resulting in leaked resources. > + } > + } > + > + ts->ready = true; > + > +out: > + mutex_unlock(&ts->lock); > + return ret; > +} -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260515-reshape-aiu-as-axg-v1-0-53b457784ff3 at baylibre.com?part=1 From sashiko-bot at kernel.org Fri May 15 09:10:47 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Fri, 15 May 2026 16:10:47 +0000 Subject: [PATCH 2/4] ASoC: meson: aiu-encoder-i2s: use gx_iface and gx_stream structures In-Reply-To: <20260515-reshape-aiu-as-axg-v1-2-53b457784ff3@baylibre.com> References: <20260515-reshape-aiu-as-axg-v1-2-53b457784ff3@baylibre.com> Message-ID: <20260515161047.C0701C2BCF7@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider: - [High] Double free / Use-After-Free memory corruption on DAI probe failure path. - [High] Sleeping in atomic context within the PCM trigger callback. - [High] Custom interface rate symmetry logic permanently locks the sample rate. - [High] Incomplete parameter symmetry allows concurrent streams to corrupt the shared hardware clock divider. - [High] Unsafe hardware soft-resets in `hw_params` corrupt active streams. - [Medium] Data race in manual rate symmetry enforcement. - [Medium] Integer underflow when programming the clock divider due to uninitialized sysclk. -- commit ed5bb252934aea5e2205d3b589d2556dcdbed81f Author: Valerio Setti Subject: ASoC: meson: aiu-encoder-i2s: use gx_iface and gx_stream structures This commit transitions the AIU I2S encoder to use gx_iface and gx_stream structures, moving clock handling and rate symmetry into the hw_params and trigger callbacks. > diff --git a/sound/soc/meson/aiu-encoder-i2s.c b/sound/soc/meson/aiu-encoder-i2s.c > index 3b4061508c180..39accd396affb 100644 > --- a/sound/soc/meson/aiu-encoder-i2s.c > +++ b/sound/soc/meson/aiu-encoder-i2s.c [ ... ] > @@ -142,24 +144,18 @@ static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component, > } > > static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component, > - struct snd_pcm_hw_params *params) > + struct gx_stream *ts) > { > struct aiu *aiu = snd_soc_component_get_drvdata(component); > - unsigned int srate = params_rate(params); > unsigned int fs, bs; > int ret; > > /* Get the oversampling factor */ > - fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate); > + fs = DIV_ROUND_CLOSEST(ts->iface->mclk_rate, ts->iface->rate); > > if (fs % 64) > return -EINVAL; If the machine driver does not call aiu_encoder_i2s_set_sysclk(), iface->mclk_rate remains 0. This causes fs to evaluate to 0, which passes the modulo check since 0 % 64 is 0. When bs = fs / 64 (which is 0) is passed into aiu_encoder_i2s_set_more_div(), does bs - 1 underflow to 0xffffffff? If so, FIELD_PREP(AIU_CLK_CTRL_MORE_I2S_DIV, bs - 1) might mask the underflowed value and write a corrupted state to the clock control register. [ ... ] > @@ -188,25 +184,55 @@ static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream, > struct snd_pcm_hw_params *params, > struct snd_soc_dai *dai) > { > + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); > + struct gx_iface *iface = ts->iface; > struct snd_soc_component *component = dai->component; > int ret; > > - /* Disable the clock while changing the settings */ > - aiu_encoder_i2s_divider_enable(component, false); The removal of aiu_encoder_i2s_divider_enable(component, false) means this function now performs pipeline soft-resets (AIU_RST_SOFT_I2S_FAST) and modifies clock dividers while the hardware might be actively running a concurrent stream. Can this corrupt the bit-clock or cause pipeline lockups on the active stream? > + /* Enforce interface wide rate symmetry. */ > + if (iface->rate && (iface->rate != params_rate(params))) { > + dev_err(dai->dev, "can't set iface rate (%d != %d)\n", > + iface->rate, params_rate(params)); > + return -EINVAL; > + } > + > + iface->rate = params_rate(params); > + ts->physical_width = params_physical_width(params); > + ts->width = params_width(params); > + ts->channels = params_channels(params); ALSA permits concurrent hw_params ioctls on independent substreams, such as playback and capture. Since the rate check and assignment are performed without mutual exclusion, could two threads simultaneously see iface->rate as 0 and overwrite each other's configuration? Additionally, this logic enforces symmetry for the sample rate, but ignores channel count and physical width. If a playback stream is actively running at 16-bit 2-channel, and a capture stream is concurrently configured to 16-bit 8-channel, it appears aiu_encoder_i2s_set_more_div() will recalculate and overwrite the shared hardware clock divider AIU_CLK_CTRL_MORE_I2S_DIV. Could this silently corrupt the audio data of the actively playing stream? [ ... ] > static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, > struct snd_soc_dai *dai) > { > struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); > + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); > + struct gx_iface *iface = ts->iface; > + > + if (!snd_soc_dai_active(dai)) > + iface->rate = 0; The ASoC core invokes the shutdown callback before decrementing the stream's active count. Because of this, does snd_soc_dai_active(dai) always evaluate to at least 1 during the shutdown of the final stream? If so, iface->rate will never be reset to 0, which would cause subsequent streams with different sample rates to permanently fail with -EINVAL until the driver is reloaded. [ ... ] > +static int aiu_encoder_i2s_trigger(struct snd_pcm_substream *substream, > + int cmd, > + struct snd_soc_dai *dai) > +{ > + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); > + int ret; > + > + switch (cmd) { > + case SNDRV_PCM_TRIGGER_START: > + case SNDRV_PCM_TRIGGER_RESUME: > + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: > + ret = gx_stream_start(ts); > + break; > + case SNDRV_PCM_TRIGGER_SUSPEND: > + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: > + case SNDRV_PCM_TRIGGER_STOP: > + gx_stream_stop(ts); > + ret = 0; > + break; The PCM trigger callback typically executes in an atomic context with the PCM stream spinlock held and hardware interrupts disabled, unless the DAI is explicitly marked with nonatomic = 1. Looking at gx_stream_start() and gx_stream_stop(), they both unconditionally acquire a sleeping lock via mutex_lock(&ts->lock). Does acquiring a mutex in this atomic context trigger a scheduling while atomic kernel panic? [ ... ] > +static int aiu_encoder_i2s_remove_dai(struct snd_soc_dai *dai) > +{ > + int stream; > + > + for_each_pcm_streams(stream) { > + struct gx_stream *ts = snd_soc_dai_dma_data_get(dai, stream); > + > + if (ts) > + gx_stream_free(ts); > + } > + > + return 0; > +} > + > +static int aiu_encoder_i2s_probe_dai(struct snd_soc_dai *dai) > +{ > + struct aiu *aiu = snd_soc_dai_get_drvdata(dai); > + struct gx_iface *iface = &aiu->i2s.iface; > + int stream; > + > + for_each_pcm_streams(stream) { > + struct gx_stream *ts; > + > + if (!snd_soc_dai_get_widget(dai, stream)) > + continue; > + > + ts = gx_stream_alloc(iface); > + if (!ts) { > + aiu_encoder_i2s_remove_dai(dai); > + return -ENOMEM; > + } > + snd_soc_dai_dma_data_set(dai, stream, ts); > + } If gx_stream_alloc() fails for the second stream in aiu_encoder_i2s_probe_dai(), it calls aiu_encoder_i2s_remove_dai() to clean up the already allocated streams. Since aiu_encoder_i2s_remove_dai() retrieves the gx_stream and frees it, but does not clear the pointer via snd_soc_dai_dma_data_set(dai, stream, NULL), the snd_soc_dai structure will still contain a dangling pointer. If a subsequent deferred probe retry fails allocation again, will remove_dai() fetch the same dangling pointer and pass it to gx_stream_free() a second time, resulting in a double-free memory corruption? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260515-reshape-aiu-as-axg-v1-0-53b457784ff3 at baylibre.com?part=2 From sashiko-bot at kernel.org Fri May 15 10:40:03 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Fri, 15 May 2026 17:40:03 +0000 Subject: [PATCH 4/4] ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data In-Reply-To: <20260515-reshape-aiu-as-axg-v1-4-53b457784ff3@baylibre.com> References: <20260515-reshape-aiu-as-axg-v1-4-53b457784ff3@baylibre.com> Message-ID: <20260515174003.ADB74C2BCB0@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Critical] Modifying a global static DAPM widget template with a devm-managed pointer creates a Use-After-Free vulnerability. - [High] Initialization order flaw between `snd_soc_register_component()` and `gx_formatter_create()` causes a type confusion kernel panic. -- commit a12ba200fe5e3358b227094679f8a17e9ac53591 Author: Valerio Setti ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data This commit introduces a new DAPM widget for the I2S formatter and routes it between the FIFO and output DAI interface. It also removes the existing I2S output formatting code from aiu-encoder-i2s. > diff --git a/sound/soc/meson/aiu.c b/sound/soc/meson/aiu.c > index f2890111c1d2c..b09c2058eacaf 100644 > --- a/sound/soc/meson/aiu.c > +++ b/sound/soc/meson/aiu.c [ ... ] > @@ -29,13 +29,22 @@ static SOC_ENUM_SINGLE_DECL(aiu_spdif_encode_sel_enum, AIU_I2S_MISC, > static const struct snd_kcontrol_new aiu_spdif_encode_mux = > SOC_DAPM_ENUM("SPDIF Buffer Src", aiu_spdif_encode_sel_enum); > > -static const struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { > - SND_SOC_DAPM_MUX("SPDIF SRC SEL", SND_SOC_NOPM, 0, 0, > - &aiu_spdif_encode_mux), > +#define AIU_WIDGET_SPDIF_SRC_SEL 0 > +#define AIU_WIDGET_I2S_FORMATTER 1 > + > +static struct snd_soc_dapm_widget aiu_cpu_dapm_widgets[] = { Is it safe to modify this global static template with a devm-managed pointer later in aiu_probe? If a subsequent initialization step fails and aiu_probe returns an error, the devres subsystem will automatically free the allocated memory. However, the dangling pointer will remain in this global static template. Upon a subsequent probe deferral, will snd_soc_register_component clone this corrupted template, bringing the freed pointer into the active DAPM context and causing a use-after-free when gx_formatter_event dereferences w->priv? [ ... ] > @@ -291,6 +305,14 @@ static int aiu_probe(struct platform_device *pdev) > return ret; > } > > + /* Allocate the aiu-formatter into its widget */ > + ret = gx_formatter_create(dev, &aiu_cpu_dapm_widgets[AIU_WIDGET_I2S_FORMATTER], > + &aiu_formatter_i2s_drv, map); Could this initialization order cause a type confusion panic? snd_soc_register_component is invoked right before this in aiu_probe. If ASoC triggers synchronous card binding upon component registration, the DAPM widgets are cloned from the template while w->priv is still NULL. Later, during audio playback, if gx_formatter_event runs, it checks w->priv, finds it NULL, and executes the fallback path using snd_soc_component_get_drvdata. The component drvdata for the AIU CPU component is explicitly set to struct aiu * via platform_set_drvdata earlier in the probe, not a struct gx_formatter *. Will the code erroneously treat the struct aiu * as a struct gx_formatter * and attempt to dereference its fields, resulting in a panic? > + if (ret) { > + dev_err(dev, "Failed to allocate aiu formatter\n"); > + goto err; > + } > + -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260515-reshape-aiu-as-axg-v1-0-53b457784ff3 at baylibre.com?part=4 From linux-kernel-dev at aliel.fr Sat May 16 08:17:12 2026 From: linux-kernel-dev at aliel.fr (Ronald Claveau) Date: Sat, 16 May 2026 17:17:12 +0200 Subject: [PATCH v5 4/8] mfd: khadas-mcu: Add support for VIM4 MCU variant In-Reply-To: <20260514105459.GJ305027@google.com> References: <20260424-add-mcu-fan-khadas-vim4-v5-0-afcfa7157b23@aliel.fr> <20260424-add-mcu-fan-khadas-vim4-v5-4-afcfa7157b23@aliel.fr> <20260514105459.GJ305027@google.com> Message-ID: <4d4fa560-6115-4065-8472-50f8acb14436@aliel.fr> Thanks for your review Lee. On 5/14/26 12:54 PM, Lee Jones wrote: > On Fri, 24 Apr 2026, Ronald Claveau via B4 Relay wrote: > >> From: Ronald Claveau >> >> Refactor probe() to use per-variant khadas_mcu_data >> instead of hardcoded globals. >> >> Add dedicated regmap configuration and device data for the VIM4 MCU, >> with its own volatile/writeable registers. >> >> Add the fan control register >> (0?100 levels vs 0?3 for previous supported boards). >> >> Add a new compatible string "khadas,vim4-mcu". >> >> Reviewed-by: Neil Armstrong >> Signed-off-by: Ronald Claveau >> --- >> drivers/mfd/khadas-mcu.c | 106 ++++++++++++++++++++++++++++++++++++++++++----- >> 1 file changed, 95 insertions(+), 11 deletions(-) >> >> diff --git a/drivers/mfd/khadas-mcu.c b/drivers/mfd/khadas-mcu.c >> index ba981a7886921..b36b3b3ab73c0 100644 >> --- a/drivers/mfd/khadas-mcu.c >> +++ b/drivers/mfd/khadas-mcu.c >> @@ -75,15 +75,91 @@ static const struct regmap_config khadas_mcu_regmap_config = { >> .cache_type = REGCACHE_MAPLE, >> }; >> >> +static const struct khadas_mcu_fan_pdata khadas_mcu_fan_pdata = { >> + .fan_reg = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG, >> + .max_level = 3, >> +}; > > What is 3? > This is the max fan speed level as defined in the MCU register. I will add it as a comment. /* Fan speed: 0 = off, 1 = low, 2 = medium, 3 = high */ >> + >> static struct mfd_cell khadas_mcu_fan_cells[] = { >> /* VIM1/2 Rev13+ and VIM3 only */ >> - { .name = "khadas-mcu-fan-ctrl", }, >> + { >> + .name = "khadas-mcu-fan-ctrl", >> + .platform_data = &khadas_mcu_fan_pdata, >> + .pdata_size = sizeof(khadas_mcu_fan_pdata), >> + }, >> }; > > Worth making this const at one point. > Yes I will. >> >> static struct mfd_cell khadas_mcu_cells[] = { >> { .name = "khadas-mcu-user-mem", }, >> }; >> I will for that one too. >> +static const struct khadas_mcu_data khadas_mcu_data = { >> + .regmap_config = &khadas_mcu_regmap_config, >> + .cells = khadas_mcu_cells, >> + .ncells = ARRAY_SIZE(khadas_mcu_cells), >> + .fan_cells = khadas_mcu_fan_cells, >> + .nfan_cells = ARRAY_SIZE(khadas_mcu_fan_cells), >> +}; > > This is a red flag! > >> +static bool khadas_mcu_vim4_reg_volatile(struct device *dev, unsigned int reg) >> +{ >> + switch (reg) { >> + case KHADAS_MCU_PWR_OFF_CMD_REG: >> + case KHADAS_MCU_VIM4_REST_CONF_REG: >> + case KHADAS_MCU_WOL_INIT_START_REG: >> + case KHADAS_MCU_VIM4_LED_ON_RAM_REG: >> + case KHADAS_MCU_VIM4_FAN_CTRL_REG: >> + case KHADAS_MCU_VIM4_WDT_EN_REG: >> + case KHADAS_MCU_VIM4_SYS_RST_REG: >> + return true; >> + default: >> + return false; >> + } >> +} >> + >> +static bool khadas_mcu_vim4_reg_writeable(struct device *dev, unsigned int reg) >> +{ >> + switch (reg) { >> + case KHADAS_MCU_VERSION_0_REG: >> + case KHADAS_MCU_VERSION_1_REG: >> + case KHADAS_MCU_SHUTDOWN_NORMAL_STATUS_REG: >> + return false; >> + default: >> + return true; >> + } >> +} >> + >> +static const struct regmap_config khadas_mcu_vim4_regmap_config = { >> + .reg_bits = 8, >> + .reg_stride = 1, >> + .val_bits = 8, >> + .max_register = KHADAS_MCU_VIM4_SYS_RST_REG, >> + .volatile_reg = khadas_mcu_vim4_reg_volatile, >> + .writeable_reg = khadas_mcu_vim4_reg_writeable, >> + .cache_type = REGCACHE_MAPLE, >> +}; >> + >> +static const struct khadas_mcu_fan_pdata khadas_vim4_fan_pdata = { >> + .fan_reg = KHADAS_MCU_VIM4_FAN_CTRL_REG, >> + .max_level = 0x64, >> +}; >> + >> +static const struct mfd_cell khadas_mcu_vim4_cells[] = { >> + { >> + .name = "khadas-mcu-fan-ctrl", >> + .platform_data = &khadas_vim4_fan_pdata, >> + .pdata_size = sizeof(khadas_vim4_fan_pdata), >> + }, >> +}; >> + >> +static const struct khadas_mcu_data khadas_vim4_mcu_data = { >> + .regmap_config = &khadas_mcu_vim4_regmap_config, >> + .cells = NULL, >> + .ncells = 0, >> + .fan_cells = khadas_mcu_vim4_cells, >> + .nfan_cells = ARRAY_SIZE(khadas_mcu_vim4_cells), >> +}; >> + >> static int khadas_mcu_probe(struct i2c_client *client) >> { >> struct device *dev = &client->dev; >> @@ -94,28 +170,35 @@ static int khadas_mcu_probe(struct i2c_client *client) >> if (!ddata) >> return -ENOMEM; >> >> + ddata->data = i2c_get_match_data(client); >> + if (!ddata->data) >> + return -EINVAL; > > Shouldn't this be -ENODEV? > I will change to -ENODEV for the switch default. >> i2c_set_clientdata(client, ddata); >> >> ddata->dev = dev; >> >> - ddata->regmap = devm_regmap_init_i2c(client, &khadas_mcu_regmap_config); >> + ddata->regmap = devm_regmap_init_i2c(client, >> + ddata->data->regmap_config); > > Use up to 100-chars to prevent this kind of wrapping. > Ok will do, is it a general rule or depends on maintainer ? >> if (IS_ERR(ddata->regmap)) { >> ret = PTR_ERR(ddata->regmap); >> dev_err(dev, "Failed to allocate register map: %d\n", ret); >> return ret; >> } > > Maybe convert this to dev_err_probe() at one point. > Ok I change that. >> - ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, >> - khadas_mcu_cells, >> - ARRAY_SIZE(khadas_mcu_cells), >> - NULL, 0, NULL); >> - if (ret) >> - return ret; >> + if (ddata->data->cells && ddata->data->ncells) { >> + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, >> + ddata->data->cells, >> + ddata->data->ncells, >> + NULL, 0, NULL); >> + if (ret) >> + return ret; >> + } >> >> if (of_property_present(dev->of_node, "#cooling-cells")) >> return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, >> - khadas_mcu_fan_cells, >> - ARRAY_SIZE(khadas_mcu_fan_cells), >> + ddata->data->fan_cells, >> + ddata->data->nfan_cells, >> NULL, 0, NULL); >> >> return 0; >> @@ -123,7 +206,8 @@ static int khadas_mcu_probe(struct i2c_client *client) >> >> #ifdef CONFIG_OF >> static const struct of_device_id khadas_mcu_of_match[] = { >> - { .compatible = "khadas,mcu", }, >> + { .compatible = "khadas,mcu", .data = &khadas_mcu_data }, >> + { .compatible = "khadas,vim4-mcu", .data = &khadas_vim4_mcu_data }, > > We don't allow data from one registration API (MFD) to be shoved through > another (DT). Pass a value to match on instead, then use a switch() > statement or similar to populate or register the devices. > Thanks I'm on it. >> {}, >> }; >> MODULE_DEVICE_TABLE(of, khadas_mcu_of_match); >> >> -- >> 2.49.0 >> >> -- Best regards, Ronald From devnull+linux-kernel-dev.aliel.fr at kernel.org Sat May 16 10:17:00 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Sat, 16 May 2026 19:17:00 +0200 Subject: [PATCH v6 0/8] Add VIM4 MCU/FAN support Message-ID: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> The Khadas VIM4 board features a different MCU variant compared to previous VIM boards. While it shares the same I2C-based communication model, it differs in some ways: - A distinct register map with its own volatile/writeable register set - A fan control with 0?100 levels instead of the 0?3 levels previously - A fan power supply gated through a regulator This series adds support for this new variant by: 1. Refactoring the khadas-mcu MFD driver to use per-variant data structures (regmap config, cells, fan platform data), and adding the khadas,vim4-mcu compatible string. 2. Extending the fan thermal driver to retrieve the fan register and maximum level from platform_data, and to optionally manage a power regulator for the fan supply. 3. Adding the corresponding DTS node for the VIM4, wiring the MCU to the I2C AO_A bus and exposing it as a thermal cooling device. Signed-off-by: Ronald Claveau --- Changes in v6: - PATCH 4: Address Lee's review comments: - Use an enum to discriminate between MCU types instead of passing MFD data through the DT match table - Fix error code from -EINVAL to -ENODEV when no MCU type is matched - Make khadas_mcu_fan_cells and khadas_mcu_cells const - Use dev_err_probe() for regmap initialization error - Document fan speed levels for max_level - Link to v5: https://lore.kernel.org/r/20260424-add-mcu-fan-khadas-vim4-v5-0-afcfa7157b23 at aliel.fr Changes in v5: - PATCH 5: Replace devm_regulator_get_optional() with devm_regulator_get() to simplify error handling and remove NULL checks, also ordering as reverse christmas according to Neil's feedback. - Link to v4: https://lore.kernel.org/r/20260421-add-mcu-fan-khadas-vim4-v4-0-447114a28f2d at aliel.fr Changes in v4: - PATCH 1: limit fan-supply property by compatible according to Conor's feedback. - Link to v3: https://lore.kernel.org/r/20260417-add-mcu-fan-khadas-vim4-v3-0-a6a7f570b11b at aliel.fr Changes in v3: - PATCH 1: adding comment on vim4 compatible saying it is not discoverable, thanks to Rob's and Neil's feedback. - Link to v2: https://lore.kernel.org/r/20260403-add-mcu-fan-khadas-vim4-v2-0-70536b22439a at aliel.fr Changes in v2: - PATCH 5: Add regulator_disable on suspend thanks to Neil's feedback. - Link to v1: https://lore.kernel.org/r/20260402-add-mcu-fan-khadas-vim4-v1-0-2b12eb4ac7b0 at aliel.fr --- Ronald Claveau (8): dt-bindings: mfd: khadas: Add new compatible for Khadas VIM4 MCU dt-bindings: i2c: amlogic: Add compatible for T7 SOC mfd: khadas-mcu: Add per-variant configuration infrastructure and VIM4 support mfd: khadas-mcu: Add support for VIM4 MCU variant thermal: khadas-mcu-fan: Add fan config from platform data Add regulator support arm64: dts: amlogic: t7: Add i2c pinctrl node arm64: dts: amlogic: t7: Add i2c controller node arm64: dts: amlogic: t7: khadas-vim4: Add i2c MCU fan node .../bindings/i2c/amlogic,meson6-i2c.yaml | 13 ++- .../devicetree/bindings/mfd/khadas,mcu.yaml | 18 ++++ .../dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 13 +++ arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 20 ++++ drivers/mfd/khadas-mcu.c | 119 ++++++++++++++++++--- drivers/thermal/khadas_mcu_fan.c | 37 +++++-- include/linux/mfd/khadas-mcu.h | 44 +++++++- 7 files changed, 236 insertions(+), 28 deletions(-) --- base-commit: f7b64ed948718290209074a50bb0df17e5944873 change-id: 20260402-add-mcu-fan-khadas-vim4-ac1cbe553c9b prerequisite-message-id: <20260326092645.1053261-1-jian.hu at amlogic.com> prerequisite-patch-id: f03a086b4137158412b2d47b3de793b858de8dde prerequisite-patch-id: 123970c9b29c2090440f2fd71c85d3c6fd8e36de prerequisite-patch-id: 3e2e56b0926ba327b520f935df4ced5089bbe503 prerequisite-patch-id: 65a5d76ffdbc9b3aab3385bb65cb027004c30e7e prerequisite-patch-id: 237269801826dd3ad7fb16eb4d7d6d4eab504278 prerequisite-patch-id: 57e9b08a968aedf543d3d0d56cf1ca4db20b2a16 prerequisite-change-id: 20260326-add-bcm43752-compatible-e264a4f7973a:v2 prerequisite-patch-id: cd98b74fa56af72af2553f391c400981d83cd4f4 prerequisite-patch-id: b730f5e42be1d89d193e63a0265495cdbf2c7d7b prerequisite-change-id: 20260330-fix-invalid-property-bbe54d933f71:v2 prerequisite-patch-id: 8d675e7a239985c762843515b241f0a2f45f9c92 prerequisite-change-id: 20260331-fix-aml-t7-null-reset-2b608ebf9da4:v1 prerequisite-patch-id: 5b5de77af11747ce964404fb827d2ee2bff47ea5 prerequisite-patch-id: 1e37fc75fed1e533adee0f3e7e6ead1f8ff3c55c prerequisite-patch-id: 65a5d76ffdbc9b3aab3385bb65cb027004c30e7e prerequisite-patch-id: 2daf583fb5e7449a02bd217d8aca330171b598aa prerequisite-patch-id: 237269801826dd3ad7fb16eb4d7d6d4eab504278 prerequisite-patch-id: d1ddf9b7710e91f8062de83bd7ba55afb2c4c112 prerequisite-patch-id: 57e9b08a968aedf543d3d0d56cf1ca4db20b2a16 prerequisite-patch-id: cd98b74fa56af72af2553f391c400981d83cd4f4 prerequisite-patch-id: b730f5e42be1d89d193e63a0265495cdbf2c7d7b prerequisite-patch-id: 9debd88fa60febed9cd7208f86603b4c2d270520 prerequisite-patch-id: 314ef9ff0c4d1d15dab1dea9d92aa065f1eac3e9 Best regards, -- Ronald Claveau From devnull+linux-kernel-dev.aliel.fr at kernel.org Sat May 16 10:17:04 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Sat, 16 May 2026 19:17:04 +0200 Subject: [PATCH v6 4/8] mfd: khadas-mcu: Add support for VIM4 MCU variant In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> References: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> Message-ID: <20260516-add-mcu-fan-khadas-vim4-v6-4-cccc9b61f465@aliel.fr> From: Ronald Claveau Refactor probe() to use per-variant khadas_mcu_data instead of hardcoded globals. Add dedicated regmap configuration and device data for the VIM4 MCU, with its own volatile/writeable registers. Add the fan control register (0?100 levels vs 0?3 for previous supported boards). Add a new compatible string "khadas,vim4-mcu". Reviewed-by: Neil Armstrong Signed-off-by: Ronald Claveau --- drivers/mfd/khadas-mcu.c | 119 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 15 deletions(-) diff --git a/drivers/mfd/khadas-mcu.c b/drivers/mfd/khadas-mcu.c index ba981a7886921..7bc538232a445 100644 --- a/drivers/mfd/khadas-mcu.c +++ b/drivers/mfd/khadas-mcu.c @@ -75,15 +75,91 @@ static const struct regmap_config khadas_mcu_regmap_config = { .cache_type = REGCACHE_MAPLE, }; -static struct mfd_cell khadas_mcu_fan_cells[] = { +static const struct khadas_mcu_fan_pdata khadas_mcu_fan_pdata = { + .fan_reg = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG, + .max_level = 3, /* Fan speed: 0 = off, 1 = low, 2 = medium, 3 = high */ +}; + +static const struct mfd_cell khadas_mcu_fan_cells[] = { /* VIM1/2 Rev13+ and VIM3 only */ - { .name = "khadas-mcu-fan-ctrl", }, + { + .name = "khadas-mcu-fan-ctrl", + .platform_data = &khadas_mcu_fan_pdata, + .pdata_size = sizeof(khadas_mcu_fan_pdata), + }, }; -static struct mfd_cell khadas_mcu_cells[] = { +static const struct mfd_cell khadas_mcu_cells[] = { { .name = "khadas-mcu-user-mem", }, }; +static const struct khadas_mcu_data khadas_mcu_data = { + .regmap_config = &khadas_mcu_regmap_config, + .cells = khadas_mcu_cells, + .ncells = ARRAY_SIZE(khadas_mcu_cells), + .fan_cells = khadas_mcu_fan_cells, + .nfan_cells = ARRAY_SIZE(khadas_mcu_fan_cells), +}; + +static bool khadas_mcu_vim4_reg_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case KHADAS_MCU_PWR_OFF_CMD_REG: + case KHADAS_MCU_VIM4_REST_CONF_REG: + case KHADAS_MCU_WOL_INIT_START_REG: + case KHADAS_MCU_VIM4_LED_ON_RAM_REG: + case KHADAS_MCU_VIM4_FAN_CTRL_REG: + case KHADAS_MCU_VIM4_WDT_EN_REG: + case KHADAS_MCU_VIM4_SYS_RST_REG: + return true; + default: + return false; + } +} + +static bool khadas_mcu_vim4_reg_writeable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case KHADAS_MCU_VERSION_0_REG: + case KHADAS_MCU_VERSION_1_REG: + case KHADAS_MCU_SHUTDOWN_NORMAL_STATUS_REG: + return false; + default: + return true; + } +} + +static const struct regmap_config khadas_mcu_vim4_regmap_config = { + .reg_bits = 8, + .reg_stride = 1, + .val_bits = 8, + .max_register = KHADAS_MCU_VIM4_SYS_RST_REG, + .volatile_reg = khadas_mcu_vim4_reg_volatile, + .writeable_reg = khadas_mcu_vim4_reg_writeable, + .cache_type = REGCACHE_MAPLE, +}; + +static const struct khadas_mcu_fan_pdata khadas_vim4_fan_pdata = { + .fan_reg = KHADAS_MCU_VIM4_FAN_CTRL_REG, + .max_level = 0x64, +}; + +static const struct mfd_cell khadas_mcu_vim4_cells[] = { + { + .name = "khadas-mcu-fan-ctrl", + .platform_data = &khadas_vim4_fan_pdata, + .pdata_size = sizeof(khadas_vim4_fan_pdata), + }, +}; + +static const struct khadas_mcu_data khadas_vim4_mcu_data = { + .regmap_config = &khadas_mcu_vim4_regmap_config, + .cells = NULL, + .ncells = 0, + .fan_cells = khadas_mcu_vim4_cells, + .nfan_cells = ARRAY_SIZE(khadas_mcu_vim4_cells), +}; + static int khadas_mcu_probe(struct i2c_client *client) { struct device *dev = &client->dev; @@ -94,28 +170,40 @@ static int khadas_mcu_probe(struct i2c_client *client) if (!ddata) return -ENOMEM; + switch ((uintptr_t)i2c_get_match_data(client)) { + case KHADAS_MCU_GENERIC: + ddata->data = &khadas_mcu_data; + break; + case KHADAS_MCU_VIM4: + ddata->data = &khadas_vim4_mcu_data; + break; + default: + return -ENODEV; + } + i2c_set_clientdata(client, ddata); ddata->dev = dev; - ddata->regmap = devm_regmap_init_i2c(client, &khadas_mcu_regmap_config); + ddata->regmap = devm_regmap_init_i2c(client, ddata->data->regmap_config); if (IS_ERR(ddata->regmap)) { ret = PTR_ERR(ddata->regmap); - dev_err(dev, "Failed to allocate register map: %d\n", ret); - return ret; + return dev_err_probe(dev, ret, "Failed to allocate register map\n"); } - ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, - khadas_mcu_cells, - ARRAY_SIZE(khadas_mcu_cells), - NULL, 0, NULL); - if (ret) - return ret; + if (ddata->data->cells && ddata->data->ncells) { + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, + ddata->data->cells, + ddata->data->ncells, + NULL, 0, NULL); + if (ret) + return ret; + } if (of_property_present(dev->of_node, "#cooling-cells")) return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, - khadas_mcu_fan_cells, - ARRAY_SIZE(khadas_mcu_fan_cells), + ddata->data->fan_cells, + ddata->data->nfan_cells, NULL, 0, NULL); return 0; @@ -123,7 +211,8 @@ static int khadas_mcu_probe(struct i2c_client *client) #ifdef CONFIG_OF static const struct of_device_id khadas_mcu_of_match[] = { - { .compatible = "khadas,mcu", }, + { .compatible = "khadas,mcu", .data = (void *)KHADAS_MCU_GENERIC }, + { .compatible = "khadas,vim4-mcu", .data = (void *)KHADAS_MCU_VIM4 }, {}, }; MODULE_DEVICE_TABLE(of, khadas_mcu_of_match); -- 2.49.0 From devnull+linux-kernel-dev.aliel.fr at kernel.org Sat May 16 10:17:02 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Sat, 16 May 2026 19:17:02 +0200 Subject: [PATCH v6 2/8] dt-bindings: i2c: amlogic: Add compatible for T7 SOC In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> References: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> Message-ID: <20260516-add-mcu-fan-khadas-vim4-v6-2-cccc9b61f465@aliel.fr> From: Ronald Claveau Add the T7 SOC compatible which fallback to AXG compatible. Acked-by: Rob Herring (Arm) Signed-off-by: Ronald Claveau --- .../devicetree/bindings/i2c/amlogic,meson6-i2c.yaml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/i2c/amlogic,meson6-i2c.yaml b/Documentation/devicetree/bindings/i2c/amlogic,meson6-i2c.yaml index c4cc8af182807..7b59b60b62e5b 100644 --- a/Documentation/devicetree/bindings/i2c/amlogic,meson6-i2c.yaml +++ b/Documentation/devicetree/bindings/i2c/amlogic,meson6-i2c.yaml @@ -16,10 +16,15 @@ allOf: properties: compatible: - enum: - - amlogic,meson6-i2c # Meson6, Meson8 and compatible SoCs - - amlogic,meson-gxbb-i2c # GXBB and compatible SoCs - - amlogic,meson-axg-i2c # AXG and compatible SoCs + oneOf: + - items: + - enum: + - amlogic,t7-i2c + - const: amlogic,meson-axg-i2c + - enum: + - amlogic,meson6-i2c # Meson6, Meson8 and compatible SoCs + - amlogic,meson-gxbb-i2c # GXBB and compatible SoCs + - amlogic,meson-axg-i2c # AXG and compatible SoCs reg: maxItems: 1 -- 2.49.0 From devnull+linux-kernel-dev.aliel.fr at kernel.org Sat May 16 10:17:01 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Sat, 16 May 2026 19:17:01 +0200 Subject: [PATCH v6 1/8] dt-bindings: mfd: khadas: Add new compatible for Khadas VIM4 MCU In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> References: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> Message-ID: <20260516-add-mcu-fan-khadas-vim4-v6-1-cccc9b61f465@aliel.fr> From: Ronald Claveau The Khadas VIM4 MCU register is slightly different from previous boards' MCU. This board also features a switchable power source for its fan. Acked-by: Conor Dooley Signed-off-by: Ronald Claveau --- Documentation/devicetree/bindings/mfd/khadas,mcu.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml b/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml index 084960fd5a1fd..1f135618e3b6f 100644 --- a/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml +++ b/Documentation/devicetree/bindings/mfd/khadas,mcu.yaml @@ -18,6 +18,7 @@ properties: compatible: enum: - khadas,mcu # MCU revision is discoverable + - khadas,vim4-mcu # Different MCU variant, not discoverable "#cooling-cells": # Only needed for boards having FAN control feature const: 2 @@ -25,10 +26,27 @@ properties: reg: maxItems: 1 + fan-supply: + description: Phandle to the regulator that powers the fan. + $ref: /schemas/types.yaml#/definitions/phandle + required: - compatible - reg +allOf: + - if: + properties: + compatible: + contains: + const: khadas,vim4-mcu + then: + required: + - fan-supply + else: + properties: + fan-supply: false + additionalProperties: false examples: -- 2.49.0 From devnull+linux-kernel-dev.aliel.fr at kernel.org Sat May 16 10:17:06 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Sat, 16 May 2026 19:17:06 +0200 Subject: [PATCH v6 6/8] arm64: dts: amlogic: t7: Add i2c pinctrl node In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> References: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> Message-ID: <20260516-add-mcu-fan-khadas-vim4-v6-6-cccc9b61f465@aliel.fr> From: Ronald Claveau Add the T7 pinctrl used by the Khadas VIM4 for MCU communication. Reviewed-by: Neil Armstrong Signed-off-by: Ronald Claveau --- arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi index 7fe72c94ed623..e96fe10b251a0 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi @@ -376,6 +376,16 @@ mux { }; }; + i2c0_ao_d_pins: i2c0-ao-d { + mux { + groups = "i2c0_ao_sck_d", + "i2c0_ao_sda_d"; + function = "i2c0_ao"; + bias-disable; + drive-strength-microamp = <3000>; + }; + }; + pwm_a_pins: pwm-a { mux { groups = "pwm_a"; -- 2.49.0 From devnull+linux-kernel-dev.aliel.fr at kernel.org Sat May 16 10:17:05 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Sat, 16 May 2026 19:17:05 +0200 Subject: [PATCH v6 5/8] thermal: khadas-mcu-fan: Add fan config from platform data Add regulator support In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> References: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> Message-ID: <20260516-add-mcu-fan-khadas-vim4-v6-5-cccc9b61f465@aliel.fr> From: Ronald Claveau Replace the hardcoded MAX_LEVEL constant and fan register with values read from platform_data (fan_reg, max_level), as new MCUs need different values. Optionally acquire and enable a "fan" regulator supply at probe time and on resume, so boards that gate fan power through a regulator are handled. Reviewed-by: Neil Armstrong Signed-off-by: Ronald Claveau --- drivers/thermal/khadas_mcu_fan.c | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/drivers/thermal/khadas_mcu_fan.c b/drivers/thermal/khadas_mcu_fan.c index d35e5313bea41..5603fa099a858 100644 --- a/drivers/thermal/khadas_mcu_fan.c +++ b/drivers/thermal/khadas_mcu_fan.c @@ -13,13 +13,15 @@ #include #include #include - -#define MAX_LEVEL 3 +#include struct khadas_mcu_fan_ctx { struct khadas_mcu *mcu; + unsigned int fan_reg; unsigned int level; + unsigned int max_level; struct thermal_cooling_device *cdev; + struct regulator *power; }; static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx, @@ -27,8 +29,7 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx, { int ret; - ret = regmap_write(ctx->mcu->regmap, KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG, - level); + ret = regmap_write(ctx->mcu->regmap, ctx->fan_reg, level); if (ret) return ret; @@ -40,7 +41,9 @@ static int khadas_mcu_fan_set_level(struct khadas_mcu_fan_ctx *ctx, static int khadas_mcu_fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { - *state = MAX_LEVEL; + struct khadas_mcu_fan_ctx *ctx = cdev->devdata; + + *state = ctx->max_level; return 0; } @@ -61,7 +64,7 @@ khadas_mcu_fan_set_cur_state(struct thermal_cooling_device *cdev, { struct khadas_mcu_fan_ctx *ctx = cdev->devdata; - if (state > MAX_LEVEL) + if (state > ctx->max_level) return -EINVAL; if (state == ctx->level) @@ -78,6 +81,7 @@ static const struct thermal_cooling_device_ops khadas_mcu_fan_cooling_ops = { static int khadas_mcu_fan_probe(struct platform_device *pdev) { + const struct khadas_mcu_fan_pdata *pdata = dev_get_platdata(&pdev->dev); struct khadas_mcu *mcu = dev_get_drvdata(pdev->dev.parent); struct thermal_cooling_device *cdev; struct device *dev = &pdev->dev; @@ -87,7 +91,21 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev) ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; + ctx->mcu = mcu; + ctx->fan_reg = pdata->fan_reg; + ctx->max_level = pdata->max_level; + + ctx->power = devm_regulator_get(dev->parent, "fan"); + if (IS_ERR(ctx->power)) + return PTR_ERR(ctx->power); + + ret = regulator_enable(ctx->power); + if (ret) { + dev_err(dev, "Failed to enable fan power supply: %d\n", ret); + return ret; + } + platform_set_drvdata(pdev, ctx); cdev = devm_thermal_of_cooling_device_register(dev->parent, @@ -124,12 +142,17 @@ static int khadas_mcu_fan_suspend(struct device *dev) ctx->level = level_save; - return 0; + return regulator_disable(ctx->power); } static int khadas_mcu_fan_resume(struct device *dev) { struct khadas_mcu_fan_ctx *ctx = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(ctx->power); + if (ret) + return ret; return khadas_mcu_fan_set_level(ctx, ctx->level); } -- 2.49.0 From devnull+linux-kernel-dev.aliel.fr at kernel.org Sat May 16 10:17:07 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Sat, 16 May 2026 19:17:07 +0200 Subject: [PATCH v6 7/8] arm64: dts: amlogic: t7: Add i2c controller node In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> References: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> Message-ID: <20260516-add-mcu-fan-khadas-vim4-v6-7-cccc9b61f465@aliel.fr> From: Ronald Claveau Add the T7 i2c controller node used by the Khadas VIM4 for MCU communication. Use amlogic,meson-axg-i2c as fallback compatible. Reviewed-by: Neil Armstrong Signed-off-by: Ronald Claveau --- arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi index e96fe10b251a0..560c9dce35266 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi @@ -711,6 +711,16 @@ pwm_ao_cd: pwm at 60000 { status = "disabled"; }; + i2c_m_ao_a: i2c at 76000 { + compatible = "amlogic,t7-i2c", "amlogic,meson-axg-i2c"; + reg = <0x0 0x76000 0x0 0x48>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = ; + clocks = <&clkc_periphs CLKID_SYS_I2C_AO_A>; + status = "disabled"; + }; + sd_emmc_a: mmc at 88000 { compatible = "amlogic,t7-mmc", "amlogic,meson-axg-mmc"; reg = <0x0 0x88000 0x0 0x800>; -- 2.49.0 From devnull+linux-kernel-dev.aliel.fr at kernel.org Sat May 16 10:17:08 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Sat, 16 May 2026 19:17:08 +0200 Subject: [PATCH v6 8/8] arm64: dts: amlogic: t7: khadas-vim4: Add i2c MCU fan node In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> References: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> Message-ID: <20260516-add-mcu-fan-khadas-vim4-v6-8-cccc9b61f465@aliel.fr> From: Ronald Claveau Enable and configure i2c MCU node to get fan working on Khadas VIM4. Reviewed-by: Neil Armstrong Signed-off-by: Ronald Claveau --- .../boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts index 69d6118ba57e7..5d7f5390f3a66 100644 --- a/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts +++ b/arch/arm64/boot/dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts @@ -157,6 +157,19 @@ wifi32k: wifi32k { }; }; +&i2c_m_ao_a { + status = "okay"; + pinctrl-0 = <&i2c0_ao_d_pins>; + pinctrl-names = "default"; + + khadas_mcu: system-controller at 18 { + compatible = "khadas,vim4-mcu"; + reg = <0x18>; + fan-supply = <&vcc5v>; + #cooling-cells = <2>; + }; +}; + &pwm_ab { status = "okay"; pinctrl-0 = <&pwm_a_pins>; -- 2.49.0 From devnull+linux-kernel-dev.aliel.fr at kernel.org Sat May 16 10:17:03 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Sat, 16 May 2026 19:17:03 +0200 Subject: [PATCH v6 3/8] mfd: khadas-mcu: Add per-variant configuration infrastructure and VIM4 support In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> References: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> Message-ID: <20260516-add-mcu-fan-khadas-vim4-v6-3-cccc9b61f465@aliel.fr> From: Ronald Claveau Introduce a per-variant configuration structure (khadas_mcu_data) holding the regmap config and MFD cells, selected at probe time via the of_device_id match data. This makes adding other variants straightforward. Add an enum khadas_mcu_type used as value to match. Also introduce khadas_mcu_fan_pdata to pass fan register address and maximum level to the fan sub-driver, removing the hardcoded constants. Reviewed-by: Neil Armstrong Signed-off-by: Ronald Claveau --- include/linux/mfd/khadas-mcu.h | 44 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/include/linux/mfd/khadas-mcu.h b/include/linux/mfd/khadas-mcu.h index a99ba2ed0e4e0..88de49b78f5e6 100644 --- a/include/linux/mfd/khadas-mcu.h +++ b/include/linux/mfd/khadas-mcu.h @@ -70,6 +70,13 @@ #define KHADAS_MCU_WOL_INIT_START_REG 0x87 /* WO */ #define KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG 0x88 /* WO */ +/* VIM4 specific registers */ +#define KHADAS_MCU_VIM4_REST_CONF_REG 0x2c /* WO - reset EEPROM */ +#define KHADAS_MCU_VIM4_LED_ON_RAM_REG 0x89 /* WO - LED volatile */ +#define KHADAS_MCU_VIM4_FAN_CTRL_REG 0x8a /* WO */ +#define KHADAS_MCU_VIM4_WDT_EN_REG 0x8b /* WO */ +#define KHADAS_MCU_VIM4_SYS_RST_REG 0x91 /* WO */ + enum { KHADAS_BOARD_VIM1 = 0x1, KHADAS_BOARD_VIM2, @@ -82,10 +89,43 @@ enum { * struct khadas_mcu - Khadas MCU structure * @device: device reference used for logs * @regmap: register map + * @data: pointer to variant-specific config */ struct khadas_mcu { - struct device *dev; - struct regmap *regmap; + struct device *dev; + struct regmap *regmap; + const struct khadas_mcu_data *data; +}; + +/** + * struct khadas_mcu_data - per-variant configuration + * @regmap_config: regmap configuration + * @cells: MFD sub-devices + * @ncells: number of sub-devices + * @fan_cells: MFD fan sub-devices + * @nfan_cells: number of fan sub-devices + */ +struct khadas_mcu_data { + const struct regmap_config *regmap_config; + const struct mfd_cell *cells; + int ncells; + const struct mfd_cell *fan_cells; + int nfan_cells; +}; + +/** + * struct khadas_mcu_fan_pdata - fan sub-driver configuration + * @fan_reg: register address to write the fan level + * @max_level: maximum fan level + */ +struct khadas_mcu_fan_pdata { + unsigned int fan_reg; + unsigned int max_level; +}; + +enum khadas_mcu_type { + KHADAS_MCU_GENERIC, /* VIM1/2/3, Edge, Edge-V */ + KHADAS_MCU_VIM4, }; #endif /* MFD_KHADAS_MCU_H */ -- 2.49.0 From jonas at kwiboo.se Sat May 16 11:38:11 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:11 +0000 Subject: [PATCH v6 01/22] drm: bridge: dw_hdmi: Disable scrambler feature when not supported In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-2-jonas@kwiboo.se> The scrambler feature can be left enabled when hotplugging from a sink and mode that require scrambling to a sink that does not support SCDC or scrambling. Typically a blank screen or 'no signal' message can be observed after using a HDMI 2.0 4K at 60Hz mode and then hotplugging to a sink that only support HDMI 1.4. Fix this by disabling the scrambler feature when SCDC is not supported. Fixes: 264fce6cc2c1 ("drm/bridge: dw-hdmi: Add SCDC and TMDS Scrambling support") Reported-by: Christopher Obbard Reviewed-by: Neil Armstrong Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v6: Collect t-b tag v5: No change v4: No change v3: Collect r-b tag v2: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 41b3a9cfa2f5..d3e6a6562870 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2135,6 +2135,8 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, HDMI_MC_SWRSTZ); drm_scdc_set_scrambling(hdmi->curr_conn, 0); } + } else if (hdmi->version >= 0x200a) { + hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL); } /* Set up horizontal active pixel width */ -- 2.54.0 From jonas at kwiboo.se Sat May 16 11:38:10 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:10 +0000 Subject: [PATCH v6 00/22] drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup Message-ID: <20260516183838.2024991-1-jonas@kwiboo.se> This is a revival of an old dw-hdmi series and is the first series part of a new effort to upstream old LibreELEC HDMI 2.0 patches for Rockchip RK33xx devices. This series ensure poweron/poweroff and CEC phys addr invalidation is happening during normal DRM funcs, ensures EDID and CEC phys addr is updated in detect() similar to how the bridge connector works with a HDMI bridge attached, and also changes to debounce hotplug processing to prevent a full disable/enable cycle during a HPD low voltage pulse. After this series HPD, EDID and CEC handling should work very similar regardless is the dw-hdmi connector or the bridge connector is used. It should also help ensure a smoother transition when dw-hdmi is fully converted into a HDMI bridge in a future series. These changes have mainly been tested on Rockchip RK3328, RK3399 and RK3568 devices using both the dw-hdmi connector and also using a basic convert to use a bridge connector. The changes has also been tested on a Amlogic S905X device that uses the bridge connector. Testing with a Rock Pi 4 (RK3399) using a Reaspberry Pi Monitor with Linux kms client console using drm.debug=0xe should log something like following: Power cycle monitor using the power button: [CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a [CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz [CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156 [CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 [CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1 [CONNECTOR:68:HDMI-A-1] Same epoch counter 10 Cable unplugged: [CONNECTOR:68:HDMI-A-1] EDID changed, epoch counter 11 [CONNECTOR:68:HDMI-A-1] status updated from connected to disconnected [CONNECTOR:68:HDMI-A-1] Changed epoch counter 10 => 12 [CONNECTOR:68:HDMI-A-1] generating connector hotplug event [CONNECTOR:68:HDMI-A-1] Sent hotplug event Cable connected: [CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a [CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz [CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156 [CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 [CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1 [CONNECTOR:68:HDMI-A-1] status updated from disconnected to connected [CONNECTOR:68:HDMI-A-1] Changed epoch counter 12 => 13 [CONNECTOR:68:HDMI-A-1] generating connector hotplug event [CONNECTOR:68:HDMI-A-1] Sent hotplug event This series has evolved into an initial part of a larger multi series effort to: - drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup [v6] - drm/bridge: dw-hdmi: Improve input/output bus format handling - drm/bridge: dw-hdmi: Convert to a HDMI bridge and use of bridge connector - drm/bridge: dw-hdmi: Add and use tmds_char_rate_valid() plat data ops - drm/meson: hdmi: Misc cleanup and use CEC notifier helpers - phy: rockchip: inno-hdmi: Change TMDS rate handling to configure() ops [v3] - drm/rockchip: dw_hdmi: Misc cleanup and propagate bus format [v1] - drm/rockchip: dw_hdmi: Enable YCbCr and Deep Color modes Link to snapshot: https://github.com/Kwiboo/linux-rockchip/commits/next-20260508-rk-hdmi-v4/ Changes in v6: - Update EDID and CEC phys addr in the bridge detect() func - Add CEC notifier bridge op for the bridge connector - Change back to disable_delayed_work_sync() in hpd disable ops, a possible deadlock is avoided by not using drm_bridge_hpd_notify() - Drop use of a suspend helper now that hpd disable ops use sync() calls - Ensure HPD interrupt is masked and IRQ handler is disabled early in dw_hdmi_remove() to prevent any irq re-arming of delayed work - Update a few commit messages and cover letter - Collect t-b tags Link to v5: https://lore.kernel.org/dri-devel/20260510124111.1226584-1-jonas at kwiboo.se/ Changes in v5: - Add patch that holds a bridge ref until connector cleanup, to fix a use-after-free issue during connector cleanup - Add patch that unregister CEC notifier during connector cleanup - Add patch that adds a common suspend helper - Add patch that drops call to drm_bridge_hpd_notify() - Collect r-b tag - Rebased on next-20260508 Link to v4: https://lore.kernel.org/dri-devel/20260504191059.275928-1-jonas at kwiboo.se/ Changes in v4: - Change to use generic CEC notifier helpers - Disable/mask hpd_work until enable_hpd()/hpd_enable() - Read connector status directly from HW regs in hpd_work - Continued rework of HDP and RXSENSE interrupt handling - Collect r-b tags - Rebased on next-20260430 Link to v3: https://lore.kernel.org/dri-devel/20260403185303.80748-1-jonas at kwiboo.se/ Changes in v3: - Rework EDID refresh handling to closer match bridge connector - Use delayed work to debounce HPD processing - Update commit messages - Collect r-b tags - Rebased on next-20260401 Link to v2: https://lore.kernel.org/dri-devel/20240908132823.3308029-1-jonas at kwiboo.se/ Changes in v2: - Add patch to disable scrambler feature when not supported - Add patch to only notify connected status on HPD interrupt - Update commit messages - Collect r-b tags - Rebased on next-20240906 Link to v1: https://lore.kernel.org/dri-devel/20240611155108.1436502-1-jonas at kwiboo.se/ Jonas Karlman (22): drm: bridge: dw_hdmi: Disable scrambler feature when not supported drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode drm: bridge: dw_hdmi: Fold poweron and setup functions drm: bridge: dw_hdmi: Remove previous_mode and mode_set drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect drm: bridge: dw_hdmi: Remove cec_notifier_mutex drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update() drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() drm: bridge: dw_hdmi: Use generic CEC notifier helpers drm: bridge: dw_hdmi: Update EDID and CEC phys addr in bridge detect() drm: bridge: dw_hdmi: Declare bridge CEC notifier support drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense() drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd() drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 1 - drivers/gpu/drm/bridge/synopsys/Kconfig | 1 + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 498 ++++++++------------ drivers/gpu/drm/meson/meson_dw_hdmi.c | 3 - drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 2 - drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 2 - include/drm/bridge/dw_hdmi.h | 6 - 7 files changed, 187 insertions(+), 326 deletions(-) -- 2.54.0 From jonas at kwiboo.se Sat May 16 11:38:13 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:13 +0000 Subject: [PATCH v6 03/22] drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-4-jonas@kwiboo.se> Change to only call poweron/poweroff from atomic_enable/atomic_disable funcs instead of trying to be clever by keeping a bridge_is_on state and poweron/off in the hotplug IRQ handler. The bridge is already enabled/disabled depending on connection status with the call to drm_helper_hpd_irq_event() in hotplug IRQ handler. Reviewed-by: Neil Armstrong Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v6: Update commit message, Collect t-b tag v5: No change v4: No change v3: Collect r-b tag v2: Update commit message --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 33 ++--------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index b7bfc0e9a6b2..8f7949d2c7f2 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -171,7 +171,6 @@ struct dw_hdmi { 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 bridge_is_on; /* indicates the bridge is on */ bool rxsense; /* rxsense state */ u8 phy_mask; /* desired phy int mask settings */ u8 mc_clkdis; /* clock disable register */ @@ -2400,8 +2399,6 @@ static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi) static void dw_hdmi_poweron(struct dw_hdmi *hdmi) { - hdmi->bridge_is_on = true; - /* * The curr_conn field is guaranteed to be valid here, as this function * is only be called when !hdmi->disabled. @@ -2415,30 +2412,6 @@ static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) hdmi->phy.ops->disable(hdmi, hdmi->phy.data); hdmi->phy.enabled = false; } - - hdmi->bridge_is_on = false; -} - -static void dw_hdmi_update_power(struct dw_hdmi *hdmi) -{ - int force = hdmi->force; - - if (hdmi->disabled) { - force = DRM_FORCE_OFF; - } else if (force == DRM_FORCE_UNSPECIFIED) { - if (hdmi->rxsense) - force = DRM_FORCE_ON; - else - force = DRM_FORCE_OFF; - } - - if (force == DRM_FORCE_OFF) { - if (hdmi->bridge_is_on) - dw_hdmi_poweroff(hdmi); - } else { - if (!hdmi->bridge_is_on) - dw_hdmi_poweron(hdmi); - } } /* @@ -2563,7 +2536,6 @@ static void dw_hdmi_connector_force(struct drm_connector *connector) mutex_lock(&hdmi->mutex); hdmi->force = connector->force; - dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); mutex_unlock(&hdmi->mutex); } @@ -2988,7 +2960,7 @@ static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, mutex_lock(&hdmi->mutex); hdmi->disabled = true; hdmi->curr_conn = NULL; - dw_hdmi_update_power(hdmi); + dw_hdmi_poweroff(hdmi); dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, false); mutex_unlock(&hdmi->mutex); @@ -3006,7 +2978,7 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, mutex_lock(&hdmi->mutex); hdmi->disabled = false; hdmi->curr_conn = connector; - dw_hdmi_update_power(hdmi); + dw_hdmi_poweron(hdmi); dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, true); mutex_unlock(&hdmi->mutex); @@ -3106,7 +3078,6 @@ void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense) if (hpd) hdmi->rxsense = true; - dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); } mutex_unlock(&hdmi->mutex); -- 2.54.0 From jonas at kwiboo.se Sat May 16 11:38:15 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:15 +0000 Subject: [PATCH v6 05/22] drm: bridge: dw_hdmi: Fold poweron and setup functions In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-6-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:14 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:14 +0000 Subject: [PATCH v6 04/22] drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-5-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:20 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:20 +0000 Subject: [PATCH v6 10/22] drm: bridge: dw_hdmi: Remove cec_notifier_mutex In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-11-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:18 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:18 +0000 Subject: [PATCH v6 08/22] drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-9-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:19 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:19 +0000 Subject: [PATCH v6 09/22] drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-10-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:21 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:21 +0000 Subject: [PATCH v6 11/22] drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update() In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-12-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:12 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:12 +0000 Subject: [PATCH v6 02/22] drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-3-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:16 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:16 +0000 Subject: [PATCH v6 06/22] drm: bridge: dw_hdmi: Remove previous_mode and mode_set In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-7-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:25 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:25 +0000 Subject: [PATCH v6 15/22] drm: bridge: dw_hdmi: Declare bridge CEC notifier support In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-16-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 --- 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 From jonas at kwiboo.se Sat May 16 11:38:23 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:23 +0000 Subject: [PATCH v6 13/22] drm: bridge: dw_hdmi: Use generic CEC notifier helpers In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-14-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 #include -#include - #include #include #include +#include #include #include #include @@ -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 From jonas at kwiboo.se Sat May 16 11:38:27 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:27 +0000 Subject: [PATCH v6 17/22] drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-18-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:30 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:30 +0000 Subject: [PATCH v6 20/22] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense() In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-21-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:31 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:31 +0000 Subject: [PATCH v6 21/22] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd() In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-22-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:32 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:32 +0000 Subject: [PATCH v6 22/22] drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-23-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:17 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:17 +0000 Subject: [PATCH v6 07/22] drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-8-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:24 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:24 +0000 Subject: [PATCH v6 14/22] drm: bridge: dw_hdmi: Update EDID and CEC phys addr in bridge detect() In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-15-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 --- 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 From jonas at kwiboo.se Sat May 16 11:38:22 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:22 +0000 Subject: [PATCH v6 12/22] drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-13-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:28 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:28 +0000 Subject: [PATCH v6 18/22] drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-19-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 --- 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 From jonas at kwiboo.se Sat May 16 11:38:26 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:26 +0000 Subject: [PATCH v6 16/22] drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-17-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 Reviewed-by: Nicolas Frattaroli Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From jonas at kwiboo.se Sat May 16 11:38:29 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 18:38:29 +0000 Subject: [PATCH v6 19/22] drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling In-Reply-To: <20260516183838.2024991-1-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> Message-ID: <20260516183838.2024991-20-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- 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 From sashiko-bot at kernel.org Sat May 16 12:07:05 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 16 May 2026 19:07:05 +0000 Subject: [PATCH v6 09/22] drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect In-Reply-To: <20260516183838.2024991-10-jonas@kwiboo.se> References: <20260516183838.2024991-10-jonas@kwiboo.se> Message-ID: <20260516190705.AEFC3C19425@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Moving CEC physical address invalidation exclusively to dw_hdmi_connector_detect breaks CEC functionality when dw_hdmi operates purely as a DRM bridge. -- commit 05206110d2cbe02dc5e2cae3b6c3e7d2d8cdbe48 Author: Jonas Karlman Subject: drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect This commit delays the CEC physical address invalidation from the IRQ handler until the connector detect operation is called. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index 5fd26ff8f55b4..aae1b890167b1 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; > } Does moving the invalidation exclusively to dw_hdmi_connector_detect break CEC functionality when the hardware operates purely as a DRM bridge? Before this change, cec_notifier_phys_addr_invalidate was called directly from the hardware interrupt handler, guaranteeing execution on disconnect regardless of how the driver was configured. In bridge-only mode, the connector is created externally and dw_hdmi_connector_detect is never executed. The DRM core or downstream component instead calls dw_hdmi_bridge_detect, which was not updated here to include the CEC invalidation logic. The CEC physical address might not be invalidated on unplug in bridge mode, which would be a regression. I see this is addressed later in the series by commit 795c172faddab (drm: bridge: dw_hdmi: Update EDID and CEC phys addr in bridge detect()), but this intermediate patch appears to introduce a functional regression that might affect bisection. [ ... ] > @@ -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; -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260516183838.2024991-1-jonas at kwiboo.se?part=9 From jonas at kwiboo.se Sat May 16 12:12:27 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 21:12:27 +0200 Subject: [PATCH v6 09/22] drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect In-Reply-To: <20260516190705.AEFC3C19425@smtp.kernel.org> References: <20260516183838.2024991-10-jonas@kwiboo.se> <20260516190705.AEFC3C19425@smtp.kernel.org> Message-ID: <7122628f-a397-4407-86c7-a3612dc67cb4@kwiboo.se> On 5/16/2026 9:07 PM, sashiko-bot at kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: > - [High] Moving CEC physical address invalidation exclusively to dw_hdmi_connector_detect breaks CEC functionality when dw_hdmi operates purely as a DRM bridge. > -- > > commit 05206110d2cbe02dc5e2cae3b6c3e7d2d8cdbe48 > Author: Jonas Karlman > Subject: drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect > > This commit delays the CEC physical address invalidation from the IRQ > handler until the connector detect operation is called. > >> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >> index 5fd26ff8f55b4..aae1b890167b1 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; >> } > > Does moving the invalidation exclusively to dw_hdmi_connector_detect break > CEC functionality when the hardware operates purely as a DRM bridge? No, hdmi->cec_notifier is going to be NULL when the hardware operates purely as a DRM bridge, CEC notifier was only created for the dw-hdmi connector prior to this series. Regards, Jonas > Before this change, cec_notifier_phys_addr_invalidate was called directly > from the hardware interrupt handler, guaranteeing execution on disconnect > regardless of how the driver was configured. > > In bridge-only mode, the connector is created externally and > dw_hdmi_connector_detect is never executed. The DRM core or downstream > component instead calls dw_hdmi_bridge_detect, which was not updated here > to include the CEC invalidation logic. The CEC physical address might not > be invalidated on unplug in bridge mode, which would be a regression. > > I see this is addressed later in the series by commit 795c172faddab (drm: > bridge: dw_hdmi: Update EDID and CEC phys addr in bridge detect()), but > this intermediate patch appears to introduce a functional regression that > might affect bisection. > > [ ... ] > >> @@ -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; > From sashiko-bot at kernel.org Sat May 16 12:20:00 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 16 May 2026 19:20:00 +0000 Subject: [PATCH v6 13/22] drm: bridge: dw_hdmi: Use generic CEC notifier helpers In-Reply-To: <20260516183838.2024991-14-jonas@kwiboo.se> References: <20260516183838.2024991-14-jonas@kwiboo.se> Message-ID: <20260516192001.27E4EC19425@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Use-After-Free and Memory Leak in CEC notifier cleanup due to drmm and manual lifecycle mismatch -- commit eb59f6fe976b03aa5f719b79a28355b27f403bfe Author: Jonas Karlman Subject: drm: bridge: dw_hdmi: Use generic CEC notifier helpers This commit replaces open-coded CEC notifier handling with generic CEC helpers using drmm_connector_hdmi_cec_notifier_register. It also removes the manual notifier unregistration from dw_hdmi_connector_destroy. > 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. Is this lifecycle assumption accurate for drivers that do not use drmm for mode config? > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c [ ... ] > @@ -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); > } Since drm_connector_cleanup() zeroes the connector memory, and drm_bridge_put() can drop the final reference to the bridge (which frees the dw_hdmi structure and its embedded connector), could this lead to a use-after-free? If a user-space application holds an open file descriptor to the DRM device during driver unbind, the DRM device remains alive. The deferred drmm release action will only execute later when the file descriptor is closed and the DRM device's refcount drops to zero. When the drmm action eventually runs, it will dereference the freed connector pointer. Additionally, because drm_connector_cleanup() executes memset(connector, 0, sizeof(*connector)), won't connector->cec.data become NULL? This would cause the delayed drmm release action to silently skip unregistration, leaking the cec_notifier object. [ ... ] > @@ -2609,15 +2600,8 @@ static int dw_hdmi_connector_create(struct dw_hdmi *hdmi) [ ... ] > - 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); > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260516183838.2024991-1-jonas at kwiboo.se?part=13 From sashiko-bot at kernel.org Sat May 16 12:26:03 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 16 May 2026 19:26:03 +0000 Subject: [PATCH v6 16/22] drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio In-Reply-To: <20260516183838.2024991-17-jonas@kwiboo.se> References: <20260516183838.2024991-17-jonas@kwiboo.se> Message-ID: <20260516192604.A207EC19425@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Direct, lockless access to `connector->display_info` from the `atomic_enable` path creates a race condition with concurrent EDID probes, which can result in incorrectly initializing the display in DVI mode with wrong audio or SCDC settings. -- commit 339e5d6c4cc3c0ee74a9425b8a94dabb6a044d69 Author: Jonas Karlman drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio This commit removes the private sink_is_hdmi and sink_has_audio tracking in dw_hdmi, and switches to using display->is_hdmi and display->has_audio from connector->display_info directly instead. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index 1402b3963ae12..e9c4e24c090ca 100644 > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c [ ... ] > @@ -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 ? Is it safe to read connector->display_info directly in the atomic_enable path? Since dw_hdmi_poweron() and hdmi_av_composer() are called from the bridge's atomic_enable callback, they can run asynchronously for non-blocking commits after the DRM modeset locks have been released. If a concurrent EDID probe triggered by userspace (e.g., via DRM_IOCTL_MODE_GETCONNECTOR) calls drm_edid_connector_update() on another CPU, it starts by calling drm_reset_display_info() which clears display_info.is_hdmi and has_audio to false until the EDID is fully re-parsed. If this code reads the display_info fields during that window, won't it observe transient zeroes and incorrectly program the hardware for DVI mode, as well as disabling SCDC scrambling and audio? Would it be better to capture these properties into an atomic state structure during atomic_check() and read them safely from the state object during atomic_enable()? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260516183838.2024991-1-jonas at kwiboo.se?part=16 From sashiko-bot at kernel.org Sat May 16 12:28:49 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 16 May 2026 19:28:49 +0000 Subject: [PATCH v6 08/22] drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup In-Reply-To: <20260516183838.2024991-9-jonas@kwiboo.se> References: <20260516183838.2024991-9-jonas@kwiboo.se> Message-ID: <20260516192849.E0F9CC19425@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Moving `cec_notifier_conn_unregister` to the connector's `.destroy` callback introduces an asynchronous state corruption vulnerability, breaking CEC functionality on driver rebind. -- commit 8c5f1d529adb3a91696d1e624a0b3dc31d5287ba Author: Jonas Karlman drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup This commit moves the cec_notifier_conn_unregister call from the bridge detach function to the connector destroy function. This is intended to align the lifetime of the connector and the CEC notifier. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index cbbd155780425..5fd26ff8f55b4 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); Could this cause state corruption if a user holds the DRM file descriptor open across a driver unbind/bind cycle? Because the connector destroy callback runs asynchronously when the last DRM file descriptor is closed, its execution can be delayed indefinitely. If the driver is unbound and then rebound while the old file descriptor is still open, a new dw_hdmi is allocated and a new connector is created. When cec_notifier_conn_register is called on rebind, it looks up the notifier by the physical device (hdmi->dev) and shares the exact same cec_notifier with the new active pipeline. When the user eventually closes the old file descriptor, the old connector's destroy callback will fire and call cec_notifier_conn_unregister. This will clear the connection info and invalidate the physical address, which would destroy the active CEC state of the newly bound display pipeline. Should hardware-coupled state like the CEC notifier be invalidated at hardware boundaries like detach, rather than software refcount boundaries like destroy? > + hdmi->cec_notifier = NULL; > + mutex_unlock(&hdmi->cec_notifier_mutex); > + > drm_connector_cleanup(connector); > drm_bridge_put(&hdmi->bridge); > } [ ... ] -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260516183838.2024991-1-jonas at kwiboo.se?part=8 From sashiko-bot at kernel.org Sat May 16 12:30:58 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 16 May 2026 19:30:58 +0000 Subject: [PATCH v6 15/22] drm: bridge: dw_hdmi: Declare bridge CEC notifier support In-Reply-To: <20260516183838.2024991-16-jonas@kwiboo.se> References: <20260516183838.2024991-16-jonas@kwiboo.se> Message-ID: <20260516193059.69570C19425@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The driver unconditionally advertises CEC notifier support even if the CEC platform device fails to register. -- commit 28a90bcf8d7861ee7124899b88745cc0c892f390 Author: Jonas Karlman drm: bridge: dw_hdmi: Declare bridge CEC notifier support This commit updates the bridge to advertise CEC notifier support, instructing the bridge connector to use generic CEC notifier helpers. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index 3649ccf8d994e..1402b3963ae12 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; If platform_device_register_full() fails and returns an error pointer, should we still advertise CEC notifier support? It looks like DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER and hdmi_cec_dev are set unconditionally here. If the CEC device registration fails, could this cause the DRM bridge connector initialization to unnecessarily allocate and register a CEC notifier for a hardware adapter that did not probe? > } > > drm_bridge_add(&hdmi->bridge); -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260516183838.2024991-1-jonas at kwiboo.se?part=15 From sashiko-bot at kernel.org Sat May 16 12:42:59 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 16 May 2026 19:42:59 +0000 Subject: [PATCH v6 18/22] drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event In-Reply-To: <20260516183838.2024991-19-jonas@kwiboo.se> References: <20260516183838.2024991-19-jonas@kwiboo.se> Message-ID: <20260516194259.6D7F1C19425@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Removing the top-level interrupt mute in the remove path leaves already-latched interrupts unmasked, causing an unhandled interrupt storm after the IRQ is freed. -- commit 0025156a478e265d2ea625b34b904b8645d80bed Author: Jonas Karlman drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event This commit uses a delayed work queue to debounce HDMI hotplug detect events, improving the handling of HPD low voltage level pulses. It also updates the driver remove path to explicitly mask the PHY interrupt and free the IRQ handler earlier during cleanup. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c [ ... ] > @@ -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); > - Can removing the top-level interrupt mute cause an unhandled interrupt storm? While writing to HDMI_PHY_MASK0 stops the PHY from latching new interrupts, it does not clear interrupts that are already latched in the HDMI_IH_PHY_STAT0 register. Because the top-level HDMI_IH_MUTE_PHY_STAT0 mute is no longer applied, any previously latched interrupt might continue to assert the physical IRQ line. Immediately after writing the mask earlier in the function, devm_free_irq() removes the driver's handler from the IRQF_SHARED line. With the handler removed, the kernel will not invoke dw_hdmi_hardirq() to clear the device's pending interrupt. Since the interrupt controller might see the line continuously asserted, other drivers sharing the IRQ would return IRQ_NONE. Could this lead to the kernel logging an 'irq nobody cared' error and permanently disabling the shared IRQ line, breaking other devices? Would it be safer to restore the HDMI_IH_MUTE_PHY_STAT0 write to guarantee the IRQ line drops before the handler is freed? > if (hdmi->i2c) > i2c_del_adapter(&hdmi->i2c->adap); > else -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260516183838.2024991-1-jonas at kwiboo.se?part=18 From sashiko-bot at kernel.org Sat May 16 12:43:13 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 16 May 2026 19:43:13 +0000 Subject: [PATCH v6 19/22] drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling In-Reply-To: <20260516183838.2024991-20-jonas@kwiboo.se> References: <20260516183838.2024991-20-jonas@kwiboo.se> Message-ID: <20260516194314.4211FC19425@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Clearing the W1C HPD interrupt after reading the live status and setting the new polarity creates a race condition that can permanently break hotplug detection. -- commit 40973548d3ce7bfe1722c0d53b828f8ef0ca4d81 Author: Jonas Karlman drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling This commit removes RXSENSE handling to simplify the HPD interrupt handling, depending instead on a delayed work to detect connection status or EDID changes. It also ensures the initial HPD interrupt polarity is based on the current HPD status. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index 300dac4a6c358..39e94d22249b2 100644 > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c [ ... ] > @@ -3060,78 +3011,29 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) > > 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); > - > - if ((intr_stat & HDMI_IH_PHY_STAT0_HPD) && > - (phy_stat & HDMI_PHY_HPD)) > - status = connector_status_connected; > + intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); > + if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { > + enum drm_connector_status status; > > - if (!(phy_stat & (HDMI_PHY_HPD | HDMI_PHY_RX_SENSE))) > - status = connector_status_disconnected; > - } > + /* 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 (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); Can this sequence create a race condition that breaks hotplug detection? If the HPD line toggles (for example, due to contact bounce) after dw_hdmi_phy_read_hpd() but before this final hdmi_writeb(), the hardware could generate a new interrupt and set the sticky bit again. Since the final hdmi_writeb() writes the original intr_stat (which has the HPD bit set) back to the register, and the register is write-1-to-clear, it will clear this newly generated interrupt. This could leave the driver and hardware out of sync, with the hardware waiting for a polarity transition that has already occurred, causing all future plug/unplug events to be ignored. Should the interrupt status be cleared before reading the live state and updating the polarity to close this race window? > - 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; > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260516183838.2024991-1-jonas at kwiboo.se?part=19 From jonas at kwiboo.se Sat May 16 12:43:30 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 21:43:30 +0200 Subject: [PATCH v6 13/22] drm: bridge: dw_hdmi: Use generic CEC notifier helpers In-Reply-To: <20260516192001.27E4EC19425@smtp.kernel.org> References: <20260516183838.2024991-14-jonas@kwiboo.se> <20260516192001.27E4EC19425@smtp.kernel.org> Message-ID: <533dd01f-9516-4267-9376-1e5ba42ba124@kwiboo.se> On 5/16/2026 9:20 PM, sashiko-bot at kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: > - [High] Use-After-Free and Memory Leak in CEC notifier cleanup due to drmm and manual lifecycle mismatch > -- > > commit eb59f6fe976b03aa5f719b79a28355b27f403bfe > Author: Jonas Karlman > Subject: drm: bridge: dw_hdmi: Use generic CEC notifier helpers > > This commit replaces open-coded CEC notifier handling with generic CEC > helpers using drmm_connector_hdmi_cec_notifier_register. It also removes > the manual notifier unregistration from dw_hdmi_connector_destroy. > >> 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. > > Is this lifecycle assumption accurate for drivers that do not use drmm > for mode config? Yes, drm_mode_config_init() was converted to call its drmm_ variant in v5.8-rc1, with commit c3b790ea07a1 ("drm: Manage drm_mode_config_init with drmm_"). Effectively meaning that all drm drivers use drmm for mode config. >> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >> --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >> +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > [ ... ] >> @@ -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); >> } > > Since drm_connector_cleanup() zeroes the connector memory, and > drm_bridge_put() can drop the final reference to the bridge (which frees > the dw_hdmi structure and its embedded connector), could this lead to a > use-after-free? It should not, destroy() is the last call made for the connector, and the drm_bridge_put() is here to ensure that the struct dw_hdmi is still around when drm_connector_cleanup() is called, to avoid a use-after-free. > If a user-space application holds an open file descriptor to the DRM device > during driver unbind, the DRM device remains alive. The deferred drmm > release action will only execute later when the file descriptor is closed > and the DRM device's refcount drops to zero. > > When the drmm action eventually runs, it will dereference the freed > connector pointer. > > Additionally, because drm_connector_cleanup() executes > memset(connector, 0, sizeof(*connector)), won't connector->cec.data become > NULL? This would cause the delayed drmm release action to silently skip > unregistration, leaking the cec_notifier object. It should not, drmm release actions are called in reverse order, and drmm_connector_hdmi_cec_notifier_register() is called last, meaning its release action should be called before drm_mode_config_cleanup() is called by the the release action for drmm_mode_config_init(). Regards, Jonas > [ ... ] >> @@ -2609,15 +2600,8 @@ static int dw_hdmi_connector_create(struct dw_hdmi *hdmi) > [ ... ] >> - 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); >> } > From sashiko-bot at kernel.org Sat May 16 12:52:24 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 16 May 2026 19:52:24 +0000 Subject: [PATCH v6 17/22] drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() In-Reply-To: <20260516183838.2024991-18-jonas@kwiboo.se> References: <20260516183838.2024991-18-jonas@kwiboo.se> Message-ID: <20260516195224.9E2CBC19425@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Removing drm_bridge_hpd_notify() violates the DRM bridge API contract and causes hotplug events to be permanently lost during system suspend. -- commit 6b3491e39082015ab62431ea0c93c74c9344b995 Author: Jonas Karlman drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() This commit removes the drm_bridge_hpd_notify() call from the dw_hdmi IRQ handler, relying instead only on drm_helper_hpd_irq_event() to avoid sending duplicate hotplug uevents to userspace. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index e9c4e24c090ca..6cc7b2a860bd8 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); > - } > } Will removing drm_bridge_hpd_notify() cause hotplug events to be permanently lost if they occur during system suspend? Since the driver advertises DRM_BRIDGE_OP_HPD, does the DRM bridge API contract still require it to call drm_bridge_hpd_notify() to properly propagate hotplug events to the rest of the bridge chain? Looking at drm_helper_hpd_irq_event(), it aborts early if polling is disabled: drivers/gpu/drm/drm_probe_helper.c:drm_helper_hpd_irq_event() { struct drm_connector *connector, *first_changed_connector = NULL; struct drm_connector_list_iter conn_iter; int changed = 0; if (!dev->mode_config.poll_enabled) return false; ... } During system suspend, drm_kms_helper_poll_disable() sets poll_enabled to false. If an HPD interrupt fires while the system is suspended, won't drm_helper_hpd_irq_event() silently drop the event without updating the connector status or scheduling any deferred work? Furthermore, when the system resumes, output_poll_execute() explicitly skips connectors that use the DRM_CONNECTOR_POLL_HPD flag, and the dw_hdmi resume handler does not appear to manually trigger a hotplug event check. Could this result in the system being completely unaware of any display connections or disconnections that happened while suspended, at least until a manual reprobe is forced by userspace? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260516183838.2024991-1-jonas at kwiboo.se?part=17 From sashiko-bot at kernel.org Sat May 16 12:52:23 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 16 May 2026 19:52:23 +0000 Subject: [PATCH v6 14/22] drm: bridge: dw_hdmi: Update EDID and CEC phys addr in bridge detect() In-Reply-To: <20260516183838.2024991-15-jonas@kwiboo.se> References: <20260516183838.2024991-15-jonas@kwiboo.se> Message-ID: <20260516195223.C5D23C19425@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Redundant, back-to-back EDID reads over I2C causing significant latency during probe. - [High] Unchecked return value from `drm_edid_raw()` causes a NULL pointer dereference on malformed EDIDs. -- commit 795c172faddaba022a4fd3aecfe0898f0291550f Author: Jonas Karlman drm: bridge: dw_hdmi: Update EDID and CEC phys addr in bridge detect() This commit updates the EDID and CEC physical address in the bridge detect() function. This is intended to closely match the behavior of a bridge connector with an HDMI bridge attached and the dw-hdmi connector. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index 2bb043d64b658..3649ccf8d994e 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; > + > + 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); Does this cause redundant EDID reads over I2C during probe? Because the driver does not set DRM_BRIDGE_OP_HDMI, the DRM core bridge framework (drm_bridge_connector_get_modes_edid) is unaware of this EDID update. Consequently, during a standard modeset probe, the core framework calls detect() again and then edid_read(), resulting in up to three separate I2C EDID reads per probe cycle (one from initial detect, one from get_modes detect, and one from get_modes edid_read). Since DDC reads are slow, could this cause a latency regression? Additionally, could calling dw_hdmi_connector_status_update() here trigger a NULL pointer dereference on malformed EDIDs? dw_hdmi_connector_status_update() calls dw_hdmi_edid_read(), which unconditionally calls drm_edid_raw() and dereferences the result. However, drm_edid_raw() can return NULL for malformed EDIDs where the extension count indicates a size larger than the read buffer. While this behavior existed previously, hooking the EDID read into the detect() callback makes this bug significantly more reachable. Could this cause a kernel panic simply by connecting a monitor with a malformed EDID on hotplug? > > - return dw_hdmi_detect(hdmi); > + return status; > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260516183838.2024991-1-jonas at kwiboo.se?part=14 From jonas at kwiboo.se Sat May 16 12:55:30 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 21:55:30 +0200 Subject: [PATCH v6 19/22] drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling In-Reply-To: <20260516194314.4211FC19425@smtp.kernel.org> References: <20260516183838.2024991-20-jonas@kwiboo.se> <20260516194314.4211FC19425@smtp.kernel.org> Message-ID: On 5/16/2026 9:43 PM, sashiko-bot at kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: > - [High] Clearing the W1C HPD interrupt after reading the live status and setting the new polarity creates a race condition that can permanently break hotplug detection. > -- > > commit 40973548d3ce7bfe1722c0d53b828f8ef0ca4d81 > Author: Jonas Karlman > > drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling > > This commit removes RXSENSE handling to simplify the HPD interrupt > handling, depending instead on a delayed work to detect connection > status or EDID changes. It also ensures the initial HPD interrupt > polarity is based on the current HPD status. > >> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >> index 300dac4a6c358..39e94d22249b2 100644 >> --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >> +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > > [ ... ] > >> @@ -3060,78 +3011,29 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) >> >> 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); >> - >> - if ((intr_stat & HDMI_IH_PHY_STAT0_HPD) && >> - (phy_stat & HDMI_PHY_HPD)) >> - status = connector_status_connected; >> + intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); >> + if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { >> + enum drm_connector_status status; >> >> - if (!(phy_stat & (HDMI_PHY_HPD | HDMI_PHY_RX_SENSE))) >> - status = connector_status_disconnected; >> - } >> + /* 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 (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); > > Can this sequence create a race condition that breaks hotplug detection? > > If the HPD line toggles (for example, due to contact bounce) after > dw_hdmi_phy_read_hpd() but before this final hdmi_writeb(), the hardware > could generate a new interrupt and set the sticky bit again. > > Since the final hdmi_writeb() writes the original intr_stat (which has the > HPD bit set) back to the register, and the register is write-1-to-clear, it > will clear this newly generated interrupt. No, the sticky bit is muted, meaning that no interrupt should be generated until this run has completed, the sticky bit is cleared and then unmuted. Regards, Jonas > This could leave the driver and hardware out of sync, with the hardware > waiting for a polarity transition that has already occurred, causing all > future plug/unplug events to be ignored. > > Should the interrupt status be cleared before reading the live state and > updating the polarity to close this race window? > >> - 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; >> } > From jonas at kwiboo.se Sat May 16 13:00:36 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Sat, 16 May 2026 22:00:36 +0200 Subject: [PATCH v6 17/22] drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() In-Reply-To: <20260516195224.9E2CBC19425@smtp.kernel.org> References: <20260516183838.2024991-18-jonas@kwiboo.se> <20260516195224.9E2CBC19425@smtp.kernel.org> Message-ID: <2ff890b4-ee97-467d-ba27-a3471861ef48@kwiboo.se> On 5/16/2026 9:52 PM, sashiko-bot at kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: > - [High] Removing drm_bridge_hpd_notify() violates the DRM bridge API contract and causes hotplug events to be permanently lost during system suspend. > -- > > commit 6b3491e39082015ab62431ea0c93c74c9344b995 > Author: Jonas Karlman > > drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() > > This commit removes the drm_bridge_hpd_notify() call from the dw_hdmi IRQ > handler, relying instead only on drm_helper_hpd_irq_event() to avoid sending > duplicate hotplug uevents to userspace. > >> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >> index e9c4e24c090ca..6cc7b2a860bd8 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); >> - } >> } > > Will removing drm_bridge_hpd_notify() cause hotplug events to be permanently > lost if they occur during system suspend? No, drm_mode_config_helper_resume() is called at resume. > Since the driver advertises DRM_BRIDGE_OP_HPD, does the DRM bridge API > contract still require it to call drm_bridge_hpd_notify() to properly > propagate hotplug events to the rest of the bridge chain? No, hpd_notify() funcs are still called from bridge connector detect(). > Looking at drm_helper_hpd_irq_event(), it aborts early if polling is > disabled: > > drivers/gpu/drm/drm_probe_helper.c:drm_helper_hpd_irq_event() { > struct drm_connector *connector, *first_changed_connector = NULL; > struct drm_connector_list_iter conn_iter; > int changed = 0; > > if (!dev->mode_config.poll_enabled) > return false; > ... > } > > During system suspend, drm_kms_helper_poll_disable() sets poll_enabled > to false. If an HPD interrupt fires while the system is suspended, won't > drm_helper_hpd_irq_event() silently drop the event without updating the > connector status or scheduling any deferred work? No, drm_mode_config_helper_suspend/resume() will call hpd_enable/disable() to signal when the connector/bridge should start listening for HPD. I.e. not during suspend. > Furthermore, when the system resumes, output_poll_execute() explicitly skips > connectors that use the DRM_CONNECTOR_POLL_HPD flag, and the dw_hdmi resume > handler does not appear to manually trigger a hotplug event check. > > Could this result in the system being completely unaware of any display > connections or disconnections that happened while suspended, at least until > a manual reprobe is forced by userspace? No, drm_mode_config_helper_resume() is called at resume. Regards, Jonas From lgs201920130244 at gmail.com Sun May 17 04:53:43 2026 From: lgs201920130244 at gmail.com (Guangshuo Li) Date: Sun, 17 May 2026 19:53:43 +0800 Subject: [PATCH] media: meson: ge2d: avoid double free on video register failure Message-ID: <20260517115343.955015-1-lgs201920130244@gmail.com> ge2d_probe() allocates a video_device with video_device_alloc() and releases it from the rel_vdev error path if video_register_device() fails. This can double free the video_device when __video_register_device() reaches device_register() and that call fails: video_register_device() -> __video_register_device() -> device_register() fails -> put_device(&vdev->dev) -> v4l2_device_release() -> vdev->release(vdev) -> video_device_release(vdev) ge2d_probe() -> rel_vdev -> video_device_release(ge2d->vfd) Use video_device_release_empty() while registering the device so that registration failure paths do not free ge2d->vfd through vdev->release(). ge2d_probe() then releases ge2d->vfd exactly once from rel_vdev. Restore video_device_release() after successful registration so the registered device keeps its normal lifetime handling. This issue was found by a static analysis tool I am developing. Fixes: 59a635327ca7 ("media: meson: Add M2M driver for the Amlogic GE2D Accelerator Unit") Signed-off-by: Guangshuo Li --- drivers/media/platform/amlogic/meson-ge2d/ge2d.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/platform/amlogic/meson-ge2d/ge2d.c b/drivers/media/platform/amlogic/meson-ge2d/ge2d.c index c5dc03905ce0..b367169e6ad8 100644 --- a/drivers/media/platform/amlogic/meson-ge2d/ge2d.c +++ b/drivers/media/platform/amlogic/meson-ge2d/ge2d.c @@ -983,6 +983,7 @@ static int ge2d_probe(struct platform_device *pdev) } *vfd = ge2d_videodev; + vfd->release = video_device_release_empty; vfd->lock = &ge2d->mutex; vfd->v4l2_dev = &ge2d->v4l2_dev; @@ -1005,6 +1006,7 @@ static int ge2d_probe(struct platform_device *pdev) v4l2_info(&ge2d->v4l2_dev, "Registered %s as /dev/%s\n", vfd->name, video_device_node_name(vfd)); + vfd->release = video_device_release; return 0; -- 2.43.0 From sashiko-bot at kernel.org Sun May 17 05:29:54 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sun, 17 May 2026 12:29:54 +0000 Subject: [PATCH] media: meson: ge2d: avoid double free on video register failure In-Reply-To: <20260517115343.955015-1-lgs201920130244@gmail.com> References: <20260517115343.955015-1-lgs201920130244@gmail.com> Message-ID: <20260517122955.29299C2BCB0@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] The patch's workaround for a double free violates kobject lifecycle rules, introducing a Use-After-Free. - [High] Pre-existing Use-After-Free due to device-managed (devm) memory lifecycle mismatch with asynchronous character device operations. -- commit 7ed6ce0ea116cd8dcd5ff2e1837f5d339cda90fa Author: Guangshuo Li media: meson: ge2d: avoid double free on video register failure This commit attempts to fix a double free when video_register_device fails by temporarily assigning video_device_release_empty to the video device release callback. It restores the original callback if registration succeeds. > diff --git a/drivers/media/platform/amlogic/meson-ge2d/ge2d.c b/drivers/media/platform/amlogic/meson-ge2d/ge2d.c > index c5dc03905ce0f..b367169e6ad84 100644 > --- a/drivers/media/platform/amlogic/meson-ge2d/ge2d.c > +++ b/drivers/media/platform/amlogic/meson-ge2d/ge2d.c > @@ -983,6 +983,7 @@ static int ge2d_probe(struct platform_device *pdev) > } > > *vfd = ge2d_videodev; > + vfd->release = video_device_release_empty; Can this workaround introduce a use-after-free regression in the kobject core? When __video_register_device fails inside device_register, it calls put_device to drop the reference count. The driver core expects the release callback to free the containing structure. By temporarily substituting vfd->release with video_device_release_empty, put_device will not free the memory synchronously. The driver then explicitly frees the video_device in the error path via video_device_release. However, put_device is not guaranteed to execute cleanup synchronously. If asynchronous kobject release debugging is enabled, it schedules cleanup asynchronously on a workqueue. Explicitly freeing the video_device struct immediately after registration fails could destroy the embedded device struct memory while the kobject core still holds an asynchronous reference. > vfd->lock = &ge2d->mutex; > vfd->v4l2_dev = &ge2d->v4l2_dev; Does the use of devm memory for the meson_ge2d struct create a lifecycle mismatch with the video device? The ge2d context is allocated via devm_kzalloc, which automatically frees the memory when the driver unbinds. However, this context embeds fields like v4l2_dev and mutex that are referenced by the video_device. If a user process opens the video character device and holds the file descriptor open, the video_device remains active even after the driver unbinds and the devm memory is freed. If the user later closes the file descriptor, the final reference drop invokes v4l2_release, which dereferences vdev->v4l2_dev and attempts to lock the freed ge2d->mutex, resulting in a use-after-free regression. -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260517115343.955015-1-lgs201920130244 at gmail.com?part=1 From ukleinek at kernel.org Sun May 17 10:12:39 2026 From: ukleinek at kernel.org (Uwe =?utf-8?Q?Kleine-K=C3=B6nig?=) Date: Sun, 17 May 2026 19:12:39 +0200 Subject: [PATCH v2 2/2] pwm: meson: Add support for Amlogic S7 In-Reply-To: <20260402-s6-s7-pwm-v2-2-657dce040956@amlogic.com> References: <20260402-s6-s7-pwm-v2-0-657dce040956@amlogic.com> <20260402-s6-s7-pwm-v2-2-657dce040956@amlogic.com> Message-ID: Hello, On Thu, Apr 02, 2026 at 02:40:16AM +0000, Xianwei Zhao via B4 Relay wrote: > From: Xianwei Zhao > > Add support for Amlogic S7 PWM. Amlogic S7 different from the > previous SoCs, a controller includes one pwm, at the same time, > the controller has only one input clock source. > > Signed-off-by: Xianwei Zhao > --- > drivers/pwm/pwm-meson.c | 32 +++++++++++++++++++++++++++++--- > 1 file changed, 29 insertions(+), 3 deletions(-) > > diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c > index 8c6bf3d49753..7a43c42ef3d6 100644 > --- a/drivers/pwm/pwm-meson.c > +++ b/drivers/pwm/pwm-meson.c > @@ -113,6 +113,7 @@ struct meson_pwm_data { > int (*channels_init)(struct pwm_chip *chip); > bool has_constant; > bool has_polarity; > + bool single_pwm; Conceptually I'd prefer a `npwm` field here. That doesn't take more space in memory and simplifies the logic a bit. (At the cost of having to adapt all already existing meson_pwm_data instances, but that's fine in my book.) > }; > > struct meson_pwm { > @@ -503,6 +504,18 @@ static void meson_pwm_s4_put_clk(void *data) > clk_put(clk); > } > > +static int meson_pwm_init_channels_s7(struct pwm_chip *chip) > +{ > + struct device *dev = pwmchip_parent(chip); > + struct meson_pwm *meson = to_meson_pwm(chip); > + > + meson->channels[0].clk = devm_clk_get(dev, NULL); > + if (IS_ERR(meson->channels[0].clk)) > + return dev_err_probe(dev, PTR_ERR(meson->channels[0].clk), > + "Failed to get clk\n"); > + return 0; > +} > + > static int meson_pwm_init_channels_s4(struct pwm_chip *chip) > { > struct device *dev = pwmchip_parent(chip); > @@ -592,6 +605,13 @@ static const struct meson_pwm_data pwm_s4_data = { > .has_polarity = true, > }; > > +static const struct meson_pwm_data pwm_s7_data = { > + .channels_init = meson_pwm_init_channels_s7, > + .has_constant = true, > + .has_polarity = true, > + .single_pwm = true, > +}; > + > static const struct of_device_id meson_pwm_matches[] = { > { > .compatible = "amlogic,meson8-pwm-v2", > @@ -642,6 +662,10 @@ static const struct of_device_id meson_pwm_matches[] = { > .compatible = "amlogic,meson-s4-pwm", > .data = &pwm_s4_data > }, > + { > + .compatible = "amlogic,s7-pwm", > + .data = &pwm_s7_data > + }, > {}, If you touch that array in the next revision, please make this line: { } (I.e. add a space and drop the comma.) Best regards Uwe -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 488 bytes Desc: not available URL: From xianwei.zhao at amlogic.com Sun May 17 19:54:27 2026 From: xianwei.zhao at amlogic.com (Xianwei Zhao) Date: Mon, 18 May 2026 10:54:27 +0800 Subject: [PATCH v2 2/2] pwm: meson: Add support for Amlogic S7 In-Reply-To: References: <20260402-s6-s7-pwm-v2-0-657dce040956@amlogic.com> <20260402-s6-s7-pwm-v2-2-657dce040956@amlogic.com> Message-ID: <20ce87e8-eaf5-4e64-bb48-c56f33001c47@amlogic.com> Hi Uwe, Thanks for your review. On 2026/5/18 01:12, Uwe Kleine-K?nig wrote: > Hello, > > On Thu, Apr 02, 2026 at 02:40:16AM +0000, Xianwei Zhao via B4 Relay wrote: >> From: Xianwei Zhao >> >> Add support for Amlogic S7 PWM. Amlogic S7 different from the >> previous SoCs, a controller includes one pwm, at the same time, >> the controller has only one input clock source. >> >> Signed-off-by: Xianwei Zhao >> --- >> drivers/pwm/pwm-meson.c | 32 +++++++++++++++++++++++++++++--- >> 1 file changed, 29 insertions(+), 3 deletions(-) >> >> diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c >> index 8c6bf3d49753..7a43c42ef3d6 100644 >> --- a/drivers/pwm/pwm-meson.c >> +++ b/drivers/pwm/pwm-meson.c >> @@ -113,6 +113,7 @@ struct meson_pwm_data { >> int (*channels_init)(struct pwm_chip *chip); >> bool has_constant; >> bool has_polarity; >> + bool single_pwm; > Conceptually I'd prefer a `npwm` field here. That doesn't take more > space in memory and simplifies the logic a bit. (At the cost of having > to adapt all already existing meson_pwm_data instances, but that's fine > in my book.) > I will use npwm(u8) instead of single_pwm. >> }; >> >> struct meson_pwm { >> @@ -503,6 +504,18 @@ static void meson_pwm_s4_put_clk(void *data) >> clk_put(clk); >> } >> >> +static int meson_pwm_init_channels_s7(struct pwm_chip *chip) >> +{ >> + struct device *dev = pwmchip_parent(chip); >> + struct meson_pwm *meson = to_meson_pwm(chip); >> + >> + meson->channels[0].clk = devm_clk_get(dev, NULL); >> + if (IS_ERR(meson->channels[0].clk)) >> + return dev_err_probe(dev, PTR_ERR(meson->channels[0].clk), >> + "Failed to get clk\n"); >> + return 0; >> +} >> + >> static int meson_pwm_init_channels_s4(struct pwm_chip *chip) >> { >> struct device *dev = pwmchip_parent(chip); >> @@ -592,6 +605,13 @@ static const struct meson_pwm_data pwm_s4_data = { >> .has_polarity = true, >> }; >> >> +static const struct meson_pwm_data pwm_s7_data = { >> + .channels_init = meson_pwm_init_channels_s7, >> + .has_constant = true, >> + .has_polarity = true, >> + .single_pwm = true, >> +}; >> + >> static const struct of_device_id meson_pwm_matches[] = { >> { >> .compatible = "amlogic,meson8-pwm-v2", >> @@ -642,6 +662,10 @@ static const struct of_device_id meson_pwm_matches[] = { >> .compatible = "amlogic,meson-s4-pwm", >> .data = &pwm_s4_data >> }, >> + { >> + .compatible = "amlogic,s7-pwm", >> + .data = &pwm_s7_data >> + }, >> {}, > If you touch that array in the next revision, please make this line: > > { } > > (I.e. add a space and drop the comma.) > Will do. > Best regards > Uwe From xianwei.zhao at amlogic.com Sun May 17 20:05:15 2026 From: xianwei.zhao at amlogic.com (Xianwei Zhao) Date: Mon, 18 May 2026 11:05:15 +0800 Subject: [PATCH v2 2/2] pwm: meson: Add support for Amlogic S7 In-Reply-To: References: <20260402-s6-s7-pwm-v2-0-657dce040956@amlogic.com> <20260402-s6-s7-pwm-v2-2-657dce040956@amlogic.com> Message-ID: <56112c57-ed8c-40c5-98ca-f08b2f0ecb94@amlogic.com> Hi Uwe, Thanks for your review. On 2026/5/18 01:12, Uwe Kleine-K?nig wrote: > Subject: > Re: [PATCH v2 2/2] pwm: meson: Add support for Amlogic S7 > From: > Uwe Kleine-K?nig > Date: > 2026/5/18 01:12 > > To: > xianwei.zhao at amlogic.com > CC: > Rob Herring , Krzysztof Kozlowski , > Conor Dooley , Heiner Kallweit > , Neil Armstrong , > Kevin Hilman , Jerome Brunet > , Martin Blumenstingl > , linux-pwm at vger.kernel.org, > devicetree at vger.kernel.org, linux-kernel at vger.kernel.org, > linux-arm-kernel at lists.infradead.org, linux-amlogic at lists.infradead.org > > > > Hello, > > On Thu, Apr 02, 2026 at 02:40:16AM +0000, Xianwei Zhao via B4 Relay wrote: >> From: Xianwei Zhao >> >> Add support for Amlogic S7 PWM. Amlogic S7 different from the >> previous SoCs, a controller includes one pwm, at the same time, >> the controller has only one input clock source. >> >> Signed-off-by: Xianwei Zhao >> --- >> drivers/pwm/pwm-meson.c | 32 +++++++++++++++++++++++++++++--- >> 1 file changed, 29 insertions(+), 3 deletions(-) >> >> diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c >> index 8c6bf3d49753..7a43c42ef3d6 100644 >> --- a/drivers/pwm/pwm-meson.c >> +++ b/drivers/pwm/pwm-meson.c >> @@ -113,6 +113,7 @@ struct meson_pwm_data { >> int (*channels_init)(struct pwm_chip *chip); >> bool has_constant; >> bool has_polarity; >> + bool single_pwm; > Conceptually I'd prefer a `npwm` field here. That doesn't take more > space in memory and simplifies the logic a bit. (At the cost of having > to adapt all already existing meson_pwm_data instances, but that's fine > in my book.) > I will use npwm(u8 type) instead of single_pwm. >> }; >> >> struct meson_pwm { >> @@ -503,6 +504,18 @@ static void meson_pwm_s4_put_clk(void *data) >> clk_put(clk); >> } >> >> +static int meson_pwm_init_channels_s7(struct pwm_chip *chip) >> +{ >> + struct device *dev = pwmchip_parent(chip); >> + struct meson_pwm *meson = to_meson_pwm(chip); >> + >> + meson->channels[0].clk = devm_clk_get(dev, NULL); >> + if (IS_ERR(meson->channels[0].clk)) >> + return dev_err_probe(dev, PTR_ERR(meson->channels[0].clk), >> + "Failed to get clk\n"); >> + return 0; >> +} >> + >> static int meson_pwm_init_channels_s4(struct pwm_chip *chip) >> { >> struct device *dev = pwmchip_parent(chip); >> @@ -592,6 +605,13 @@ static const struct meson_pwm_data pwm_s4_data = { >> .has_polarity = true, >> }; >> >> +static const struct meson_pwm_data pwm_s7_data = { >> + .channels_init = meson_pwm_init_channels_s7, >> + .has_constant = true, >> + .has_polarity = true, >> + .single_pwm = true, >> +}; >> + >> static const struct of_device_id meson_pwm_matches[] = { >> { >> .compatible = "amlogic,meson8-pwm-v2", >> @@ -642,6 +662,10 @@ static const struct of_device_id meson_pwm_matches[] = { >> .compatible = "amlogic,meson-s4-pwm", >> .data = &pwm_s4_data >> }, >> + { >> + .compatible = "amlogic,s7-pwm", >> + .data = &pwm_s7_data >> + }, >> {}, > If you touch that array in the next revision, please make this line: > > { } > > (I.e. add a space and drop the comma.) > Will do. > Best regards > Uwe From lgs201920130244 at gmail.com Sun May 17 22:43:18 2026 From: lgs201920130244 at gmail.com (Guangshuo Li) Date: Mon, 18 May 2026 13:43:18 +0800 Subject: [PATCH] media: meson: vdec: avoid double free on video register failure Message-ID: <20260518054318.979147-1-lgs201920130244@gmail.com> vdec_probe() allocates a video_device with video_device_alloc() and releases it from the err_vdev_release error path if video_register_device() fails. This can double free the video_device when __video_register_device() reaches device_register() and that call fails: video_register_device() -> __video_register_device() -> device_register() fails -> put_device(&vdev->dev) -> v4l2_device_release() -> vdev->release(vdev) -> video_device_release(vdev) vdec_probe() -> err_vdev_release -> video_device_release(vdev) Use video_device_release_empty() while registering the device so that registration failure paths do not free vdev through vdev->release(). vdec_probe() then releases vdev exactly once from err_vdev_release. Restore video_device_release() after successful registration so the registered device keeps its normal lifetime handling. This issue was found by a static analysis tool I am developing. Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Guangshuo Li --- drivers/staging/media/meson/vdec/vdec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 4b77ec1af5a7..2994e46cbc25 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -1074,7 +1074,7 @@ static int vdec_probe(struct platform_device *pdev) mutex_init(&core->lock); strscpy(vdev->name, "meson-video-decoder", sizeof(vdev->name)); - vdev->release = video_device_release; + vdev->release = video_device_release_empty; vdev->fops = &vdec_fops; vdev->ioctl_ops = &vdec_ioctl_ops; vdev->vfl_dir = VFL_DIR_M2M; @@ -1089,6 +1089,7 @@ static int vdec_probe(struct platform_device *pdev) dev_err(dev, "Failed registering video device\n"); goto err_vdev_release; } + vdev->release = video_device_release; return 0; -- 2.43.0 From sashiko-bot at kernel.org Sun May 17 23:10:04 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 18 May 2026 06:10:04 +0000 Subject: [PATCH] media: meson: vdec: avoid double free on video register failure In-Reply-To: <20260518054318.979147-1-lgs201920130244@gmail.com> References: <20260518054318.979147-1-lgs201920130244@gmail.com> Message-ID: <20260518061005.B9EB4C2BCB7@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Critical] Bypassing kobject lifetime management via a dummy release function creates a Use-After-Free (UAF) vulnerability. -- commit c9bf433f66378b70af4be79f5c3eedbee4a4582b Author: Guangshuo Li media: meson: vdec: avoid double free on video register failure This commit temporarily sets the video device release function to video_device_release_empty before calling video_register_device() to prevent a double-free on failure. It restores the original release function upon success. > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index 4b77ec1af5a76..2994e46cbc259 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c > @@ -1074,7 +1074,7 @@ static int vdec_probe(struct platform_device *pdev) > mutex_init(&core->lock); > > strscpy(vdev->name, "meson-video-decoder", sizeof(vdev->name)); > - vdev->release = video_device_release; > + vdev->release = video_device_release_empty; > vdev->fops = &vdec_fops; > vdev->ioctl_ops = &vdec_ioctl_ops; > vdev->vfl_dir = VFL_DIR_M2M; > @@ -1089,6 +1089,7 @@ static int vdec_probe(struct platform_device *pdev) > dev_err(dev, "Failed registering video device\n"); > goto err_vdev_release; When video_register_device() invokes device_register(), the embedded struct device is initialized and can potentially be exposed to the system via sysfs. If registration fails, put_device() is called to drop the reference, but this does not guarantee immediate synchronous destruction. Concurrent subsystems might temporarily hold references, or if CONFIG_DEBUG_KOBJECT_RELEASE is enabled, the kobject release is scheduled on a workqueue. Will bypassing the kobject lifetime management here by setting a dummy release function and synchronously freeing the device in err_vdev_release lead to a use-after-free? Once the delayed kobject cleanup runs or concurrent holders drop their references, the kernel could access the already-freed struct device within the freed vdev to perform internal kobject teardown. > } > + vdev->release = video_device_release; > > return 0; -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260518054318.979147-1-lgs201920130244 at gmail.com?part=1 From sashiko-bot at kernel.org Mon May 18 01:20:22 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 18 May 2026 08:20:22 +0000 Subject: [PATCH] PCI: meson: Propagate devm_add_action_or_reset() failure In-Reply-To: <177909148011.9588.6639767953842842291@gmail.com> References: <177909148011.9588.6639767953842842291@gmail.com> Message-ID: <20260518082022.F4229C2BCB7@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Missing `.remove` callback or `.suppress_bind_attrs = true` leaves PCI subsystem and PHY active after driver unbind. -- commit fde9e1cd9d05730f72b074fa8edf3a1233d11384 Author: Shuvam Pandey PCI: meson: Propagate devm_add_action_or_reset() failure This commit updates meson_pcie_probe_clock() to check the return value of devm_add_action_or_reset(). If it fails, the error is returned so that the probe error path can unwind normally. > diff --git a/drivers/pci/controller/dwc/pci-meson.c b/drivers/pci/controller/dwc/pci-meson.c > index 0694084f612b7..8d495bcc3a41a 100644 > --- a/drivers/pci/controller/dwc/pci-meson.c > +++ b/drivers/pci/controller/dwc/pci-meson.c > @@ -204,7 +204,9 @@ static inline struct clk *meson_pcie_probe_clock(struct device *dev, > return ERR_PTR(ret); > } > > - devm_add_action_or_reset(dev, meson_pcie_disable_clock, clk); > + ret = devm_add_action_or_reset(dev, meson_pcie_disable_clock, clk); > + if (ret) > + return ERR_PTR(ret); This isn't a bug introduced by this patch, but could this driver be unbound via sysfs, triggering a use-after-free? The meson_pcie_probe() function registers the PCI host controller via dw_pcie_host_init(&pci->pp) and powers on the PHY via meson_pcie_power_on(). However, the driver does not implement a .remove callback to call dw_pcie_host_deinit() and meson_pcie_power_off(), nor does it set .suppress_bind_attrs = true in its platform_driver struct. If the driver is unbound via /sys/bus/platform/drivers/meson-pcie/unbind, the device core will trigger devres cleanup. This frees the mp structure, unmaps IO memory, disables clocks, and releases resets. Because the PCI subsystem is never explicitly unregistered via dw_pcie_host_deinit(), the PCI core retains references to the destroyed root bus and freed structures. Can a subsequent access by the PCI subsystem or userspace cause a use-after-free and system crash? Additionally, the PHY is left permanently powered on without its required clocks. Should the driver either implement a proper teardown in a .remove hook or prevent unbinding by setting .suppress_bind_attrs = true? > > return clk; > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/177909148011.9588.6639767953842842291 at gmail.com?part=1 From devnull+xianwei.zhao.amlogic.com at kernel.org Mon May 18 01:26:20 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Mon, 18 May 2026 08:26:20 +0000 Subject: [PATCH] pinctrl: meson: amlogic-a4: fix gpio output glitch Message-ID: <20260518-fix-set-value-glitch-v1-1-d350732dc934@amlogic.com> From: Xianwei Zhao When the system transitions from bootloader to kernel, the GPIO is expected to keep driving high. However, the Linux kernel first configures the pin direction and then sets the output value. This may cause a brief low-level glitch on the GPIO line, which can be problematic for regulator control. By configuring the output value before switching the pin direction to output, the glitch can be avoided. This commit fixes the issue by swapping the configuration order. Fixes: 6e9be3abb78c ("pinctrl: Add driver support for Amlogic SoCs") Signed-off-by: Xianwei Zhao --- fix one issue when set gpio line high. --- drivers/pinctrl/meson/pinctrl-amlogic-a4.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/pinctrl/meson/pinctrl-amlogic-a4.c b/drivers/pinctrl/meson/pinctrl-amlogic-a4.c index 35d27626a336..1bd58fbbd26a 100644 --- a/drivers/pinctrl/meson/pinctrl-amlogic-a4.c +++ b/drivers/pinctrl/meson/pinctrl-amlogic-a4.c @@ -548,11 +548,11 @@ static int aml_pinconf_set_output_drive(struct aml_pinctrl *info, { int ret; - ret = aml_pinconf_set_output(info, pin, true); + ret = aml_pinconf_set_drive(info, pin, high); if (ret) return ret; - return aml_pinconf_set_drive(info, pin, high); + return aml_pinconf_set_output(info, pin, true); } static int aml_pinconf_set(struct pinctrl_dev *pcdev, unsigned int pin, @@ -921,15 +921,14 @@ static int aml_gpio_direction_output(struct gpio_chip *chip, unsigned int gpio, unsigned int bit, reg; int ret; - aml_gpio_calc_reg_and_bit(bank, AML_REG_DIR, gpio, ®, &bit); - ret = regmap_update_bits(bank->reg_gpio, reg, BIT(bit), 0); + aml_gpio_calc_reg_and_bit(bank, AML_REG_OUT, gpio, ®, &bit); + ret = regmap_update_bits(bank->reg_gpio, reg, BIT(bit), + value ? BIT(bit) : 0); if (ret < 0) return ret; - aml_gpio_calc_reg_and_bit(bank, AML_REG_OUT, gpio, ®, &bit); - - return regmap_update_bits(bank->reg_gpio, reg, BIT(bit), - value ? BIT(bit) : 0); + aml_gpio_calc_reg_and_bit(bank, AML_REG_DIR, gpio, ®, &bit); + return regmap_update_bits(bank->reg_gpio, reg, BIT(bit), 0); } static int aml_gpio_set(struct gpio_chip *chip, unsigned int gpio, int value) --- base-commit: 73d4991a6949eedb51e442d4e81415017d85975b change-id: 20260518-fix-set-value-glitch-f43cd366c295 Best regards, -- Xianwei Zhao From sashiko-bot at kernel.org Mon May 18 01:44:09 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 18 May 2026 08:44:09 +0000 Subject: [PATCH] pinctrl: meson: amlogic-a4: fix gpio output glitch In-Reply-To: <20260518-fix-set-value-glitch-v1-1-d350732dc934@amlogic.com> References: <20260518-fix-set-value-glitch-v1-1-d350732dc934@amlogic.com> Message-ID: <20260518084410.74AC9C2BCB7@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Incomplete fix: The GPIO output glitch bug is identically present in the core `pinctrl-meson.c` driver, leaving other Amlogic SoCs vulnerable. - [High] AA deadlock in `aml_pinconf_set_*` functions triggerable from userspace via GPIO character device. -- commit 91d93a06a0e9681b94621504af7968b8a6f4f155 Author: Xianwei Zhao pinctrl: meson: amlogic-a4: fix gpio output glitch This commit fixes a brief low-level glitch on GPIO lines during the transition from bootloader to kernel. It does this by swapping the configuration order to set the output value before switching the pin direction to output. > diff --git a/drivers/pinctrl/meson/pinctrl-amlogic-a4.c b/drivers/pinctrl/meson/pinctrl-amlogic-a4.c > index 35d27626a336b..1bd58fbbd26ac 100644 > --- a/drivers/pinctrl/meson/pinctrl-amlogic-a4.c > +++ b/drivers/pinctrl/meson/pinctrl-amlogic-a4.c > @@ -548,11 +548,11 @@ static int aml_pinconf_set_output_drive(struct aml_pinctrl *info, > { > int ret; > > - ret = aml_pinconf_set_output(info, pin, true); > + ret = aml_pinconf_set_drive(info, pin, high); Does this call chain trigger an AA deadlock when configured at runtime? When a GPIO line's configuration is changed from userspace (for example, via the GPIO_V2_LINE_SET_CONFIG_IOCTL), the core pinctrl_gpio_set_config() function acquires pctldev->mutex before invoking the .pin_config_set callback. If aml_pinconf_set_drive() calls aml_pinconf_set_gpio_bit(), and that function still uses pinctrl_find_gpio_range_from_pin(), it will attempt to acquire the same pctldev->mutex. A previous commit fixed a similar deadlock for the _get functions by switching to pinctrl_find_gpio_range_from_pin_nolock(). Do the _set functions need the same update? Additionally, while this resolves the glitch for the amlogic-a4 driver, does the core pinctrl-meson.c driver have the same issue? Looking at meson_pinconf_set_output_drive() in the core driver, it appears to still call meson_pinconf_set_output() before meson_pinconf_set_drive(). Could other Amlogic SoCs be vulnerable to the same regulator control glitch unless the order is swapped there as well? > if (ret) > return ret; > > - return aml_pinconf_set_drive(info, pin, high); > + return aml_pinconf_set_output(info, pin, true); > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260518-fix-set-value-glitch-v1-1-d350732dc934 at amlogic.com?part=1 From jani.nikula at linux.intel.com Mon May 18 02:02:03 2026 From: jani.nikula at linux.intel.com (Jani Nikula) Date: Mon, 18 May 2026 12:02:03 +0300 Subject: [PATCH v6 16/22] drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio In-Reply-To: <20260516183838.2024991-17-jonas@kwiboo.se> References: <20260516183838.2024991-1-jonas@kwiboo.se> <20260516183838.2024991-17-jonas@kwiboo.se> Message-ID: <937d97789b78c4ad463c7e17f48f044837402537@intel.com> On Sat, 16 May 2026, Jonas Karlman wrote: > - /* > - * 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); Yay! \o/ > > return drm_edid; > } -- Jani Nikula, Intel From neil.armstrong at linaro.org Mon May 18 05:56:29 2026 From: neil.armstrong at linaro.org (neil.armstrong at linaro.org) Date: Mon, 18 May 2026 14:56:29 +0200 Subject: [PATCH] PCI: meson: Propagate devm_add_action_or_reset() failure In-Reply-To: <20260518082022.F4229C2BCB7@smtp.kernel.org> References: <177909148011.9588.6639767953842842291@gmail.com> <20260518082022.F4229C2BCB7@smtp.kernel.org> Message-ID: <940d7714-4303-4e82-a006-3066d2165844@linaro.org> On 5/18/26 10:20, sashiko-bot at kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: > - [High] Missing `.remove` callback or `.suppress_bind_attrs = true` leaves PCI subsystem and PHY active after driver unbind. > -- > > commit fde9e1cd9d05730f72b074fa8edf3a1233d11384 > Author: Shuvam Pandey > > PCI: meson: Propagate devm_add_action_or_reset() failure > > This commit updates meson_pcie_probe_clock() to check the return value > of devm_add_action_or_reset(). If it fails, the error is returned so > that the probe error path can unwind normally. > >> diff --git a/drivers/pci/controller/dwc/pci-meson.c b/drivers/pci/controller/dwc/pci-meson.c >> index 0694084f612b7..8d495bcc3a41a 100644 >> --- a/drivers/pci/controller/dwc/pci-meson.c >> +++ b/drivers/pci/controller/dwc/pci-meson.c >> @@ -204,7 +204,9 @@ static inline struct clk *meson_pcie_probe_clock(struct device *dev, >> return ERR_PTR(ret); >> } >> >> - devm_add_action_or_reset(dev, meson_pcie_disable_clock, clk); >> + ret = devm_add_action_or_reset(dev, meson_pcie_disable_clock, clk); >> + if (ret) >> + return ERR_PTR(ret); > > This isn't a bug introduced by this patch, but could this driver be unbound > via sysfs, triggering a use-after-free? > > The meson_pcie_probe() function registers the PCI host controller via > dw_pcie_host_init(&pci->pp) and powers on the PHY via meson_pcie_power_on(). > > However, the driver does not implement a .remove callback to call > dw_pcie_host_deinit() and meson_pcie_power_off(), nor does it set > .suppress_bind_attrs = true in its platform_driver struct. > > If the driver is unbound via /sys/bus/platform/drivers/meson-pcie/unbind, > the device core will trigger devres cleanup. This frees the mp structure, > unmaps IO memory, disables clocks, and releases resets. > > Because the PCI subsystem is never explicitly unregistered via > dw_pcie_host_deinit(), the PCI core retains references to the destroyed root > bus and freed structures. > > Can a subsequent access by the PCI subsystem or userspace cause a > use-after-free and system crash? This probably a bug to fix, but totally unrelated to this change > > Additionally, the PHY is left permanently powered on without its required > clocks. Should the driver either implement a proper teardown in a .remove > hook or prevent unbinding by setting .suppress_bind_attrs = true? > >> >> return clk; >> } > From neil.armstrong at linaro.org Mon May 18 05:56:36 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Mon, 18 May 2026 14:56:36 +0200 Subject: [PATCH] PCI: meson: Propagate devm_add_action_or_reset() failure In-Reply-To: <177909148011.9588.6639767953842842291@gmail.com> References: <177909148011.9588.6639767953842842291@gmail.com> Message-ID: <08de1873-d2d8-4387-8521-7161ad71ee46@linaro.org> On 5/18/26 10:04, Shuvam Pandey wrote: > meson_pcie_probe_clock() enables a clock and then registers a devres > action to disable it during teardown. If devm_add_action_or_reset() > fails, it runs the action immediately, disabling the clock. > > The return value is currently ignored, so on that failure path > meson_pcie_probe_clock() returns the disabled clock and probe continues. > Return the error so the existing probe error path unwinds normally. > > Fixes: 9c0ef6d34fdbf ("PCI: amlogic: Add the Amlogic Meson PCIe controller driver") > Signed-off-by: Shuvam Pandey > --- > drivers/pci/controller/dwc/pci-meson.c | 4 +++- > 1 file changed, 3 insertions(+), 1 deletion(-) > > diff --git a/drivers/pci/controller/dwc/pci-meson.c b/drivers/pci/controller/dwc/pci-meson.c > index 0694084f612b..8d495bcc3a41 100644 > --- a/drivers/pci/controller/dwc/pci-meson.c > +++ b/drivers/pci/controller/dwc/pci-meson.c > @@ -204,7 +204,9 @@ static inline struct clk *meson_pcie_probe_clock(struct device *dev, > return ERR_PTR(ret); > } > > - devm_add_action_or_reset(dev, meson_pcie_disable_clock, clk); > + ret = devm_add_action_or_reset(dev, meson_pcie_disable_clock, clk); > + if (ret) > + return ERR_PTR(ret); > > return clk; > } Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Mon May 18 07:17:47 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Mon, 18 May 2026 16:17:47 +0200 Subject: [PATCH v6 0/8] Add VIM4 MCU/FAN support In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> References: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> Message-ID: <4df49ad2-797b-4971-a8a9-13fd438e7c6f@linaro.org> Hi, On 5/16/26 19:17, Ronald Claveau via B4 Relay wrote: > The Khadas VIM4 board features a different MCU variant compared to > previous VIM boards. > While it shares the same I2C-based communication model, > it differs in some ways: > > - A distinct register map with its own volatile/writeable register set > - A fan control with 0?100 levels instead of the 0?3 levels previously > - A fan power supply gated through a regulator > > This series adds support for this new variant by: > > 1. Refactoring the khadas-mcu MFD driver to use per-variant data > structures (regmap config, cells, fan platform data), > and adding the khadas,vim4-mcu compatible string. > > 2. Extending the fan thermal driver to retrieve the fan register > and maximum level from platform_data, > and to optionally manage a power regulator for the fan supply. > > 3. Adding the corresponding DTS node for the VIM4, wiring the MCU to > the I2C AO_A bus and exposing it as a thermal cooling device. I will directly pick patch 6 now, and patch 7 & 8 later when patches 1,2,3,4 are picked by the i2c & mfd maintainers. Patch 5 should not be applied without path 4. Neil > > Signed-off-by: Ronald Claveau > --- > Changes in v6: > - PATCH 4: Address Lee's review comments: > - Use an enum to discriminate between MCU types instead of passing > MFD data through the DT match table > - Fix error code from -EINVAL to -ENODEV when no MCU type is matched > - Make khadas_mcu_fan_cells and khadas_mcu_cells const > - Use dev_err_probe() for regmap initialization error > - Document fan speed levels for max_level > - Link to v5: https://lore.kernel.org/r/20260424-add-mcu-fan-khadas-vim4-v5-0-afcfa7157b23 at aliel.fr > > Changes in v5: > - PATCH 5: Replace devm_regulator_get_optional() with devm_regulator_get() > to simplify error handling and remove NULL checks, also > ordering as reverse christmas according to Neil's feedback. > - Link to v4: https://lore.kernel.org/r/20260421-add-mcu-fan-khadas-vim4-v4-0-447114a28f2d at aliel.fr > > Changes in v4: > - PATCH 1: limit fan-supply property by compatible according to Conor's feedback. > - Link to v3: https://lore.kernel.org/r/20260417-add-mcu-fan-khadas-vim4-v3-0-a6a7f570b11b at aliel.fr > > Changes in v3: > - PATCH 1: adding comment on vim4 compatible saying it is not discoverable, > thanks to Rob's and Neil's feedback. > - Link to v2: https://lore.kernel.org/r/20260403-add-mcu-fan-khadas-vim4-v2-0-70536b22439a at aliel.fr > > Changes in v2: > - PATCH 5: Add regulator_disable on suspend thanks to Neil's feedback. > - Link to v1: https://lore.kernel.org/r/20260402-add-mcu-fan-khadas-vim4-v1-0-2b12eb4ac7b0 at aliel.fr > > --- > Ronald Claveau (8): > dt-bindings: mfd: khadas: Add new compatible for Khadas VIM4 MCU > dt-bindings: i2c: amlogic: Add compatible for T7 SOC > mfd: khadas-mcu: Add per-variant configuration infrastructure and VIM4 support > mfd: khadas-mcu: Add support for VIM4 MCU variant > thermal: khadas-mcu-fan: Add fan config from platform data Add regulator support > arm64: dts: amlogic: t7: Add i2c pinctrl node > arm64: dts: amlogic: t7: Add i2c controller node > arm64: dts: amlogic: t7: khadas-vim4: Add i2c MCU fan node > > .../bindings/i2c/amlogic,meson6-i2c.yaml | 13 ++- > .../devicetree/bindings/mfd/khadas,mcu.yaml | 18 ++++ > .../dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 13 +++ > arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 20 ++++ > drivers/mfd/khadas-mcu.c | 119 ++++++++++++++++++--- > drivers/thermal/khadas_mcu_fan.c | 37 +++++-- > include/linux/mfd/khadas-mcu.h | 44 +++++++- > 7 files changed, 236 insertions(+), 28 deletions(-) > --- > base-commit: f7b64ed948718290209074a50bb0df17e5944873 > change-id: 20260402-add-mcu-fan-khadas-vim4-ac1cbe553c9b > prerequisite-message-id: <20260326092645.1053261-1-jian.hu at amlogic.com> > prerequisite-patch-id: f03a086b4137158412b2d47b3de793b858de8dde > prerequisite-patch-id: 123970c9b29c2090440f2fd71c85d3c6fd8e36de > prerequisite-patch-id: 3e2e56b0926ba327b520f935df4ced5089bbe503 > prerequisite-patch-id: 65a5d76ffdbc9b3aab3385bb65cb027004c30e7e > prerequisite-patch-id: 237269801826dd3ad7fb16eb4d7d6d4eab504278 > prerequisite-patch-id: 57e9b08a968aedf543d3d0d56cf1ca4db20b2a16 > prerequisite-change-id: 20260326-add-bcm43752-compatible-e264a4f7973a:v2 > prerequisite-patch-id: cd98b74fa56af72af2553f391c400981d83cd4f4 > prerequisite-patch-id: b730f5e42be1d89d193e63a0265495cdbf2c7d7b > prerequisite-change-id: 20260330-fix-invalid-property-bbe54d933f71:v2 > prerequisite-patch-id: 8d675e7a239985c762843515b241f0a2f45f9c92 > prerequisite-change-id: 20260331-fix-aml-t7-null-reset-2b608ebf9da4:v1 > prerequisite-patch-id: 5b5de77af11747ce964404fb827d2ee2bff47ea5 > prerequisite-patch-id: 1e37fc75fed1e533adee0f3e7e6ead1f8ff3c55c > prerequisite-patch-id: 65a5d76ffdbc9b3aab3385bb65cb027004c30e7e > prerequisite-patch-id: 2daf583fb5e7449a02bd217d8aca330171b598aa > prerequisite-patch-id: 237269801826dd3ad7fb16eb4d7d6d4eab504278 > prerequisite-patch-id: d1ddf9b7710e91f8062de83bd7ba55afb2c4c112 > prerequisite-patch-id: 57e9b08a968aedf543d3d0d56cf1ca4db20b2a16 > prerequisite-patch-id: cd98b74fa56af72af2553f391c400981d83cd4f4 > prerequisite-patch-id: b730f5e42be1d89d193e63a0265495cdbf2c7d7b > prerequisite-patch-id: 9debd88fa60febed9cd7208f86603b4c2d270520 > prerequisite-patch-id: 314ef9ff0c4d1d15dab1dea9d92aa065f1eac3e9 > > Best regards, From neil.armstrong at linaro.org Mon May 18 07:32:00 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Mon, 18 May 2026 16:32:00 +0200 Subject: [PATCH] pinctrl: meson: amlogic-a4: fix gpio output glitch In-Reply-To: <20260518-fix-set-value-glitch-v1-1-d350732dc934@amlogic.com> References: <20260518-fix-set-value-glitch-v1-1-d350732dc934@amlogic.com> Message-ID: <72b341e6-5ada-48e9-a9c1-a442810b15da@linaro.org> On 5/18/26 10:26, Xianwei Zhao via B4 Relay wrote: > From: Xianwei Zhao > > When the system transitions from bootloader to kernel, the GPIO is > expected to keep driving high. > > However, the Linux kernel first configures the pin direction and then > sets the output value. This may cause a brief low-level glitch on the > GPIO line, which can be problematic for regulator control. > > By configuring the output value before switching the pin direction to > output, the glitch can be avoided. > > This commit fixes the issue by swapping the configuration order. > > Fixes: 6e9be3abb78c ("pinctrl: Add driver support for Amlogic SoCs") > Signed-off-by: Xianwei Zhao > --- > fix one issue when set gpio line high. > --- > drivers/pinctrl/meson/pinctrl-amlogic-a4.c | 15 +++++++-------- > 1 file changed, 7 insertions(+), 8 deletions(-) > > diff --git a/drivers/pinctrl/meson/pinctrl-amlogic-a4.c b/drivers/pinctrl/meson/pinctrl-amlogic-a4.c > index 35d27626a336..1bd58fbbd26a 100644 > --- a/drivers/pinctrl/meson/pinctrl-amlogic-a4.c > +++ b/drivers/pinctrl/meson/pinctrl-amlogic-a4.c > @@ -548,11 +548,11 @@ static int aml_pinconf_set_output_drive(struct aml_pinctrl *info, > { > int ret; > > - ret = aml_pinconf_set_output(info, pin, true); > + ret = aml_pinconf_set_drive(info, pin, high); > if (ret) > return ret; > > - return aml_pinconf_set_drive(info, pin, high); > + return aml_pinconf_set_output(info, pin, true); > } > > static int aml_pinconf_set(struct pinctrl_dev *pcdev, unsigned int pin, > @@ -921,15 +921,14 @@ static int aml_gpio_direction_output(struct gpio_chip *chip, unsigned int gpio, > unsigned int bit, reg; > int ret; > > - aml_gpio_calc_reg_and_bit(bank, AML_REG_DIR, gpio, ®, &bit); > - ret = regmap_update_bits(bank->reg_gpio, reg, BIT(bit), 0); > + aml_gpio_calc_reg_and_bit(bank, AML_REG_OUT, gpio, ®, &bit); > + ret = regmap_update_bits(bank->reg_gpio, reg, BIT(bit), > + value ? BIT(bit) : 0); > if (ret < 0) > return ret; > > - aml_gpio_calc_reg_and_bit(bank, AML_REG_OUT, gpio, ®, &bit); > - > - return regmap_update_bits(bank->reg_gpio, reg, BIT(bit), > - value ? BIT(bit) : 0); > + aml_gpio_calc_reg_and_bit(bank, AML_REG_DIR, gpio, ®, &bit); > + return regmap_update_bits(bank->reg_gpio, reg, BIT(bit), 0); > } > > static int aml_gpio_set(struct gpio_chip *chip, unsigned int gpio, int value) > > --- > base-commit: 73d4991a6949eedb51e442d4e81415017d85975b > change-id: 20260518-fix-set-value-glitch-f43cd366c295 > > Best regards, Reviewed-by: Neil Armstrong Thanks, Neil From dmitry.baryshkov at oss.qualcomm.com Mon May 18 09:56:59 2026 From: dmitry.baryshkov at oss.qualcomm.com (Dmitry Baryshkov) Date: Mon, 18 May 2026 19:56:59 +0300 Subject: [PATCH RESEND v3 1/6] drm/connector: report IRQ_HPD events to drm_connector_oob_hotplug_event() In-Reply-To: <20260514104424.04B3FC2BCB3@smtp.kernel.org> References: <20260513-hpd-irq-events-v3-1-086857017f16@oss.qualcomm.com> <20260514104424.04B3FC2BCB3@smtp.kernel.org> Message-ID: On Thu, May 14, 2026 at 10:44:23AM +0000, sashiko-bot at kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: > - [High] IRQ_HPD events are silently dropped if a status update triggers a reconfiguration while the HPD state remains unchanged. > - [High] Deferred events in `dp_altmode_configured()` unconditionally report the display as connected and leak stale `pending_irq_hpd` / `pending_hpd` state. > -- > > commit 53d82f9a5286a8fa5530ca1be94f61940637586b > Author: Dmitry Baryshkov > > drm/connector: report IRQ_HPD events to drm_connector_oob_hotplug_event() > > This commit extends the drm_connector_oob_hotplug_event() interface to report > DisplayPort irq events. These events are now forwarded through the alt mode > messages to the sink drivers to support multi-stream transport handling. > > > diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c > > index 35d9c30869900..7182a8e2e7101 100644 > > --- a/drivers/usb/typec/altmodes/displayport.c > > +++ b/drivers/usb/typec/altmodes/displayport.c > [ ... ] > > @@ -212,7 +214,10 @@ static int dp_altmode_configured(struct dp_altmode *dp) > > */ > > if (dp->pending_hpd) { > > drm_connector_oob_hotplug_event(dp->connector_fwnode, > > - connector_status_connected); > > + connector_status_connected, > > + dp->pending_irq_hpd ? > > + DRM_CONNECTOR_DP_IRQ_HPD : > > + DRM_CONNECTOR_NO_EXTRA_STATUS); > > Are irq events silently dropped if a status update triggers a > reconfiguration while the hotplug detect state remains unchanged? > > Looking at dp_altmode_status_update(), if a status update requires > reconfiguration, the code attempts to buffer the event: Only if the DP isn't configured at the moment of the event. > > if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) { > ret = dp_altmode_configure(dp, con); > if (!ret) { > dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME : > DP_STATE_CONFIGURE; > if (dp->hpd != hpd) { > dp->hpd = hpd; > dp->pending_hpd = true; > } > if (dp->hpd && dp->pending_hpd && irq_hpd) > dp->pending_irq_hpd = true; > } > } > > It only sets dp->pending_hpd = true if the hotplug detect state has toggled. If > it remains unchanged, dp->pending_hpd stays false. This prevents > dp->pending_irq_hpd from being set even if irq_hpd is true. Because it's all about the pending events. If there is no HPD set, then there is no point in processing IRQ_HPD events too (please correct me if I'm wrong). > > Could this cause DisplayPort irq events that arrive concurrently with a > configuration change to be lost, preventing downstream multi-stream transport > topology updates and link training? > > Additionally, does this deferred event unconditionally report the display as > connected and leak stale state? > > If multiple status update messages arrive before configuration > completes, dp->hpd can transition to false while dp->pending_hpd and > dp->pending_irq_hpd remain true. > > Similarly, if the alt mode is exited via CMD_EXIT_MODE while > dp->pending_hpd is true, these pending flags are never cleared. > > Will this cause the drm driver to receive a spurious connection event and > irq when configuration completes or when the alt mode is later re-entered? > > -- > Sashiko AI review ? https://sashiko.dev/#/patchset/20260513-hpd-irq-events-v3-0-086857017f16 at oss.qualcomm.com?part=1 > > _______________________________________________ > linux-amlogic mailing list > linux-amlogic at lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-amlogic -- With best wishes Dmitry From shuvampandey1 at gmail.com Mon May 18 09:59:17 2026 From: shuvampandey1 at gmail.com (Shuvam Pandey) Date: Mon, 18 May 2026 22:44:17 +0545 Subject: [PATCH 1/2] PCI: dwc: Guard RAS DES debugfs deinit Message-ID: <0f97352506d8d813f70f441de4d63fcd5b7d1c3e.1779123847.git.shuvampandey1@gmail.com> dwc_pcie_rasdes_debugfs_init() returns success when the controller has no RAS DES capability, leaving pci->debugfs->rasdes_info unset. The common debugfs teardown path still calls dwc_pcie_rasdes_debugfs_deinit(), which dereferences rasdes_info unconditionally. Return early when no RAS DES state was allocated. In that case no RAS DES mutex was initialized, so there is nothing to destroy. Fixes: 4fbfa17f9a07 ("PCI: dwc: Add debugfs based Silicon Debug support for DWC") Signed-off-by: Shuvam Pandey --- drivers/pci/controller/dwc/pcie-designware-debugfs.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware-debugfs.c b/drivers/pci/controller/dwc/pcie-designware-debugfs.c index d0884253b..c3671cb2f 100644 --- a/drivers/pci/controller/dwc/pcie-designware-debugfs.c +++ b/drivers/pci/controller/dwc/pcie-designware-debugfs.c @@ -557,6 +557,9 @@ static void dwc_pcie_rasdes_debugfs_deinit(struct dw_pcie *pci) { struct dwc_pcie_rasdes_info *rinfo = pci->debugfs->rasdes_info; + if (!rinfo) + return; + mutex_destroy(&rinfo->reg_event_lock); } -- 2.50.1 (Apple Git-155) From shuvampandey1 at gmail.com Mon May 18 09:59:18 2026 From: shuvampandey1 at gmail.com (Shuvam Pandey) Date: Mon, 18 May 2026 22:44:18 +0545 Subject: [PATCH 2/2] PCI: meson: Add missing remove callback In-Reply-To: =?utf-8?q?=3C0f97352506d8d813f70f441de4d63fcd5b7d1c3e=2E1779123?= =?utf-8?q?847=2Egit=2Eshuvampandey1=40gmail=2Ecom=3E?= References: =?utf-8?q?=3C0f97352506d8d813f70f441de4d63fcd5b7d1c3e=2E17791238?= =?utf-8?q?47=2Egit=2Eshuvampandey1=40gmail=2Ecom=3E?= Message-ID: <1a0c86ab264cdc1c79c917e984b90991af51d827.1779123847.git.shuvampandey1@gmail.com> meson_pcie_probe() powers on the PHY and registers the DesignWare host bridge with dw_pcie_host_init(), but the driver has no remove callback. On driver unbind or module unload, the driver core therefore proceeds to devres cleanup without first unregistering the host bridge or powering off the PHY. Add a remove callback that deinitializes the DesignWare host bridge and powers off the PHY while device-managed resources are still valid. Fixes: 9c0ef6d34fdb ("PCI: amlogic: Add the Amlogic Meson PCIe controller driver") Signed-off-by: Shuvam Pandey --- drivers/pci/controller/dwc/pci-meson.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/pci/controller/dwc/pci-meson.c b/drivers/pci/controller/dwc/pci-meson.c index 0694084f6..c96e2244a 100644 --- a/drivers/pci/controller/dwc/pci-meson.c +++ b/drivers/pci/controller/dwc/pci-meson.c @@ -451,6 +451,14 @@ static int meson_pcie_probe(struct platform_device *pdev) return ret; } +static void meson_pcie_remove(struct platform_device *pdev) +{ + struct meson_pcie *mp = platform_get_drvdata(pdev); + + dw_pcie_host_deinit(&mp->pci.pp); + meson_pcie_power_off(mp); +} + static const struct of_device_id meson_pcie_of_match[] = { { .compatible = "amlogic,axg-pcie", @@ -464,6 +472,7 @@ MODULE_DEVICE_TABLE(of, meson_pcie_of_match); static struct platform_driver meson_pcie_driver = { .probe = meson_pcie_probe, + .remove = meson_pcie_remove, .driver = { .name = "meson-pcie", .of_match_table = meson_pcie_of_match, -- 2.50.1 (Apple Git-155) From jonas at kwiboo.se Mon May 18 11:01:36 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:36 +0000 Subject: [PATCH v7 00/23] drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup Message-ID: <20260518180206.2480119-1-jonas@kwiboo.se> This is a revival of an old dw-hdmi series and is the first series part of a new effort to upstream old LibreELEC HDMI 2.0 patches for Rockchip RK33xx devices. This series ensure poweron/poweroff and CEC phys addr invalidation is happening during normal DRM funcs, ensures EDID and CEC phys addr is updated in detect() similar to how the bridge connector works with a HDMI bridge attached, and also changes to debounce hotplug processing to prevent a full disable/enable cycle during a HPD low voltage pulse. After this series HPD, EDID and CEC handling should work very similar regardless is the dw-hdmi connector or the bridge connector is used. It should also help ensure a smoother transition when dw-hdmi is fully converted into a HDMI bridge in a future series. These changes have mainly been tested on Rockchip RK3328, RK3399 and RK3568 devices using both the dw-hdmi connector and also using a basic convert to use a bridge connector. The changes has also been tested on Amlogic S905X, S905Y2 and A311D devices that uses the bridge connector. Testing with a Rock Pi 4 (RK3399) using a Reaspberry Pi Monitor with Linux kms client console using drm.debug=0xe should log something like following: Power cycle monitor using the power button: [CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a [CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz [CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156 [CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 [CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1 [CONNECTOR:68:HDMI-A-1] Same epoch counter 10 Cable unplugged: [CONNECTOR:68:HDMI-A-1] EDID changed, epoch counter 11 [CONNECTOR:68:HDMI-A-1] status updated from connected to disconnected [CONNECTOR:68:HDMI-A-1] Changed epoch counter 10 => 12 [CONNECTOR:68:HDMI-A-1] generating connector hotplug event [CONNECTOR:68:HDMI-A-1] Sent hotplug event Cable connected: [CONNECTOR:68:HDMI-A-1] CEA VCDB 0x4a [CONNECTOR:68:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz [CONNECTOR:68:HDMI-A-1] ELD monitor RPI MON156 [CONNECTOR:68:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 [CONNECTOR:68:HDMI-A-1] ELD size 36, SAD count 1 [CONNECTOR:68:HDMI-A-1] status updated from disconnected to connected [CONNECTOR:68:HDMI-A-1] Changed epoch counter 12 => 13 [CONNECTOR:68:HDMI-A-1] generating connector hotplug event [CONNECTOR:68:HDMI-A-1] Sent hotplug event This series has evolved into an initial part of a larger multi series effort to: - drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup [v7] - drm/bridge: dw-hdmi: Improve input/output bus format handling - drm/bridge: dw-hdmi: Convert to a HDMI bridge and use of bridge connector - drm/bridge: dw-hdmi: Add and use tmds_char_rate_valid() plat data ops - drm/meson: hdmi: Misc cleanup and use CEC notifier helpers [v1] - phy: rockchip: inno-hdmi: Change TMDS rate handling to configure() ops [v4] - drm/rockchip: dw_hdmi: Misc cleanup and propagate bus format [v2] - drm/rockchip: dw_hdmi: Enable YCbCr and Deep Color modes Link to snapshot: https://github.com/Kwiboo/linux-rockchip/commits/next-20260518-rk-hdmi-v5/ Changes in v7: - Add patch to fix use-after-free when CEC adapter is unregistered - Only declare CEC notifier support when CEC device register succeeds - Re-order patches to drop drm_edid_raw() use before exposing more usage - Change to free irq before mute and clear using IH regs - Rebased on next-20260518 Link to v6: https://lore.kernel.org/dri-devel/20260516183838.2024991-1-jonas at kwiboo.se/ Changes in v6: - Update EDID and CEC phys addr in the bridge detect() func - Add CEC notifier bridge op for the bridge connector - Change back to disable_delayed_work_sync() in hpd disable ops, a possible deadlock is avoided by not using drm_bridge_hpd_notify() - Drop use of a suspend helper now that hpd disable ops use sync() calls - Ensure HPD interrupt is masked and IRQ handler is disabled early in dw_hdmi_remove() to prevent any irq re-arming of delayed work - Update a few commit messages and cover letter - Collect t-b tags Link to v5: https://lore.kernel.org/dri-devel/20260510124111.1226584-1-jonas at kwiboo.se/ Changes in v5: - Add patch that holds a bridge ref until connector cleanup, to fix a use-after-free issue during connector cleanup - Add patch that unregister CEC notifier during connector cleanup - Add patch that adds a common suspend helper - Add patch that drops call to drm_bridge_hpd_notify() - Collect r-b tag - Rebased on next-20260508 Link to v4: https://lore.kernel.org/dri-devel/20260504191059.275928-1-jonas at kwiboo.se/ Changes in v4: - Change to use generic CEC notifier helpers - Disable/mask hpd_work until enable_hpd()/hpd_enable() - Read connector status directly from HW regs in hpd_work - Continued rework of HDP and RXSENSE interrupt handling - Collect r-b tags - Rebased on next-20260430 Link to v3: https://lore.kernel.org/dri-devel/20260403185303.80748-1-jonas at kwiboo.se/ Changes in v3: - Rework EDID refresh handling to closer match bridge connector - Use delayed work to debounce HPD processing - Update commit messages - Collect r-b tags - Rebased on next-20260401 Link to v2: https://lore.kernel.org/dri-devel/20240908132823.3308029-1-jonas at kwiboo.se/ Changes in v2: - Add patch to disable scrambler feature when not supported - Add patch to only notify connected status on HPD interrupt - Update commit messages - Collect r-b tags - Rebased on next-20240906 Link to v1: https://lore.kernel.org/dri-devel/20240611155108.1436502-1-jonas at kwiboo.se/ Jonas Karlman (23): drm: bridge: dw_hdmi: Disable scrambler feature when not supported drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt drm: bridge: dw_hdmi: Free IRQ before CEC adapter is unregistered drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode drm: bridge: dw_hdmi: Fold poweron and setup functions drm: bridge: dw_hdmi: Remove previous_mode and mode_set drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect drm: bridge: dw_hdmi: Remove cec_notifier_mutex drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update() drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio drm: bridge: dw_hdmi: Use generic CEC notifier helpers drm: bridge: dw_hdmi: Update EDID and CEC phys addr in bridge detect() drm: bridge: dw_hdmi: Declare bridge CEC notifier support drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense() drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd() drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 1 - drivers/gpu/drm/bridge/synopsys/Kconfig | 1 + drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c | 1 + drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 502 +++++++----------- drivers/gpu/drm/meson/meson_dw_hdmi.c | 3 - drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 2 - drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c | 2 - include/drm/bridge/dw_hdmi.h | 6 - 8 files changed, 192 insertions(+), 326 deletions(-) -- 2.54.0 From jonas at kwiboo.se Mon May 18 11:01:39 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:39 +0000 Subject: [PATCH v7 03/23] drm: bridge: dw_hdmi: Free IRQ before CEC adapter is unregistered In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-4-jonas@kwiboo.se> The interrupt allocated with devm_request_threaded_irq() can be use-after-free when the devres release action try to free_irq(). KASAN report a slab-use-after-free in dw_hdmi_cec_hardirq during unbind: Call trace: [...] dw_hdmi_cec_hardirq+0x4cc/0x560 free_irq+0x48c/0x7e4 devm_irq_release+0x54/0x90 dr_node_release+0x38/0x5c release_nodes+0xac/0x130 devres_release_all+0xf4/0x1b0 device_unbind_cleanup+0x28/0x1f8 device_release_driver_internal+0x358/0x470 device_release_driver+0x18/0x24 bus_remove_device+0x33c/0x4f0 device_del+0x2d8/0x790 platform_device_del+0x34/0x1e0 platform_device_unregister+0x14/0x3c dw_hdmi_remove+0x74/0x180 [...] Freed by: [...] kfree+0x1dc/0x5dc cec_delete_adapter+0xd4/0x118 cec_devnode_release+0xa4/0xe0 device_release+0xa0/0x200 kobject_put+0x14c/0x26c put_device+0x14/0x30 cec_unregister_adapter+0x20c/0x280 dw_hdmi_cec_remove+0x8c/0xd0 [...] Explicitly devm_free_irq() before the CEC adapter is unregistered to fix this possible use-after-free issue. Fixes: a616e63c56ef ("drm/bridge: dw-hdmi: add cec driver") Signed-off-by: Jonas Karlman --- v7: New patch KASAN report a slab-use-after-free in dw_hdmi_cec_hardirq when, echo fe0a0000.hdmi > /sys/bus/platform/drivers/dwhdmi-rockchip/unbind on a Rockchip RK3566 device prior to this fix. --- drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c index 9549dabde941..67a2a242d3ca 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c @@ -309,6 +309,7 @@ static void dw_hdmi_cec_remove(struct platform_device *pdev) struct dw_hdmi_cec *cec = platform_get_drvdata(pdev); cec_notifier_cec_adap_unregister(cec->notify, cec->adap); + devm_free_irq(&pdev->dev, cec->irq, cec->adap); cec_unregister_adapter(cec->adap); } -- 2.54.0 From jonas at kwiboo.se Mon May 18 11:01:40 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:40 +0000 Subject: [PATCH v7 04/23] drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-5-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 (...) 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 (...) 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 Fixes: ed6987b67418 ("drm/bridge: dw-hdmi: convert to devm_drm_bridge_alloc() API") Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: Add fixes tag, re-order patch v6: Collect t-b tag v5: New patch This use-after-free issue likely existed before commit ed6987b67418 when devm_kzalloc() was used instead of devm_drm_bridge_alloc(). However, v6.16-rc1 first introduced bridge refcount and drm_bridge_put(), parts that are used to help fix the use-after-free issue. KASAN report a slab-use-after-free in __refcount_add_not_zero when, echo fe0a0000.hdmi > /sys/bus/platform/drivers/dwhdmi-rockchip/unbind on a Rockchip RK3566 device prior to this fix. --- 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 b7bfc0e9a6b2..9d795c550f8a 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2568,10 +2568,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, @@ -2588,6 +2596,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 = @@ -2600,10 +2609,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 From jonas at kwiboo.se Mon May 18 11:01:41 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:41 +0000 Subject: [PATCH v7 05/23] drm: bridge: dw_hdmi: Call poweron/poweroff from atomic enable/disable In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-6-jonas@kwiboo.se> Change to only call poweron/poweroff from atomic_enable/atomic_disable funcs instead of trying to be clever by keeping a bridge_is_on state and poweron/off in the hotplug IRQ handler. The bridge is already enabled/disabled depending on connection status with the call to drm_helper_hpd_irq_event() in hotplug IRQ handler. Reviewed-by: Neil Armstrong Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change v6: Update commit message, Collect t-b tag v5: No change v4: No change v3: Collect r-b tag v2: Update commit message --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 33 ++--------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 9d795c550f8a..8bec9b5cd803 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -171,7 +171,6 @@ struct dw_hdmi { 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 bridge_is_on; /* indicates the bridge is on */ bool rxsense; /* rxsense state */ u8 phy_mask; /* desired phy int mask settings */ u8 mc_clkdis; /* clock disable register */ @@ -2400,8 +2399,6 @@ static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi) static void dw_hdmi_poweron(struct dw_hdmi *hdmi) { - hdmi->bridge_is_on = true; - /* * The curr_conn field is guaranteed to be valid here, as this function * is only be called when !hdmi->disabled. @@ -2415,30 +2412,6 @@ static void dw_hdmi_poweroff(struct dw_hdmi *hdmi) hdmi->phy.ops->disable(hdmi, hdmi->phy.data); hdmi->phy.enabled = false; } - - hdmi->bridge_is_on = false; -} - -static void dw_hdmi_update_power(struct dw_hdmi *hdmi) -{ - int force = hdmi->force; - - if (hdmi->disabled) { - force = DRM_FORCE_OFF; - } else if (force == DRM_FORCE_UNSPECIFIED) { - if (hdmi->rxsense) - force = DRM_FORCE_ON; - else - force = DRM_FORCE_OFF; - } - - if (force == DRM_FORCE_OFF) { - if (hdmi->bridge_is_on) - dw_hdmi_poweroff(hdmi); - } else { - if (!hdmi->bridge_is_on) - dw_hdmi_poweron(hdmi); - } } /* @@ -2563,7 +2536,6 @@ static void dw_hdmi_connector_force(struct drm_connector *connector) mutex_lock(&hdmi->mutex); hdmi->force = connector->force; - dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); mutex_unlock(&hdmi->mutex); } @@ -3001,7 +2973,7 @@ static void dw_hdmi_bridge_atomic_disable(struct drm_bridge *bridge, mutex_lock(&hdmi->mutex); hdmi->disabled = true; hdmi->curr_conn = NULL; - dw_hdmi_update_power(hdmi); + dw_hdmi_poweroff(hdmi); dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, false); mutex_unlock(&hdmi->mutex); @@ -3019,7 +2991,7 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge, mutex_lock(&hdmi->mutex); hdmi->disabled = false; hdmi->curr_conn = connector; - dw_hdmi_update_power(hdmi); + dw_hdmi_poweron(hdmi); dw_hdmi_update_phy_mask(hdmi); handle_plugged_change(hdmi, true); mutex_unlock(&hdmi->mutex); @@ -3119,7 +3091,6 @@ void dw_hdmi_setup_rx_sense(struct dw_hdmi *hdmi, bool hpd, bool rx_sense) if (hpd) hdmi->rxsense = true; - dw_hdmi_update_power(hdmi); dw_hdmi_update_phy_mask(hdmi); } mutex_unlock(&hdmi->mutex); -- 2.54.0 From jonas at kwiboo.se Mon May 18 11:01:42 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:42 +0000 Subject: [PATCH v7 06/23] drm: bridge: dw_hdmi: Use passed mode instead of stored previous_mode In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-7-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 8bec9b5cd803..7a9b4a7e9cbf 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 From jonas at kwiboo.se Mon May 18 11:01:43 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:43 +0000 Subject: [PATCH v7 07/23] drm: bridge: dw_hdmi: Fold poweron and setup functions In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-8-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 7a9b4a7e9cbf..161ab16fdd78 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) { @@ -2982,15 +2973,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 From jonas at kwiboo.se Mon May 18 11:01:44 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:44 +0000 Subject: [PATCH v7 08/23] drm: bridge: dw_hdmi: Remove previous_mode and mode_set In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-9-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 161ab16fdd78..cbbd15578042 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 */ @@ -2941,20 +2939,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) { @@ -3018,7 +3002,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 From jonas at kwiboo.se Mon May 18 11:01:45 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:45 +0000 Subject: [PATCH v7 09/23] drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-10-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 From jonas at kwiboo.se Mon May 18 11:01:48 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:48 +0000 Subject: [PATCH v7 12/23] drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update() In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-13-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 From jonas at kwiboo.se Mon May 18 11:01:49 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:49 +0000 Subject: [PATCH v7 13/23] drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-14-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 From jonas at kwiboo.se Mon May 18 11:01:50 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:50 +0000 Subject: [PATCH v7 14/23] drm: bridge: dw_hdmi: Use display_info is_hdmi and has_audio In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-15-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 Reviewed-by: Nicolas Frattaroli Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change, re-order patch 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 a4ecf830103d..0e84dff72470 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -46,8 +46,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 @@ -147,8 +145,6 @@ struct dw_hdmi { int vic; - u8 edid[HDMI_EDID_LEN]; - struct { const struct dw_hdmi_phy_ops *ops; const char *name; @@ -158,8 +154,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; @@ -2056,7 +2050,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; @@ -2292,7 +2286,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; @@ -2312,7 +2306,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 */ @@ -2321,7 +2315,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 */ @@ -2435,29 +2429,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 From jonas at kwiboo.se Mon May 18 11:01:51 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:51 +0000 Subject: [PATCH v7 15/23] drm: bridge: dw_hdmi: Use generic CEC notifier helpers In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-16-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 0e84dff72470..37406555af7b 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -23,12 +23,11 @@ #include #include -#include - #include #include #include +#include #include #include #include @@ -183,8 +182,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; @@ -2453,7 +2450,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; } @@ -2462,8 +2459,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 @@ -2525,9 +2521,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); } @@ -2550,8 +2543,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) @@ -2587,15 +2578,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 From jonas at kwiboo.se Mon May 18 11:01:37 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:37 +0000 Subject: [PATCH v7 01/23] drm: bridge: dw_hdmi: Disable scrambler feature when not supported In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-2-jonas@kwiboo.se> The scrambler feature can be left enabled when hotplugging from a sink and mode that require scrambling to a sink that does not support SCDC or scrambling. Typically a blank screen or 'no signal' message can be observed after using a HDMI 2.0 4K at 60Hz mode and then hotplugging to a sink that only support HDMI 1.4. Fix this by disabling the scrambler feature when SCDC is not supported. Fixes: 264fce6cc2c1 ("drm/bridge: dw-hdmi: Add SCDC and TMDS Scrambling support") Reported-by: Christopher Obbard Reviewed-by: Neil Armstrong Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change v6: Collect t-b tag v5: No change v4: No change v3: Collect r-b tag v2: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 41b3a9cfa2f5..d3e6a6562870 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2135,6 +2135,8 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi, HDMI_MC_SWRSTZ); drm_scdc_set_scrambling(hdmi->curr_conn, 0); } + } else if (hdmi->version >= 0x200a) { + hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL); } /* Set up horizontal active pixel width */ -- 2.54.0 From jonas at kwiboo.se Mon May 18 11:01:38 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:38 +0000 Subject: [PATCH v7 02/23] drm: bridge: dw_hdmi: Only notify connected status on HPD interrupt In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-3-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 From jonas at kwiboo.se Mon May 18 11:01:53 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:53 +0000 Subject: [PATCH v7 17/23] drm: bridge: dw_hdmi: Declare bridge CEC notifier support In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-18-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 --- v7: Only declare CEC notifier support when CEC device register succeeds v6: New patch --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 0c4388e7aa5e..5dacb8a99715 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -3515,6 +3515,10 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, pdevinfo.dma_mask = 0; hdmi->cec = platform_device_register_full(&pdevinfo); + if (!IS_ERR(hdmi->cec)) { + hdmi->bridge.ops |= DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER; + hdmi->bridge.hdmi_cec_dev = hdmi->dev; + } } drm_bridge_add(&hdmi->bridge); -- 2.54.0 From jonas at kwiboo.se Mon May 18 11:01:54 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:54 +0000 Subject: [PATCH v7 18/23] drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-19-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 5dacb8a99715..8afc9d240121 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 From jonas at kwiboo.se Mon May 18 11:01:55 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:55 +0000 Subject: [PATCH v7 19/23] drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-20-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 --- v7: Change to free irq before mute and clear using IH regs, also include clear of STAT0_RX_SENSE 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 | 80 +++++++++++++++++++++-- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 8afc9d240121..270db58a0e7c 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); @@ -3532,6 +3593,18 @@ 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); + + /* Free, mute and clear phy interrupts */ + devm_free_irq(hdmi->dev, irq, hdmi); + hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, + HDMI_IH_PHY_STAT0); + + /* Cancel any pending hot plug work */ + cancel_delayed_work_sync(&hdmi->hpd_work); + drm_bridge_remove(&hdmi->bridge); if (hdmi->audio && !IS_ERR(hdmi->audio)) @@ -3539,9 +3612,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 From jonas at kwiboo.se Mon May 18 11:01:56 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:56 +0000 Subject: [PATCH v7 20/23] drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-21-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: Remove clear of STAT0_RX_SENSE in dw_hdmi_remove() added in prior patch 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 | 147 ++++------------------ 1 file changed, 22 insertions(+), 125 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 270db58a0e7c..2e09bff5faf7 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; @@ -3599,8 +3497,7 @@ void dw_hdmi_remove(struct dw_hdmi *hdmi) /* Free, mute and clear phy interrupts */ devm_free_irq(hdmi->dev, irq, hdmi); hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); - 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); /* Cancel any pending hot plug work */ cancel_delayed_work_sync(&hdmi->hpd_work); -- 2.54.0 From jonas at kwiboo.se Mon May 18 11:01:46 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:46 +0000 Subject: [PATCH v7 10/23] drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-11-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. The CEC notifier is only registered for the dw-hdmi connector so this has no impact when the bridge connector is used. Reviewed-by: Neil Armstrong Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: Update commit message 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 From jonas at kwiboo.se Mon May 18 11:01:57 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:57 +0000 Subject: [PATCH v7 21/23] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_setup_rx_sense() In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-22-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 2e09bff5faf7..42d630efb875 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 From jonas at kwiboo.se Mon May 18 11:01:59 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:59 +0000 Subject: [PATCH v7 23/23] drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-24-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 c596534510da..99dd62b6becf 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 From jonas at kwiboo.se Mon May 18 11:01:52 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:52 +0000 Subject: [PATCH v7 16/23] drm: bridge: dw_hdmi: Update EDID and CEC phys addr in bridge detect() In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-17-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. This change introduce a slight delay to the bridge connector detect() and get_modes() funcs due to multiple EDID reads. This is an acceptable added delay to help ensure EDID and CEC phys addr always are correct. Signed-off-by: Jonas Karlman --- v7: Update commit message 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. The patch "drm/bridge-connector: Use cached connector status in .get_modes()" [1] can help remove one unnecessary EDID read until dw-hdmi is fully converted into a HDMI bridge. [1] https://lore.kernel.org/dri-devel/20260426-dw-hdmi-qp-scramb-v5-3-d778e70c317b at collabora.com/ --- 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 37406555af7b..0c4388e7aa5e 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -2947,8 +2947,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 From jonas at kwiboo.se Mon May 18 11:01:47 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:47 +0000 Subject: [PATCH v7 11/23] drm: bridge: dw_hdmi: Remove cec_notifier_mutex In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-12-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 Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 From jonas at kwiboo.se Mon May 18 11:01:58 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 18:01:58 +0000 Subject: [PATCH v7 22/23] drm: bridge: dw_hdmi: Remove the empty dw_hdmi_phy_update_hpd() In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <20260518180206.2480119-23-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 # Rock64, RockPro64, Quartz64-B Signed-off-by: Jonas Karlman --- v7: No change 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 42d630efb875..c596534510da 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 From sashiko-bot at kernel.org Mon May 18 11:33:27 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 18 May 2026 18:33:27 +0000 Subject: [PATCH v7 03/23] drm: bridge: dw_hdmi: Free IRQ before CEC adapter is unregistered In-Reply-To: <20260518180206.2480119-4-jonas@kwiboo.se> References: <20260518180206.2480119-4-jonas@kwiboo.se> Message-ID: <20260518183328.13424C2BCF6@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Freeing the shared IRQ before masking the hardware interrupt source creates a race condition that can lead to an unhandled interrupt storm, permanently disabling the shared IRQ line. -- commit 860a0518c5583dda606c670b609794b60e6bc07b Author: Jonas Karlman drm: bridge: dw_hdmi: Free IRQ before CEC adapter is unregistered This commit explicitly frees the IRQ before the CEC adapter is unregistered to prevent a use-after-free issue. This addresses a KASAN slab-use-after-free report triggered during device unbind when the devres release action frees the IRQ. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c > index 9549dabde941c..67a2a242d3ca9 100644 > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c > @@ -309,6 +309,7 @@ static void dw_hdmi_cec_remove(struct platform_device *pdev) > struct dw_hdmi_cec *cec = platform_get_drvdata(pdev); > > cec_notifier_cec_adap_unregister(cec->notify, cec->adap); > + devm_free_irq(&pdev->dev, cec->irq, cec->adap); Does this sequence create a race condition that could result in an unhandled interrupt storm? The dw-hdmi-cec driver shares this IRQ line with the main dw-hdmi driver. By freeing the IRQ handler before calling cec_unregister_adapter(), the hardware CEC interrupts might still be unmasked and active. If the CEC hardware asserts an interrupt during this window, the shared IRQ line will go high. The remaining handlers on that line won't clear the CEC interrupt, returning IRQ_NONE. This could lead to an interrupt storm, causing the kernel to permanently disable the shared IRQ line and break the main dw-hdmi driver's functionality. Should the CEC hardware interrupts be explicitly masked before devm_free_irq() is called? > cec_unregister_adapter(cec->adap); > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260518180206.2480119-1-jonas at kwiboo.se?part=3 From sashiko-bot at kernel.org Mon May 18 11:47:48 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 18 May 2026 18:47:48 +0000 Subject: [PATCH v7 13/23] drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() In-Reply-To: <20260518180206.2480119-14-jonas@kwiboo.se> References: <20260518180206.2480119-14-jonas@kwiboo.se> Message-ID: <20260518184748.CDC7EC2BCB7@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] The CEC physical address is no longer set when the connector status is `connector_status_unknown`, breaking CEC functionality for displays with unreliable HPD. - [High] Unconditional EDID read over I2C in `detect()` ignores the `force` parameter, causing severe performance degradation and I2C spam. -- commit d5d270011a2b02f003218ae7b96f62fae2b447e9 Author: Jonas Karlman drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() This commit updates the dw-hdmi connector to fetch EDID and update CEC physical addresses during detect and force functions rather than in get_modes. It aims to ensure userspace always has access to the latest read EDID. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > --- 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, [ ... ] > 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); > } Does this break CEC functionality for displays with unreliable HPD? By guarding the cec_notifier_set_phys_addr() call with a check for connector_status_connected, the CEC physical address update is skipped if the status evaluates to connector_status_unknown. Previously, this was called unconditionally after a successful EDID read, which allowed CEC to work even when HPD was unreliable. > 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); Will this cause performance issues by performing unconditional I2C EDID reads on every detect call? The dw_hdmi_connector_status_update() function reads the EDID over I2C unless the status is disconnected, but this completely ignores the force parameter passed to detect. If a system-wide hotplug event occurs on another port, drm_helper_hpd_irq_event() will call detect with force=false on all HPD-capable connectors. Could this trigger an unnecessary 20-50ms I2C EDID read here, spamming the I2C bus and blocking the shared KMS system workqueue? > > return status; > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260518180206.2480119-1-jonas at kwiboo.se?part=13 From sashiko-bot at kernel.org Mon May 18 11:53:00 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 18 May 2026 18:53:00 +0000 Subject: [PATCH v7 15/23] drm: bridge: dw_hdmi: Use generic CEC notifier helpers In-Reply-To: <20260518180206.2480119-16-jonas@kwiboo.se> References: <20260518180206.2480119-16-jonas@kwiboo.se> Message-ID: <20260518185300.C749CC2BCB7@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Use-After-Free and memory leak due to drmm action lifetime mismatch with the connector -- commit 7a3d2d4c9ae6107ffef88b867d73cc39dd7e8567 Author: Jonas Karlman drm: bridge: dw_hdmi: Use generic CEC notifier helpers This commit replaces the open-coded CEC notifier handling with the generic DRM HDMI CEC notifier helpers. > 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. Is this assumption accurate for all drivers using dw_hdmi? When master drivers like sun4i or imx are unbound, drm_mode_config_cleanup() is often called synchronously, which in turn calls dw_hdmi_connector_destroy(). If a userspace process holds the DRM file descriptor open, the drm_device outlives the unbind. Doesn't this mean the drmm release action will run much later than dw_hdmi_connector_destroy(), rather than just before it? > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index 0e84dff724706..37406555af7bc 100644 > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c [ ... ] > @@ -2587,15 +2578,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); > } Could this cause a use-after-free when the drm_device is finally released? Looking at dw_hdmi_connector_destroy(), it cleans up the connector state: dw_hdmi_connector_destroy() drm_connector_cleanup(connector) ... memset(connector, 0, sizeof(*connector)); Since the connector is embedded in struct dw_hdmi which is managed by devres and the bridge refcount, the memory will be freed when the platform device unbind completes. When the drmm action drm_connector_hdmi_cec_notifier_unregister() eventually executes during drm_dev_release(), won't it attempt to dereference the already freed connector pointer? If the memory happens to still be accessible, won't it read the zeroed connector->cec.data as a NULL pointer, silently leaking the CEC notifier? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260518180206.2480119-1-jonas at kwiboo.se?part=15 From sashiko-bot at kernel.org Mon May 18 11:57:13 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 18 May 2026 18:57:13 +0000 Subject: [PATCH v7 19/23] drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event In-Reply-To: <20260518180206.2480119-20-jonas@kwiboo.se> References: <20260518180206.2480119-20-jonas@kwiboo.se> Message-ID: <20260518185714.1EED3C2BCB7@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] The debounce logic completely filters out the 100ms EDID change pulse, causing the DRM core to miss the hotplug event. - [High] The shared IRQ handler is freed before hardware interrupts are masked, potentially causing an unhandled interrupt storm. -- commit 2b07e0d99061a1389b7eef1c604f98ccf0e7d115 Author: Jonas Karlman drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event This commit introduces a delayed work queue to debounce HDMI hotplug detect (HPD) events. It aims to improve the handling of EDID change pulses from the sink by applying an 1100 msec timeout between the event and the DRM core notification. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c [ ... ] > @@ -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)); > } Does this debounce logic unintentionally filter out the 100ms EDID change pulse entirely? When the sink signals an EDID change by pulsing HPD low for 100ms and then high again, mod_delayed_work() will be called on both edges. The second call on the rising edge will reset the timer, pushing the work execution to 1100ms after the pin returns high. When dw_hdmi_hpd_work() finally runs and calls drm_helper_hpd_irq_event(), the DRM connector detect function will see the current HPD status as connected. Since the previous status known to the DRM core was also connected (because the brief disconnect was never sampled), check_connector_changed() will conclude that nothing changed. The epoch counter will not increment, and the userspace uevent might be lost. [ ... ] > @@ -3532,6 +3593,18 @@ 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); > + > + /* Free, mute and clear phy interrupts */ > + devm_free_irq(hdmi->dev, irq, hdmi); > + hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); > + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, > + HDMI_IH_PHY_STAT0); Could freeing the shared IRQ handler before masking the hardware interrupts cause an unhandled interrupt storm? If the HDMI device asserts a level-triggered interrupt exactly when devm_free_irq() is called, the interrupt line will stay asserted but the dw_hdmi handler will no longer be registered to clear it. Because the IRQ is shared (requested with IRQF_SHARED), the kernel will repeatedly invoke other handlers on this IRQ line. Those other handlers will all return IRQ_NONE, which can cause the kernel to permanently disable the shared IRQ line. Should the hardware interrupts be explicitly masked and cleared before the handler is freed? > + > + /* Cancel any pending hot plug work */ > + cancel_delayed_work_sync(&hdmi->hpd_work); > + > drm_bridge_remove(&hdmi->bridge); -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260518180206.2480119-1-jonas at kwiboo.se?part=19 From sashiko-bot at kernel.org Mon May 18 12:05:38 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 18 May 2026 19:05:38 +0000 Subject: [PATCH v7 18/23] drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() In-Reply-To: <20260518180206.2480119-19-jonas@kwiboo.se> References: <20260518180206.2480119-19-jonas@kwiboo.se> Message-ID: <20260518190538.BFFA5C2BCB7@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Removing `drm_bridge_hpd_notify()` violates the `DRM_BRIDGE_OP_HPD` contract and replaces targeted bridge HPD notification with an inefficient global device poll. -- commit f2225f0de66ea8205ddbd49531c0c402dab0c72b Author: Jonas Karlman drm: bridge: dw_hdmi: Drop call to drm_bridge_hpd_notify() This commit removes the call to drm_bridge_hpd_notify() from the dw_hdmi hotplug interrupt handler. It relies instead solely on drm_helper_hpd_irq_event() to trigger hotplug events in order to prevent multiple hotplug uevents and redundant modeset probes. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index 5dacb8a99715b..8afc9d2401214 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); > - } Does removing the drm_bridge_hpd_notify() call violate the DRM_BRIDGE_OP_HPD API contract? The dw_hdmi driver is registered with the DRM_BRIDGE_OP_HPD flag, which typically requires the driver to call drm_bridge_hpd_notify() on status changes so that registered callbacks like drm_bridge_connector_hpd_cb() are properly invoked. By relying solely on drm_helper_hpd_irq_event(), doesn't this replace the targeted bridge HPD notification with a global device poll? Looking at drm_helper_hpd_irq_event() in drm_probe_helper.c, it acquires dev->mode_config.mutex and iterates over all connectors on the DRM device, calling their detect() functions synchronously. In multi-display systems (for example, HDMI alongside eDP or DSI), will an HDMI hotplug interrupt now trigger slow DDC/I2C/AUX probes on those completely unrelated connectors? While later patches or discussions might address using delayed_work to debounce hotplug events to avoid mutex deadlocks, could the driver resolve those locking issues without bypassing the bridge framework's targeted notification mechanism and forcing an inefficient global poll? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260518180206.2480119-1-jonas at kwiboo.se?part=18 From sashiko-bot at kernel.org Mon May 18 12:08:09 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 18 May 2026 19:08:09 +0000 Subject: [PATCH v7 20/23] drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling In-Reply-To: <20260518180206.2480119-21-jonas@kwiboo.se> References: <20260518180206.2480119-21-jonas@kwiboo.se> Message-ID: <20260518190810.55320C2BCC6@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Race condition when clearing the HPD interrupt sticky bit can permanently break hotplug detection -- commit cb7edad18b267f9862ac845a3672fd15ae20a0f1 Author: Jonas Karlman Subject: drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling This commit removes RXSENSE handling to simplify the HPD interrupt handling, instead relying on a delayed work mechanism to detect connection status or EDID changes. It also updates the HPD interrupt polarity initialization to match current HPD status. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index 270db58a0e7c3..2e09bff5faf79 100644 > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c [ ... ] > @@ -3060,78 +3011,29 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) [ ... ] > 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); [ ... ] > + 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); > - > - if ((intr_stat & HDMI_IH_PHY_STAT0_HPD) && > - (phy_stat & HDMI_PHY_HPD)) > - status = connector_status_connected; > + intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0); > + if (intr_stat & HDMI_IH_PHY_STAT0_HPD) { > + enum drm_connector_status status; > > - if (!(phy_stat & (HDMI_PHY_HPD | HDMI_PHY_RX_SENSE))) > - status = connector_status_disconnected; > - } > + /* 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 (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); This problem wasn't introduced by this patch, but could this interrupt clearing sequence contain a race condition that breaks hotplug detection? If the HPD line bounces after the polarity is updated but before the interrupt is cleared, the new state will match the newly configured polarity. This generates a new interrupt edge and sets the sticky bit. The subsequent write to HDMI_IH_PHY_STAT0 using the originally read intr_stat will immediately clear this new sticky bit. As a result, the hardware is left with a cleared interrupt while the line matches the polarity. The next physical edge will not trigger an interrupt because it transitions away from the matched polarity, leaving HPD permanently desynchronized. Should HDMI_IH_PHY_STAT0 be cleared before reading the live status and updating the polarity to prevent losing interrupts? > - 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; > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260518180206.2480119-1-jonas at kwiboo.se?part=20 From sashiko-bot at kernel.org Mon May 18 12:10:28 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 18 May 2026 19:10:28 +0000 Subject: [PATCH v7 23/23] drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers In-Reply-To: <20260518180206.2480119-24-jonas@kwiboo.se> References: <20260518180206.2480119-24-jonas@kwiboo.se> Message-ID: <20260518191028.CBF63C2BCB7@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Clearing the HPD interrupt status after modifying the interrupt polarity creates a race window where an edge transition is missed, breaking hotplug detection. -- commit b4c743935eb9892c0e2a6a962a52440a37e985de Author: Jonas Karlman drm: bridge: dw_hdmi: Merge top and bottom half IRQ handlers This commit simplifies IRQ handling by merging the top and bottom half IRQ handlers into a single handler. It transitions HPD event handling to use a delayed work queue instead of an IRQ thread. > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > --- 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) [ ... ] > - 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); > + hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); Does clearing the HPD interrupt status after modifying the interrupt polarity create a race window where an edge transition is missed? While this isn't a new regression introduced by this patch, it persists in the new merged handler: 1. The ISR reads HDMI_IH_PHY_STAT0 and finds the HPD bit set. 2. It reads the current PHY state (e.g. CONNECTED) and updates the polarity HDMI_PHY_POL0 to look for the opposite edge (e.g. PLUGOUT). 3. If a fast bounce occurs right here (the cable is unplugged), the hardware detects the edge and sets the HPD sticky bit to 1 again. 4. The ISR then writes the original intr_stat (with the HPD bit set) back to HDMI_IH_PHY_STAT0 (a write-1-to-clear register). This clears the newly latched unplug event. This leaves the hardware waiting for a PLUGOUT event, but the cable is already disconnected. When the cable is plugged back in, the hardware ignores the PLUGIN edge because it is still looking for a PLUGOUT. Could this lead to a regression where hotplug interrupts break until the next reboot? Should the W1C register be cleared before reading the PHY status and updating the polarity? > + hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); > + ret = IRQ_HANDLED; > + } > > - return IRQ_HANDLED; > + return ret; > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260518180206.2480119-1-jonas at kwiboo.se?part=23 From jonas at kwiboo.se Mon May 18 12:47:29 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:29 +0000 Subject: [PATCH 01/13] drm/meson: dw-hdmi: Report connector status based on HPD bit In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-2-jonas@kwiboo.se> G12A added support for RX-SENSE, status for both HPD and RX-SENSE is reported in the TOP_STAT0 register. Limit read_hpd() phy ops to only report connected status based on the HPD status bit in TOP_STAT0, to help ensure that EDID can be read from the sink in the connector detect() func. Fixes: 3b7c1237a72a ("drm/meson: Add G12A support for the DW-HDMI Glue") Signed-off-by: Jonas Karlman --- drivers/gpu/drm/meson/meson_dw_hdmi.c | 6 ++++-- drivers/gpu/drm/meson/meson_dw_hdmi.h | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c index 2a8756da569b..993f6d5d4b29 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -457,9 +457,11 @@ static enum drm_connector_status dw_hdmi_read_hpd(struct dw_hdmi *hdmi, void *data) { struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; + unsigned int stat; - return !!dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_STAT0) ? - connector_status_connected : connector_status_disconnected; + stat = dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_STAT0); + return stat & HDMITX_TOP_STAT0_HPD ? connector_status_connected : + connector_status_disconnected; } static void dw_hdmi_setup_hpd(struct dw_hdmi *hdmi, diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.h b/drivers/gpu/drm/meson/meson_dw_hdmi.h index 08e1c14e4ea0..cb4616daf667 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.h +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.h @@ -157,4 +157,7 @@ */ #define HDMITX_TOP_STAT0 (0x00E) +#define HDMITX_TOP_STAT0_HPD BIT(0) +#define HDMITX_TOP_STAT0_RXSENSE BIT(1) + #endif /* __MESON_DW_HDMI_H */ -- 2.54.0 From jonas at kwiboo.se Mon May 18 12:47:28 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:28 +0000 Subject: [PATCH 00/13] drm/meson: dw-hdmi: Misc cleanup and use CEC notifier helpers Message-ID: <20260518194744.2483580-1-jonas@kwiboo.se> This series include misc cleanup of the meson-dw-hdmi driver, changes to use the bridge CEC notifier op and to use the dw-hdmi delayed work for HPD event handling. Patch 1 ensure connector status is based on HPD bit Patch 2 protect from a possible NULL pointer dereference during bind() Patch 4 reduce number of hotplug uevents and hpd_notify() calls Patch 6 changes to use bridge connector CEC notifier Patch 3,5,7-10 cleanup code for consistency Patch 11-12 changes to use dw-hdmi HPD delayed work at HPD event Patch 13 changes to use suspend_late/resume_early/resume_noirq pm ops This series depends on improvements made in the series "drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup" [1]. [1] https://patchwork.freedesktop.org/series/134727/ This series is part of a multi series effort to: - drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup [v7] - drm/meson: hdmi: Misc cleanup and use CEC notifier helpers [v1] - drm/bridge: dw-hdmi: Improve input/output bus format handling - drm/bridge: dw-hdmi: Convert to a HDMI bridge and use of bridge connector - drm/bridge: dw-hdmi: Add and use tmds_char_rate_valid() plat data ops - phy: rockchip: inno-hdmi: Change TMDS rate handling to configure() ops [v4] - drm/rockchip: dw_hdmi: Misc cleanup and propagate bus format [v2] - drm/rockchip: dw_hdmi: Enable YCbCr and Deep Color modes Link to snapshot: https://github.com/Kwiboo/linux-rockchip/commits/next-20260518-rk-hdmi-v5/ Jonas Karlman (13): drm/meson: dw-hdmi: Report connector status based on HPD bit drm/meson: dw-hdmi: Protect from possible NULL pointer dereference drm/meson: dw-hdmi: Call dw_hdmi_remove() consistently drm/meson: dw-hdmi: Drop call to drm_bridge_hpd_notify() drm/meson: encoder_hdmi: Use CEC phys addr from display_info drm/meson: encoder_hdmi: Use bridge connector CEC notifier drm/meson: encoder_hdmi: Report ycbcr_420_allowed from encoder drm/meson: dw-hdmi: Use local dev variable consistently in bind() drm/meson: dw-hdmi: Use devm_clk_get_enabled() helper drm/meson: dw-hdmi: Use dev_err_probe() to report errors drm/bridge: dw-hdmi: Export dw_hdmi_schedule_hpd_work() helper drm/meson: dw-hdmi: Use dw_hdmi_schedule_hpd_work() helper drm/meson: dw-hdmi: Use suspend_late/resume_early/resume_noirq pm ops drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 11 +- drivers/gpu/drm/meson/Kconfig | 1 + drivers/gpu/drm/meson/meson_dw_hdmi.c | 169 +++++++++------------ drivers/gpu/drm/meson/meson_dw_hdmi.h | 3 + drivers/gpu/drm/meson/meson_encoder_hdmi.c | 107 +++---------- include/drm/bridge/dw_hdmi.h | 2 + 6 files changed, 106 insertions(+), 187 deletions(-) -- 2.54.0 From jonas at kwiboo.se Mon May 18 12:47:32 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:32 +0000 Subject: [PATCH 04/13] drm/meson: dw-hdmi: Drop call to drm_bridge_hpd_notify() In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-5-jonas@kwiboo.se> Calls to both drm_helper_hpd_irq_event() and drm_bridge_hpd_notify() in the IRQ handler causes multiple hotplug uevents and modesets during an HPD interrupt. Change to only call drm_helper_hpd_irq_event() in IRQ handler to ensure only one hotplug uevent is triggered when the connection status or EDID has changed. The bridge connectors detect() func help ensure that any hpd_notify() func is called for all bridges in the chain. Signed-off-by: Jonas Karlman --- drivers/gpu/drm/meson/meson_dw_hdmi.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c index 9aafdc768f2b..30099bf71aad 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -521,17 +521,8 @@ static irqreturn_t dw_hdmi_top_thread_irq(int irq, void *dev_id) /* HPD Events */ if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL) && - dw_hdmi->bridge) { - bool hpd_connected = false; - - if (stat & HDMITX_TOP_INTR_HPD_RISE) - hpd_connected = true; - + dw_hdmi->bridge) drm_helper_hpd_irq_event(dw_hdmi->bridge->dev); - drm_bridge_hpd_notify(dw_hdmi->bridge, - hpd_connected ? connector_status_connected - : connector_status_disconnected); - } return IRQ_HANDLED; } -- 2.54.0 From jonas at kwiboo.se Mon May 18 12:47:33 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:33 +0000 Subject: [PATCH 05/13] drm/meson: encoder_hdmi: Use CEC phys addr from display_info In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-6-jonas@kwiboo.se> The dw-hdmi bridge detect() func now updates EDID for the connector. Something that ensures that display_info.source_physical_address has an updated CEC phys addr when the hpd_notify() func is called. Change to use display_info source_physical_address directly instead of re-reading EDID to set the CEC phys addr at HPD interrupt. Signed-off-by: Jonas Karlman --- drivers/gpu/drm/meson/meson_encoder_hdmi.c | 26 ++++------------------ 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c index 55c0601df3c6..1b9a1d9ed3d3 100644 --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c @@ -330,28 +330,10 @@ static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge, if (!encoder_hdmi->cec_notifier) return; - if (status == connector_status_connected) { - const struct drm_edid *drm_edid; - const struct edid *edid; - - drm_edid = drm_bridge_edid_read(encoder_hdmi->bridge.next_bridge, - encoder_hdmi->connector); - if (!drm_edid) - return; - - /* - * FIXME: The CEC physical address should be set using - * cec_notifier_set_phys_addr(encoder_hdmi->cec_notifier, - * connector->display_info.source_physical_address) from a path - * that has read the EDID and called - * drm_edid_connector_update(). - */ - edid = drm_edid_raw(drm_edid); - - cec_notifier_set_phys_addr_from_edid(encoder_hdmi->cec_notifier, edid); - - drm_edid_free(drm_edid); - } else + if (status == connector_status_connected) + cec_notifier_set_phys_addr(encoder_hdmi->cec_notifier, + connector->display_info.source_physical_address); + else cec_notifier_phys_addr_invalidate(encoder_hdmi->cec_notifier); } -- 2.54.0 From jonas at kwiboo.se Mon May 18 12:47:35 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:35 +0000 Subject: [PATCH 07/13] drm/meson: encoder_hdmi: Report ycbcr_420_allowed from encoder In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-8-jonas@kwiboo.se> The bridge connector report ycbcr_420_allowed support when all bridges in the chain support ycbcr_420_allowed. Report ycbcr_420_allowed on the encoder bridge so that the bridge connector automatically can report correct ycbcr_420_allowed support. Signed-off-by: Jonas Karlman --- drivers/gpu/drm/meson/meson_encoder_hdmi.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c index 45104ef35344..484675cb8284 100644 --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c @@ -354,6 +354,7 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) meson_encoder_hdmi->bridge.of_node = priv->dev->of_node; meson_encoder_hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA; meson_encoder_hdmi->bridge.interlace_allowed = true; + meson_encoder_hdmi->bridge.ycbcr_420_allowed = true; pdev = of_find_device_by_node(remote); of_node_put(remote); @@ -406,9 +407,6 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) drm_connector_attach_max_bpc_property(meson_encoder_hdmi->connector, 8, 8); - /* Handle this here until handled by drm_bridge_connector_init() */ - meson_encoder_hdmi->connector->ycbcr_420_allowed = true; - priv->encoders[MESON_ENC_HDMI] = meson_encoder_hdmi; dev_dbg(priv->dev, "HDMI encoder initialized\n"); -- 2.54.0 From jonas at kwiboo.se Mon May 18 12:47:31 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:31 +0000 Subject: [PATCH 03/13] drm/meson: dw-hdmi: Call dw_hdmi_remove() consistently In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-4-jonas@kwiboo.se> dw-hdmi export two similar pair of functions to probe()/remove() and bind()/unbind(), update to use dw_hdmi_remove() for consistency. dw_hdmi_probe() will drm_bridge_add() the dw-hdmi bridge, so it is expected that of_drm_find_and_get_bridge() always return the dw-hdmi bridge. Regardless, validate that the dw-hdmi can be found for consistent error handling. Also always ensure the IRQ handler and bridge is released before dw_hdmi_remove() is called. Signed-off-by: Jonas Karlman --- drivers/gpu/drm/meson/meson_dw_hdmi.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c index eafe7daf6ff1..9aafdc768f2b 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -775,10 +775,17 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, platform_set_drvdata(pdev, meson_dw_hdmi); meson_dw_hdmi->hdmi = dw_hdmi_probe(pdev, &meson_dw_hdmi->dw_plat_data); - if (IS_ERR(meson_dw_hdmi->hdmi)) + if (IS_ERR(meson_dw_hdmi->hdmi)) { + devm_free_irq(dev, irq, meson_dw_hdmi); return PTR_ERR(meson_dw_hdmi->hdmi); + } meson_dw_hdmi->bridge = of_drm_find_and_get_bridge(pdev->dev.of_node); + if (!meson_dw_hdmi->bridge) { + devm_free_irq(dev, irq, meson_dw_hdmi); + dw_hdmi_remove(meson_dw_hdmi->hdmi); + return -ENODEV; + } DRM_DEBUG_DRIVER("HDMI controller initialized\n"); @@ -793,8 +800,8 @@ static void meson_dw_hdmi_unbind(struct device *dev, struct device *master, int irq = platform_get_irq(pdev, 0); devm_free_irq(dev, irq, meson_dw_hdmi); - dw_hdmi_unbind(meson_dw_hdmi->hdmi); drm_bridge_put(meson_dw_hdmi->bridge); + dw_hdmi_remove(meson_dw_hdmi->hdmi); } static const struct component_ops meson_dw_hdmi_ops = { -- 2.54.0 From jonas at kwiboo.se Mon May 18 12:47:30 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:30 +0000 Subject: [PATCH 02/13] drm/meson: dw-hdmi: Protect from possible NULL pointer dereference In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-3-jonas@kwiboo.se> The IRQ handler can be called at any time after the call to devm_request_threaded_irq() completes, even before dw_hdmi->bridge has been assigned later in meson_dw_hdmi_bind(). Protect from a possible NULL pointer dereference in IRQ handler by only calling drm_helper_hpd_irq_event() when bridge has been assigned. Fixes: e67f6037ae1b ("drm/meson: split out encoder from meson_dw_hdmi") Signed-off-by: Jonas Karlman --- I only observed this NULL pointer dereference one time, without being able to reliably re-create a similar timing scenario. I still think this is an issue that possible could happen and likely should be fixed. Note that patches later in this series will fully replace this change. --- drivers/gpu/drm/meson/meson_dw_hdmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c index 993f6d5d4b29..eafe7daf6ff1 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -520,7 +520,8 @@ static irqreturn_t dw_hdmi_top_thread_irq(int irq, void *dev_id) u32 stat = dw_hdmi->irq_stat; /* HPD Events */ - if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) { + if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL) && + dw_hdmi->bridge) { bool hpd_connected = false; if (stat & HDMITX_TOP_INTR_HPD_RISE) -- 2.54.0 From jonas at kwiboo.se Mon May 18 12:47:36 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:36 +0000 Subject: [PATCH 08/13] drm/meson: dw-hdmi: Use local dev variable consistently in bind() In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-9-jonas@kwiboo.se> Replace indirect struct device accesses via pdev->dev with the local dev parameter already available in meson_dw_hdmi_bind(), for consistency and readability. Signed-off-by: Jonas Karlman --- drivers/gpu/drm/meson/meson_dw_hdmi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c index 30099bf71aad..fcd2426af9fc 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -671,9 +671,9 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, DRM_DEBUG_DRIVER("\n"); - match = of_device_get_match_data(&pdev->dev); + match = of_device_get_match_data(dev); if (!match) { - dev_err(&pdev->dev, "failed to get match data\n"); + dev_err(dev, "failed to get match data\n"); return -ENODEV; } @@ -771,7 +771,7 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, return PTR_ERR(meson_dw_hdmi->hdmi); } - meson_dw_hdmi->bridge = of_drm_find_and_get_bridge(pdev->dev.of_node); + meson_dw_hdmi->bridge = of_drm_find_and_get_bridge(dev->of_node); if (!meson_dw_hdmi->bridge) { devm_free_irq(dev, irq, meson_dw_hdmi); dw_hdmi_remove(meson_dw_hdmi->hdmi); -- 2.54.0 From jonas at kwiboo.se Mon May 18 12:47:34 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:34 +0000 Subject: [PATCH 06/13] drm/meson: encoder_hdmi: Use bridge connector CEC notifier In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-7-jonas@kwiboo.se> The dw-hdmi bridge detect() func now updates EDID and CEC phys addr for the connector and any registered generic CEC notifier. Replace the open-coded CEC notifier handling with use of the bridge CEC notifier op. Signed-off-by: Jonas Karlman --- drivers/gpu/drm/meson/Kconfig | 1 + drivers/gpu/drm/meson/meson_encoder_hdmi.c | 85 +++++----------------- 2 files changed, 19 insertions(+), 67 deletions(-) diff --git a/drivers/gpu/drm/meson/Kconfig b/drivers/gpu/drm/meson/Kconfig index 417f79829cf8..0dc5ca21e4fc 100644 --- a/drivers/gpu/drm/meson/Kconfig +++ b/drivers/gpu/drm/meson/Kconfig @@ -13,6 +13,7 @@ config DRM_MESON select REGMAP_MMIO select MESON_CANVAS select CEC_CORE if CEC_NOTIFIER + select DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER if CEC_NOTIFIER config DRM_MESON_DW_HDMI tristate "HDMI Synopsys Controller support for Amlogic Meson Display" diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c index 1b9a1d9ed3d3..45104ef35344 100644 --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c @@ -16,8 +16,6 @@ #include #include -#include - #include #include #include @@ -41,7 +39,6 @@ struct meson_encoder_hdmi { struct drm_connector *connector; struct meson_drm *priv; unsigned long output_bus_fmt; - struct cec_notifier *cec_notifier; }; #define bridge_to_meson_encoder_hdmi(x) \ @@ -57,14 +54,6 @@ static int meson_encoder_hdmi_attach(struct drm_bridge *bridge, &encoder_hdmi->bridge, flags); } -static void meson_encoder_hdmi_detach(struct drm_bridge *bridge) -{ - struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); - - cec_notifier_conn_unregister(encoder_hdmi->cec_notifier); - encoder_hdmi->cec_notifier = NULL; -} - static void meson_encoder_hdmi_set_vclk(struct meson_encoder_hdmi *encoder_hdmi, const struct drm_display_mode *mode) { @@ -321,27 +310,9 @@ static int meson_encoder_hdmi_atomic_check(struct drm_bridge *bridge, return 0; } -static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge, - struct drm_connector *connector, - enum drm_connector_status status) -{ - struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); - - if (!encoder_hdmi->cec_notifier) - return; - - if (status == connector_status_connected) - cec_notifier_set_phys_addr(encoder_hdmi->cec_notifier, - connector->display_info.source_physical_address); - else - cec_notifier_phys_addr_invalidate(encoder_hdmi->cec_notifier); -} - static const struct drm_bridge_funcs meson_encoder_hdmi_bridge_funcs = { .attach = meson_encoder_hdmi_attach, - .detach = meson_encoder_hdmi_detach, .mode_valid = meson_encoder_hdmi_mode_valid, - .hpd_notify = meson_encoder_hdmi_hpd_notify, .atomic_enable = meson_encoder_hdmi_atomic_enable, .atomic_disable = meson_encoder_hdmi_atomic_disable, .atomic_get_input_bus_fmts = meson_encoder_hdmi_get_inp_bus_fmts, @@ -374,9 +345,9 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) meson_encoder_hdmi->bridge.next_bridge = of_drm_find_and_get_bridge(remote); if (!meson_encoder_hdmi->bridge.next_bridge) { - ret = dev_err_probe(priv->dev, -EPROBE_DEFER, - "Failed to find HDMI transceiver bridge\n"); - goto err_put_node; + of_node_put(remote); + return dev_err_probe(priv->dev, -EPROBE_DEFER, + "Failed to find HDMI transceiver bridge\n"); } /* HDMI Encoder Bridge */ @@ -384,6 +355,13 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) meson_encoder_hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA; meson_encoder_hdmi->bridge.interlace_allowed = true; + pdev = of_find_device_by_node(remote); + of_node_put(remote); + if (pdev) { + meson_encoder_hdmi->bridge.ops |= DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER; + meson_encoder_hdmi->bridge.hdmi_cec_dev = &pdev->dev; + } + drm_bridge_add(&meson_encoder_hdmi->bridge); meson_encoder_hdmi->priv = priv; @@ -391,30 +369,24 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) /* Encoder */ ret = drm_simple_encoder_init(priv->drm, &meson_encoder_hdmi->encoder, DRM_MODE_ENCODER_TMDS); - if (ret) { - dev_err_probe(priv->dev, ret, "Failed to init HDMI encoder\n"); - goto err_put_node; - } + if (ret) + return dev_err_probe(priv->dev, ret, "Failed to init HDMI encoder\n"); meson_encoder_hdmi->encoder.possible_crtcs = BIT(0); /* Attach HDMI Encoder Bridge to Encoder */ ret = drm_bridge_attach(&meson_encoder_hdmi->encoder, &meson_encoder_hdmi->bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); - if (ret) { - dev_err_probe(priv->dev, ret, "Failed to attach bridge\n"); - goto err_put_node; - } + if (ret) + return dev_err_probe(priv->dev, ret, "Failed to attach bridge\n"); /* Initialize & attach Bridge Connector */ meson_encoder_hdmi->connector = drm_bridge_connector_init(priv->drm, &meson_encoder_hdmi->encoder); - if (IS_ERR(meson_encoder_hdmi->connector)) { - ret = dev_err_probe(priv->dev, - PTR_ERR(meson_encoder_hdmi->connector), - "Unable to create HDMI bridge connector\n"); - goto err_put_node; - } + if (IS_ERR(meson_encoder_hdmi->connector)) + return dev_err_probe(priv->dev, + PTR_ERR(meson_encoder_hdmi->connector), + "Unable to create HDMI bridge connector\n"); /* * We should have now in place: @@ -437,32 +409,11 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) /* Handle this here until handled by drm_bridge_connector_init() */ meson_encoder_hdmi->connector->ycbcr_420_allowed = true; - pdev = of_find_device_by_node(remote); - of_node_put(remote); - if (pdev) { - struct cec_connector_info conn_info; - struct cec_notifier *notifier; - - cec_fill_conn_info_from_drm(&conn_info, meson_encoder_hdmi->connector); - - notifier = cec_notifier_conn_register(&pdev->dev, NULL, &conn_info); - if (!notifier) { - put_device(&pdev->dev); - return -ENOMEM; - } - - meson_encoder_hdmi->cec_notifier = notifier; - } - priv->encoders[MESON_ENC_HDMI] = meson_encoder_hdmi; dev_dbg(priv->dev, "HDMI encoder initialized\n"); return 0; - -err_put_node: - of_node_put(remote); - return ret; } void meson_encoder_hdmi_remove(struct meson_drm *priv) -- 2.54.0 From jonas at kwiboo.se Mon May 18 12:47:37 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:37 +0000 Subject: [PATCH 09/13] drm/meson: dw-hdmi: Use devm_clk_get_enabled() helper In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-10-jonas@kwiboo.se> Change to use the devm_clk_get_enabled() helper instead of using an open-coded variant. Signed-off-by: Jonas Karlman --- drivers/gpu/drm/meson/meson_dw_hdmi.c | 48 +++++++++------------------ 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c index fcd2426af9fc..d0cf2042d41c 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -634,29 +634,6 @@ static void meson_dw_hdmi_init(struct meson_dw_hdmi *meson_dw_hdmi) } -static void meson_disable_clk(void *data) -{ - clk_disable_unprepare(data); -} - -static int meson_enable_clk(struct device *dev, char *name) -{ - struct clk *clk; - int ret; - - clk = devm_clk_get(dev, name); - if (IS_ERR(clk)) { - dev_err(dev, "Unable to get %s pclk\n", name); - return PTR_ERR(clk); - } - - ret = clk_prepare_enable(clk); - if (!ret) - ret = devm_add_action_or_reset(dev, meson_disable_clk, clk); - - return ret; -} - static int meson_dw_hdmi_bind(struct device *dev, struct device *master, void *data) { @@ -666,6 +643,7 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, struct drm_device *drm = data; struct meson_drm *priv = drm->dev_private; struct dw_hdmi_plat_data *dw_plat_data; + struct clk *clk; int irq; int ret; @@ -716,17 +694,23 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, if (IS_ERR(meson_dw_hdmi->hdmitx)) return PTR_ERR(meson_dw_hdmi->hdmitx); - ret = meson_enable_clk(dev, "isfr"); - if (ret) - return ret; + clk = devm_clk_get_enabled(dev, "isfr"); + if (IS_ERR(clk)) { + dev_err(dev, "Unable to get isfr pclk\n"); + return PTR_ERR(clk); + } - ret = meson_enable_clk(dev, "iahb"); - if (ret) - return ret; + clk = devm_clk_get_enabled(dev, "iahb"); + if (IS_ERR(clk)) { + dev_err(dev, "Unable to get iahb pclk\n"); + return PTR_ERR(clk); + } - ret = meson_enable_clk(dev, "venci"); - if (ret) - return ret; + clk = devm_clk_get_enabled(dev, "venci"); + if (IS_ERR(clk)) { + dev_err(dev, "Unable to get venci pclk\n"); + return PTR_ERR(clk); + } dw_plat_data->regm = devm_regmap_init(dev, NULL, meson_dw_hdmi, &meson_dw_hdmi_regmap_config); -- 2.54.0 From jonas at kwiboo.se Mon May 18 12:47:38 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:38 +0000 Subject: [PATCH 10/13] drm/meson: dw-hdmi: Use dev_err_probe() to report errors In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-11-jonas@kwiboo.se> Change to use dev_err_probe() to report bind() errors consistently. Signed-off-by: Jonas Karlman --- drivers/gpu/drm/meson/meson_dw_hdmi.c | 65 +++++++++++++-------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c index d0cf2042d41c..1dd59196ff7f 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -650,10 +650,9 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, DRM_DEBUG_DRIVER("\n"); match = of_device_get_match_data(dev); - if (!match) { - dev_err(dev, "failed to get match data\n"); - return -ENODEV; - } + if (!match) + return dev_err_probe(dev, -ENODEV, + "Failed to get match data\n"); meson_dw_hdmi = devm_kzalloc(dev, sizeof(*meson_dw_hdmi), GFP_KERNEL); @@ -667,50 +666,45 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, ret = devm_regulator_get_enable_optional(dev, "hdmi"); if (ret < 0 && ret != -ENODEV) - return ret; + return dev_err_probe(dev, ret, + "Failed to get/enable hdmi regulator\n"); meson_dw_hdmi->hdmitx_apb = devm_reset_control_get_exclusive(dev, "hdmitx_apb"); - if (IS_ERR(meson_dw_hdmi->hdmitx_apb)) { - dev_err(dev, "Failed to get hdmitx_apb reset\n"); - return PTR_ERR(meson_dw_hdmi->hdmitx_apb); - } + if (IS_ERR(meson_dw_hdmi->hdmitx_apb)) + return dev_err_probe(dev, PTR_ERR(meson_dw_hdmi->hdmitx_apb), + "Failed to get hdmitx_apb reset\n"); meson_dw_hdmi->hdmitx_ctrl = devm_reset_control_get_exclusive(dev, "hdmitx"); - if (IS_ERR(meson_dw_hdmi->hdmitx_ctrl)) { - dev_err(dev, "Failed to get hdmitx reset\n"); - return PTR_ERR(meson_dw_hdmi->hdmitx_ctrl); - } + if (IS_ERR(meson_dw_hdmi->hdmitx_ctrl)) + return dev_err_probe(dev, PTR_ERR(meson_dw_hdmi->hdmitx_ctrl), + "Failed to get hdmitx reset\n"); meson_dw_hdmi->hdmitx_phy = devm_reset_control_get_exclusive(dev, "hdmitx_phy"); - if (IS_ERR(meson_dw_hdmi->hdmitx_phy)) { - dev_err(dev, "Failed to get hdmitx_phy reset\n"); - return PTR_ERR(meson_dw_hdmi->hdmitx_phy); - } + if (IS_ERR(meson_dw_hdmi->hdmitx_phy)) + return dev_err_probe(dev, PTR_ERR(meson_dw_hdmi->hdmitx_phy), + "Failed to get hdmitx_phy reset\n"); meson_dw_hdmi->hdmitx = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(meson_dw_hdmi->hdmitx)) return PTR_ERR(meson_dw_hdmi->hdmitx); clk = devm_clk_get_enabled(dev, "isfr"); - if (IS_ERR(clk)) { - dev_err(dev, "Unable to get isfr pclk\n"); - return PTR_ERR(clk); - } + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to get isfr pclk\n"); clk = devm_clk_get_enabled(dev, "iahb"); - if (IS_ERR(clk)) { - dev_err(dev, "Unable to get iahb pclk\n"); - return PTR_ERR(clk); - } + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to get iahb pclk\n"); clk = devm_clk_get_enabled(dev, "venci"); - if (IS_ERR(clk)) { - dev_err(dev, "Unable to get venci pclk\n"); - return PTR_ERR(clk); - } + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to get venci pclk\n"); dw_plat_data->regm = devm_regmap_init(dev, NULL, meson_dw_hdmi, &meson_dw_hdmi_regmap_config); @@ -724,10 +718,9 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, ret = devm_request_threaded_irq(dev, irq, dw_hdmi_top_irq, dw_hdmi_top_thread_irq, IRQF_SHARED, "dw_hdmi_top_irq", meson_dw_hdmi); - if (ret) { - dev_err(dev, "Failed to request hdmi top irq\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, + "Failed to request hdmi top irq\n"); meson_dw_hdmi_init(meson_dw_hdmi); @@ -752,14 +745,16 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, meson_dw_hdmi->hdmi = dw_hdmi_probe(pdev, &meson_dw_hdmi->dw_plat_data); if (IS_ERR(meson_dw_hdmi->hdmi)) { devm_free_irq(dev, irq, meson_dw_hdmi); - return PTR_ERR(meson_dw_hdmi->hdmi); + return dev_err_probe(dev, PTR_ERR(meson_dw_hdmi->hdmi), + "Failed to probe dw-hdmi bridge\n"); } meson_dw_hdmi->bridge = of_drm_find_and_get_bridge(dev->of_node); if (!meson_dw_hdmi->bridge) { devm_free_irq(dev, irq, meson_dw_hdmi); dw_hdmi_remove(meson_dw_hdmi->hdmi); - return -ENODEV; + return dev_err_probe(dev, -ENODEV, + "Failed to find dw-hdmi bridge\n"); } DRM_DEBUG_DRIVER("HDMI controller initialized\n"); -- 2.54.0 From jonas at kwiboo.se Mon May 18 12:47:40 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:40 +0000 Subject: [PATCH 12/13] drm/meson: dw-hdmi: Use dw_hdmi_schedule_hpd_work() helper In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-13-jonas@kwiboo.se> Change to use the dw-hdmi HPD delayed work to handle HPD events. Also merge top and bottom half IRQ handlers to simplify IRQ handling now that HPD event is handled using a delayed work. Signed-off-by: Jonas Karlman --- drivers/gpu/drm/meson/meson_dw_hdmi.c | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c index 1dd59196ff7f..7b465e216759 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -145,7 +145,6 @@ struct meson_dw_hdmi { struct reset_control *hdmitx_apb; struct reset_control *hdmitx_ctrl; struct reset_control *hdmitx_phy; - u32 irq_stat; struct dw_hdmi *hdmi; struct drm_bridge *bridge; }; @@ -498,10 +497,10 @@ static irqreturn_t dw_hdmi_top_irq(int irq, void *dev_id) stat = dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_INTR_STAT); dw_hdmi->data->top_write(dw_hdmi, HDMITX_TOP_INTR_STAT_CLR, stat); - /* HPD Events, handle in the threaded interrupt handler */ + /* HPD Events, handle in delayed work */ if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) { - dw_hdmi->irq_stat = stat; - return IRQ_WAKE_THREAD; + dw_hdmi_schedule_hpd_work(dw_hdmi->hdmi); + return IRQ_HANDLED; } /* HDMI Controller Interrupt */ @@ -513,20 +512,6 @@ static irqreturn_t dw_hdmi_top_irq(int irq, void *dev_id) return IRQ_HANDLED; } -/* Threaded interrupt handler to manage HPD events */ -static irqreturn_t dw_hdmi_top_thread_irq(int irq, void *dev_id) -{ - struct meson_dw_hdmi *dw_hdmi = dev_id; - u32 stat = dw_hdmi->irq_stat; - - /* HPD Events */ - if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL) && - dw_hdmi->bridge) - drm_helper_hpd_irq_event(dw_hdmi->bridge->dev); - - return IRQ_HANDLED; -} - /* DW HDMI Regmap */ static int meson_dw_hdmi_reg_read(void *context, unsigned int reg, @@ -715,9 +700,8 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, if (irq < 0) return irq; - ret = devm_request_threaded_irq(dev, irq, dw_hdmi_top_irq, - dw_hdmi_top_thread_irq, IRQF_SHARED, - "dw_hdmi_top_irq", meson_dw_hdmi); + ret = devm_request_irq(dev, irq, dw_hdmi_top_irq, IRQF_SHARED, + "dw_hdmi_top_irq", meson_dw_hdmi); if (ret) return dev_err_probe(dev, ret, "Failed to request hdmi top irq\n"); -- 2.54.0 From jonas at kwiboo.se Mon May 18 12:47:39 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:39 +0000 Subject: [PATCH 11/13] drm/bridge: dw-hdmi: Export dw_hdmi_schedule_hpd_work() helper In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-12-jonas@kwiboo.se> Export a dw_hdmi_schedule_hpd_work() helper that schedule the HPD delayed work. Primarily to be used by the meson dw-hdmi driver. Signed-off-by: Jonas Karlman --- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 11 +++++++++-- include/drm/bridge/dw_hdmi.h | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c index 99dd62b6becf..54d75317ce9c 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c @@ -3014,8 +3014,7 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) status == connector_status_connected ? "plugin" : "plugout"); - mod_delayed_work(system_percpu_wq, &hdmi->hpd_work, - msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); + dw_hdmi_schedule_hpd_work(hdmi); hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0); hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0); @@ -3025,6 +3024,14 @@ static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id) return ret; } +void dw_hdmi_schedule_hpd_work(struct dw_hdmi *hdmi) +{ + if (!IS_ERR_OR_NULL(hdmi)) + mod_delayed_work(system_percpu_wq, &hdmi->hpd_work, + msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); +} +EXPORT_SYMBOL_GPL(dw_hdmi_schedule_hpd_work); + static void dw_hdmi_hpd_work(struct work_struct *work) { struct dw_hdmi *hdmi = container_of(work, struct dw_hdmi, hpd_work.work); diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index 10013b8d3adb..c56d1775f04a 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -184,6 +184,8 @@ struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev, void dw_hdmi_resume(struct dw_hdmi *hdmi); +void dw_hdmi_schedule_hpd_work(struct dw_hdmi *hdmi); + 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 From jonas at kwiboo.se Mon May 18 12:47:41 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Mon, 18 May 2026 19:47:41 +0000 Subject: [PATCH 13/13] drm/meson: dw-hdmi: Use suspend_late/resume_early/resume_noirq pm ops In-Reply-To: <20260518194744.2483580-1-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> Message-ID: <20260518194744.2483580-14-jonas@kwiboo.se> meson_drv_pm_suspend()/drm_mode_config_helper_suspend() and meson_drv_pm_resume()/drm_mode_config_helper_resume() is called after/before the suspend/resume pm ops of dw-hdmi. This result in atomic_disable() being called after meson_dw_hdmi_pm_suspend() and atomic_enable() before meson_dw_hdmi_pm_resume()/dw_hdmi_resume() is called. Suspend (without changes): - meson_dw_hdmi_pm_suspend() - meson_drv_pm_suspend() - drm_mode_config_helper_suspend() - atomic_disable() Resume (without changes): - resume_irq() <<-- system freeze at hdmi-tx irq on G12B - meson_drv_pm_resume() - drm_mode_config_helper_resume() - atomic_enable() - meson_dw_hdmi_pm_resume() - meson_dw_hdmi_init() - dw_hdmi_resume() - dw_hdmi_init_hw() Change to use suspend_late/resume_early pm ops for system suspend to ensure pm ops for dw-hdmi is run after/before meson_drv pm ops. The G12B can trigger an IRQ (stat=0x40) very early during resume_irq that causes a system freeze. Move the meson_dw_hdmi_init() call to resume_noirq pm ops to resolve the system freeze. Also move meson_dw_hdmi_init() to run before interrupt allocation to ensure HW has been initialized when first IRQ is triggered. Suspend (with changes): - meson_drv_pm_suspend() - drm_mode_config_helper_suspend() - atomic_disable() - meson_dw_hdmi_pm_suspend_late() Resume (with changes): - meson_dw_hdmi_pm_resume_noirq() - meson_dw_hdmi_init() - resume_irq() - meson_dw_hdmi_pm_resume_early() - dw_hdmi_resume() - dw_hdmi_init_hw() - meson_drv_pm_resume() - drm_mode_config_helper_resume() - atomic_enable() Signed-off-by: Jonas Karlman --- This can be tested using CONFIG_PM_DEBUG with a simulated suspend: echo N > /sys/module/printk/parameters/console_suspend echo 1 > /sys/power/pm_debug_messages echo platform > /sys/power/pm_test echo mem > /sys/power/state or using something like following for real suspend/resume: echo N > /sys/module/printk/parameters/console_suspend rtcwake -m mem -s 5 -d /dev/rtc1 On S905X a simulated platform pm_test could only be tested. On G12A both simulated and real suspend works. And on G12B the board seems to freeze at a later stage when taking out of real suspend, however platform pm_test works. --- drivers/gpu/drm/meson/meson_dw_hdmi.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c index 7b465e216759..16f15466a6f4 100644 --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c @@ -696,6 +696,8 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, if (IS_ERR(dw_plat_data->regm)) return PTR_ERR(dw_plat_data->regm); + meson_dw_hdmi_init(meson_dw_hdmi); + irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; @@ -706,8 +708,6 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, return dev_err_probe(dev, ret, "Failed to request hdmi top irq\n"); - meson_dw_hdmi_init(meson_dw_hdmi); - /* Bridge / Connector */ dw_plat_data->priv_data = meson_dw_hdmi; @@ -763,7 +763,7 @@ static const struct component_ops meson_dw_hdmi_ops = { .unbind = meson_dw_hdmi_unbind, }; -static int __maybe_unused meson_dw_hdmi_pm_suspend(struct device *dev) +static int __maybe_unused meson_dw_hdmi_pm_suspend_late(struct device *dev) { struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev); @@ -777,7 +777,19 @@ static int __maybe_unused meson_dw_hdmi_pm_suspend(struct device *dev) return 0; } -static int __maybe_unused meson_dw_hdmi_pm_resume(struct device *dev) +static int __maybe_unused meson_dw_hdmi_pm_resume_early(struct device *dev) +{ + struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev); + + if (!meson_dw_hdmi) + return 0; + + dw_hdmi_resume(meson_dw_hdmi->hdmi); + + return 0; +} + +static int __maybe_unused meson_dw_hdmi_pm_resume_noirq(struct device *dev) { struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev); @@ -786,8 +798,6 @@ static int __maybe_unused meson_dw_hdmi_pm_resume(struct device *dev) meson_dw_hdmi_init(meson_dw_hdmi); - dw_hdmi_resume(meson_dw_hdmi->hdmi); - return 0; } @@ -802,8 +812,9 @@ static void meson_dw_hdmi_remove(struct platform_device *pdev) } static const struct dev_pm_ops meson_dw_hdmi_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(meson_dw_hdmi_pm_suspend, - meson_dw_hdmi_pm_resume) + SET_LATE_SYSTEM_SLEEP_PM_OPS(meson_dw_hdmi_pm_suspend_late, + meson_dw_hdmi_pm_resume_early) + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, meson_dw_hdmi_pm_resume_noirq) }; static const struct of_device_id meson_dw_hdmi_of_table[] = { -- 2.54.0 From hverkuil+cisco at kernel.org Mon May 18 23:21:30 2026 From: hverkuil+cisco at kernel.org (Hans Verkuil) Date: Tue, 19 May 2026 08:21:30 +0200 Subject: [PATCH v7 03/23] drm: bridge: dw_hdmi: Free IRQ before CEC adapter is unregistered In-Reply-To: <20260518180206.2480119-4-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-4-jonas@kwiboo.se> Message-ID: On 18/05/2026 20:01, Jonas Karlman wrote: > The interrupt allocated with devm_request_threaded_irq() can be > use-after-free when the devres release action try to free_irq(). > > KASAN report a slab-use-after-free in dw_hdmi_cec_hardirq during unbind: > > Call trace: > [...] > dw_hdmi_cec_hardirq+0x4cc/0x560 > free_irq+0x48c/0x7e4 > devm_irq_release+0x54/0x90 > dr_node_release+0x38/0x5c > release_nodes+0xac/0x130 > devres_release_all+0xf4/0x1b0 > device_unbind_cleanup+0x28/0x1f8 > device_release_driver_internal+0x358/0x470 > device_release_driver+0x18/0x24 > bus_remove_device+0x33c/0x4f0 > device_del+0x2d8/0x790 > platform_device_del+0x34/0x1e0 > platform_device_unregister+0x14/0x3c > dw_hdmi_remove+0x74/0x180 > [...] > > Freed by: > [...] > kfree+0x1dc/0x5dc > cec_delete_adapter+0xd4/0x118 > cec_devnode_release+0xa4/0xe0 > device_release+0xa0/0x200 > kobject_put+0x14c/0x26c > put_device+0x14/0x30 > cec_unregister_adapter+0x20c/0x280 > dw_hdmi_cec_remove+0x8c/0xd0 > [...] > > Explicitly devm_free_irq() before the CEC adapter is unregistered to > fix this possible use-after-free issue. > > Fixes: a616e63c56ef ("drm/bridge: dw-hdmi: add cec driver") > Signed-off-by: Jonas Karlman Acked-by: Hans Verkuil Regards, Hans > --- > v7: New patch > > KASAN report a slab-use-after-free in dw_hdmi_cec_hardirq when, > echo fe0a0000.hdmi > /sys/bus/platform/drivers/dwhdmi-rockchip/unbind > on a Rockchip RK3566 device prior to this fix. > --- > drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c | 1 + > 1 file changed, 1 insertion(+) > > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c > index 9549dabde941..67a2a242d3ca 100644 > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c > @@ -309,6 +309,7 @@ static void dw_hdmi_cec_remove(struct platform_device *pdev) > struct dw_hdmi_cec *cec = platform_get_drvdata(pdev); > > cec_notifier_cec_adap_unregister(cec->notify, cec->adap); > + devm_free_irq(&pdev->dev, cec->irq, cec->adap); > cec_unregister_adapter(cec->adap); > } > From hverkuil+cisco at kernel.org Mon May 18 23:22:34 2026 From: hverkuil+cisco at kernel.org (Hans Verkuil) Date: Tue, 19 May 2026 08:22:34 +0200 Subject: [PATCH v7 09/23] drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup In-Reply-To: <20260518180206.2480119-10-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-10-jonas@kwiboo.se> Message-ID: <3b6e1fa8-228c-4e2e-b985-72a3f9a1daf0@kernel.org> On 18/05/2026 20:01, Jonas Karlman wrote: > 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 # Rock64, RockPro64, Quartz64-B > Signed-off-by: Jonas Karlman Acked-by: Hans Verkuil Regards, Hans > --- > v7: No change > 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, From hverkuil+cisco at kernel.org Mon May 18 23:25:07 2026 From: hverkuil+cisco at kernel.org (Hans Verkuil) Date: Tue, 19 May 2026 08:25:07 +0200 Subject: [PATCH v7 10/23] drm: bridge: dw_hdmi: Invalidate CEC phys addr from connector detect In-Reply-To: <20260518180206.2480119-11-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-11-jonas@kwiboo.se> Message-ID: On 18/05/2026 20:01, Jonas Karlman wrote: > Wait until the connector detect ops is called to invalidate CEC phys > addr instead of doing it directly from the irq handler. > > The CEC notifier is only registered for the dw-hdmi connector so this > has no impact when the bridge connector is used. > > Reviewed-by: Neil Armstrong > Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B > Signed-off-by: Jonas Karlman Acked-by: Hans Verkuil Regards, Hans > --- > v7: Update commit message > 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; From hverkuil+cisco at kernel.org Mon May 18 23:26:28 2026 From: hverkuil+cisco at kernel.org (Hans Verkuil) Date: Tue, 19 May 2026 08:26:28 +0200 Subject: [PATCH v7 12/23] drm: bridge: dw_hdmi: Extract dw_hdmi_connector_status_update() In-Reply-To: <20260518180206.2480119-13-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-13-jonas@kwiboo.se> Message-ID: <758d577f-cd03-4c41-a9f6-9d8ef909fd9a@kernel.org> On 18/05/2026 20:01, Jonas Karlman wrote: > 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 > Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B > Signed-off-by: Jonas Karlman Acked-by: Hans Verkuil Regards, Hans > --- > v7: No change > 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, From hverkuil+cisco at kernel.org Mon May 18 23:28:09 2026 From: hverkuil+cisco at kernel.org (Hans Verkuil) Date: Tue, 19 May 2026 08:28:09 +0200 Subject: [PATCH v7 11/23] drm: bridge: dw_hdmi: Remove cec_notifier_mutex In-Reply-To: <20260518180206.2480119-12-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-12-jonas@kwiboo.se> Message-ID: <44a15912-b099-43aa-afe1-83f2025309cd@kernel.org> On 18/05/2026 20:01, Jonas Karlman wrote: > 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 > Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B > Signed-off-by: Jonas Karlman Acked-by: Hans Verkuil Nice, I wondered why that mutex was there at all. Regards, Hans > --- > v7: No change > 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); From hverkuil+cisco at kernel.org Mon May 18 23:29:22 2026 From: hverkuil+cisco at kernel.org (Hans Verkuil) Date: Tue, 19 May 2026 08:29:22 +0200 Subject: [PATCH v7 13/23] drm: bridge: dw_hdmi: Use dw_hdmi_connector_status_update() In-Reply-To: <20260518180206.2480119-14-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-14-jonas@kwiboo.se> Message-ID: <46d05ad3-0334-4fad-816c-cce0d5403a14@kernel.org> On 18/05/2026 20:01, Jonas Karlman wrote: > 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 > Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B > Signed-off-by: Jonas Karlman Acked-by: Hans Verkuil Regards, Hans > --- > v7: No change > 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) From hverkuil+cisco at kernel.org Mon May 18 23:32:41 2026 From: hverkuil+cisco at kernel.org (Hans Verkuil) Date: Tue, 19 May 2026 08:32:41 +0200 Subject: [PATCH v7 15/23] drm: bridge: dw_hdmi: Use generic CEC notifier helpers In-Reply-To: <20260518180206.2480119-16-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-16-jonas@kwiboo.se> Message-ID: <4b51d6a4-057a-4a92-86c2-a6f41b5d4608@kernel.org> On 18/05/2026 20:01, Jonas Karlman wrote: > 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 > Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B > Signed-off-by: Jonas Karlman Acked-by: Hans Verkuil Regards, Hans > --- > v7: No change > 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 0e84dff72470..37406555af7b 100644 > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > @@ -23,12 +23,11 @@ > #include > #include > > -#include > - > #include > #include > > #include > +#include > #include > #include > #include > @@ -183,8 +182,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; > @@ -2453,7 +2450,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; > } > > @@ -2462,8 +2459,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 > @@ -2525,9 +2521,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); > } > @@ -2550,8 +2543,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) > @@ -2587,15 +2578,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); > } > > /* ----------------------------------------------------------------------------- From hverkuil+cisco at kernel.org Mon May 18 23:35:23 2026 From: hverkuil+cisco at kernel.org (Hans Verkuil) Date: Tue, 19 May 2026 08:35:23 +0200 Subject: [PATCH v7 17/23] drm: bridge: dw_hdmi: Declare bridge CEC notifier support In-Reply-To: <20260518180206.2480119-18-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-18-jonas@kwiboo.se> Message-ID: <3672b556-e66d-471d-90f9-ef1078527929@kernel.org> On 18/05/2026 20:01, Jonas Karlman wrote: > 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 Acked-by: Hans Verkuil Regards, Hans > --- > v7: Only declare CEC notifier support when CEC device register succeeds > v6: New patch > --- > drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 4 ++++ > 1 file changed, 4 insertions(+) > > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index 0c4388e7aa5e..5dacb8a99715 100644 > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > @@ -3515,6 +3515,10 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev, > pdevinfo.dma_mask = 0; > > hdmi->cec = platform_device_register_full(&pdevinfo); > + if (!IS_ERR(hdmi->cec)) { > + hdmi->bridge.ops |= DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER; > + hdmi->bridge.hdmi_cec_dev = hdmi->dev; > + } > } > > drm_bridge_add(&hdmi->bridge); From hverkuil+cisco at kernel.org Mon May 18 23:36:50 2026 From: hverkuil+cisco at kernel.org (Hans Verkuil) Date: Tue, 19 May 2026 08:36:50 +0200 Subject: [PATCH 05/13] drm/meson: encoder_hdmi: Use CEC phys addr from display_info In-Reply-To: <20260518194744.2483580-6-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> <20260518194744.2483580-6-jonas@kwiboo.se> Message-ID: <48fdaecd-a8b9-473d-aad2-35f5e459f161@kernel.org> On 18/05/2026 21:47, Jonas Karlman wrote: > The dw-hdmi bridge detect() func now updates EDID for the connector. > Something that ensures that display_info.source_physical_address has an > updated CEC phys addr when the hpd_notify() func is called. > > Change to use display_info source_physical_address directly instead of > re-reading EDID to set the CEC phys addr at HPD interrupt. > > Signed-off-by: Jonas Karlman Acked-by: Hans Verkuil Regards, Hans > --- > drivers/gpu/drm/meson/meson_encoder_hdmi.c | 26 ++++------------------ > 1 file changed, 4 insertions(+), 22 deletions(-) > > diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > index 55c0601df3c6..1b9a1d9ed3d3 100644 > --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > @@ -330,28 +330,10 @@ static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge, > if (!encoder_hdmi->cec_notifier) > return; > > - if (status == connector_status_connected) { > - const struct drm_edid *drm_edid; > - const struct edid *edid; > - > - drm_edid = drm_bridge_edid_read(encoder_hdmi->bridge.next_bridge, > - encoder_hdmi->connector); > - if (!drm_edid) > - return; > - > - /* > - * FIXME: The CEC physical address should be set using > - * cec_notifier_set_phys_addr(encoder_hdmi->cec_notifier, > - * connector->display_info.source_physical_address) from a path > - * that has read the EDID and called > - * drm_edid_connector_update(). > - */ > - edid = drm_edid_raw(drm_edid); > - > - cec_notifier_set_phys_addr_from_edid(encoder_hdmi->cec_notifier, edid); > - > - drm_edid_free(drm_edid); > - } else > + if (status == connector_status_connected) > + cec_notifier_set_phys_addr(encoder_hdmi->cec_notifier, > + connector->display_info.source_physical_address); > + else > cec_notifier_phys_addr_invalidate(encoder_hdmi->cec_notifier); > } > From hverkuil+cisco at kernel.org Mon May 18 23:38:23 2026 From: hverkuil+cisco at kernel.org (Hans Verkuil) Date: Tue, 19 May 2026 08:38:23 +0200 Subject: [PATCH 06/13] drm/meson: encoder_hdmi: Use bridge connector CEC notifier In-Reply-To: <20260518194744.2483580-7-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> <20260518194744.2483580-7-jonas@kwiboo.se> Message-ID: <0133e67b-0c85-4805-87a6-7fe30f9856f2@kernel.org> On 18/05/2026 21:47, Jonas Karlman wrote: > The dw-hdmi bridge detect() func now updates EDID and CEC phys addr for > the connector and any registered generic CEC notifier. > > Replace the open-coded CEC notifier handling with use of the bridge CEC > notifier op. > > Signed-off-by: Jonas Karlman Acked-by: Hans Verkuil Regards, Hans > --- > drivers/gpu/drm/meson/Kconfig | 1 + > drivers/gpu/drm/meson/meson_encoder_hdmi.c | 85 +++++----------------- > 2 files changed, 19 insertions(+), 67 deletions(-) > > diff --git a/drivers/gpu/drm/meson/Kconfig b/drivers/gpu/drm/meson/Kconfig > index 417f79829cf8..0dc5ca21e4fc 100644 > --- a/drivers/gpu/drm/meson/Kconfig > +++ b/drivers/gpu/drm/meson/Kconfig > @@ -13,6 +13,7 @@ config DRM_MESON > select REGMAP_MMIO > select MESON_CANVAS > select CEC_CORE if CEC_NOTIFIER > + select DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER if CEC_NOTIFIER > > config DRM_MESON_DW_HDMI > tristate "HDMI Synopsys Controller support for Amlogic Meson Display" > diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > index 1b9a1d9ed3d3..45104ef35344 100644 > --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > @@ -16,8 +16,6 @@ > #include > #include > > -#include > - > #include > #include > #include > @@ -41,7 +39,6 @@ struct meson_encoder_hdmi { > struct drm_connector *connector; > struct meson_drm *priv; > unsigned long output_bus_fmt; > - struct cec_notifier *cec_notifier; > }; > > #define bridge_to_meson_encoder_hdmi(x) \ > @@ -57,14 +54,6 @@ static int meson_encoder_hdmi_attach(struct drm_bridge *bridge, > &encoder_hdmi->bridge, flags); > } > > -static void meson_encoder_hdmi_detach(struct drm_bridge *bridge) > -{ > - struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); > - > - cec_notifier_conn_unregister(encoder_hdmi->cec_notifier); > - encoder_hdmi->cec_notifier = NULL; > -} > - > static void meson_encoder_hdmi_set_vclk(struct meson_encoder_hdmi *encoder_hdmi, > const struct drm_display_mode *mode) > { > @@ -321,27 +310,9 @@ static int meson_encoder_hdmi_atomic_check(struct drm_bridge *bridge, > return 0; > } > > -static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge, > - struct drm_connector *connector, > - enum drm_connector_status status) > -{ > - struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); > - > - if (!encoder_hdmi->cec_notifier) > - return; > - > - if (status == connector_status_connected) > - cec_notifier_set_phys_addr(encoder_hdmi->cec_notifier, > - connector->display_info.source_physical_address); > - else > - cec_notifier_phys_addr_invalidate(encoder_hdmi->cec_notifier); > -} > - > static const struct drm_bridge_funcs meson_encoder_hdmi_bridge_funcs = { > .attach = meson_encoder_hdmi_attach, > - .detach = meson_encoder_hdmi_detach, > .mode_valid = meson_encoder_hdmi_mode_valid, > - .hpd_notify = meson_encoder_hdmi_hpd_notify, > .atomic_enable = meson_encoder_hdmi_atomic_enable, > .atomic_disable = meson_encoder_hdmi_atomic_disable, > .atomic_get_input_bus_fmts = meson_encoder_hdmi_get_inp_bus_fmts, > @@ -374,9 +345,9 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) > > meson_encoder_hdmi->bridge.next_bridge = of_drm_find_and_get_bridge(remote); > if (!meson_encoder_hdmi->bridge.next_bridge) { > - ret = dev_err_probe(priv->dev, -EPROBE_DEFER, > - "Failed to find HDMI transceiver bridge\n"); > - goto err_put_node; > + of_node_put(remote); > + return dev_err_probe(priv->dev, -EPROBE_DEFER, > + "Failed to find HDMI transceiver bridge\n"); > } > > /* HDMI Encoder Bridge */ > @@ -384,6 +355,13 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) > meson_encoder_hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA; > meson_encoder_hdmi->bridge.interlace_allowed = true; > > + pdev = of_find_device_by_node(remote); > + of_node_put(remote); > + if (pdev) { > + meson_encoder_hdmi->bridge.ops |= DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER; > + meson_encoder_hdmi->bridge.hdmi_cec_dev = &pdev->dev; > + } > + > drm_bridge_add(&meson_encoder_hdmi->bridge); > > meson_encoder_hdmi->priv = priv; > @@ -391,30 +369,24 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) > /* Encoder */ > ret = drm_simple_encoder_init(priv->drm, &meson_encoder_hdmi->encoder, > DRM_MODE_ENCODER_TMDS); > - if (ret) { > - dev_err_probe(priv->dev, ret, "Failed to init HDMI encoder\n"); > - goto err_put_node; > - } > + if (ret) > + return dev_err_probe(priv->dev, ret, "Failed to init HDMI encoder\n"); > > meson_encoder_hdmi->encoder.possible_crtcs = BIT(0); > > /* Attach HDMI Encoder Bridge to Encoder */ > ret = drm_bridge_attach(&meson_encoder_hdmi->encoder, &meson_encoder_hdmi->bridge, NULL, > DRM_BRIDGE_ATTACH_NO_CONNECTOR); > - if (ret) { > - dev_err_probe(priv->dev, ret, "Failed to attach bridge\n"); > - goto err_put_node; > - } > + if (ret) > + return dev_err_probe(priv->dev, ret, "Failed to attach bridge\n"); > > /* Initialize & attach Bridge Connector */ > meson_encoder_hdmi->connector = drm_bridge_connector_init(priv->drm, > &meson_encoder_hdmi->encoder); > - if (IS_ERR(meson_encoder_hdmi->connector)) { > - ret = dev_err_probe(priv->dev, > - PTR_ERR(meson_encoder_hdmi->connector), > - "Unable to create HDMI bridge connector\n"); > - goto err_put_node; > - } > + if (IS_ERR(meson_encoder_hdmi->connector)) > + return dev_err_probe(priv->dev, > + PTR_ERR(meson_encoder_hdmi->connector), > + "Unable to create HDMI bridge connector\n"); > > /* > * We should have now in place: > @@ -437,32 +409,11 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) > /* Handle this here until handled by drm_bridge_connector_init() */ > meson_encoder_hdmi->connector->ycbcr_420_allowed = true; > > - pdev = of_find_device_by_node(remote); > - of_node_put(remote); > - if (pdev) { > - struct cec_connector_info conn_info; > - struct cec_notifier *notifier; > - > - cec_fill_conn_info_from_drm(&conn_info, meson_encoder_hdmi->connector); > - > - notifier = cec_notifier_conn_register(&pdev->dev, NULL, &conn_info); > - if (!notifier) { > - put_device(&pdev->dev); > - return -ENOMEM; > - } > - > - meson_encoder_hdmi->cec_notifier = notifier; > - } > - > priv->encoders[MESON_ENC_HDMI] = meson_encoder_hdmi; > > dev_dbg(priv->dev, "HDMI encoder initialized\n"); > > return 0; > - > -err_put_node: > - of_node_put(remote); > - return ret; > } > > void meson_encoder_hdmi_remove(struct meson_drm *priv) From neil.armstrong at linaro.org Tue May 19 00:23:15 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Tue, 19 May 2026 09:23:15 +0200 Subject: [PATCH 01/13] drm/meson: dw-hdmi: Report connector status based on HPD bit In-Reply-To: <20260518194744.2483580-2-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> <20260518194744.2483580-2-jonas@kwiboo.se> Message-ID: <77cff8e2-11a6-4fd0-ac02-7468441f7476@linaro.org> On 5/18/26 21:47, Jonas Karlman wrote: > G12A added support for RX-SENSE, status for both HPD and RX-SENSE is > reported in the TOP_STAT0 register. > > Limit read_hpd() phy ops to only report connected status based on the > HPD status bit in TOP_STAT0, to help ensure that EDID can be read from > the sink in the connector detect() func. > > Fixes: 3b7c1237a72a ("drm/meson: Add G12A support for the DW-HDMI Glue") > Signed-off-by: Jonas Karlman > --- > drivers/gpu/drm/meson/meson_dw_hdmi.c | 6 ++++-- > drivers/gpu/drm/meson/meson_dw_hdmi.h | 3 +++ > 2 files changed, 7 insertions(+), 2 deletions(-) > > diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c > index 2a8756da569b..993f6d5d4b29 100644 > --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c > @@ -457,9 +457,11 @@ static enum drm_connector_status dw_hdmi_read_hpd(struct dw_hdmi *hdmi, > void *data) > { > struct meson_dw_hdmi *dw_hdmi = (struct meson_dw_hdmi *)data; > + unsigned int stat; > > - return !!dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_STAT0) ? > - connector_status_connected : connector_status_disconnected; > + stat = dw_hdmi->data->top_read(dw_hdmi, HDMITX_TOP_STAT0); > + return stat & HDMITX_TOP_STAT0_HPD ? connector_status_connected : > + connector_status_disconnected; > } > > static void dw_hdmi_setup_hpd(struct dw_hdmi *hdmi, > diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.h b/drivers/gpu/drm/meson/meson_dw_hdmi.h > index 08e1c14e4ea0..cb4616daf667 100644 > --- a/drivers/gpu/drm/meson/meson_dw_hdmi.h > +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.h > @@ -157,4 +157,7 @@ > */ > #define HDMITX_TOP_STAT0 (0x00E) > > +#define HDMITX_TOP_STAT0_HPD BIT(0) > +#define HDMITX_TOP_STAT0_RXSENSE BIT(1) > + > #endif /* __MESON_DW_HDMI_H */ Thanks, pretty sure the RXSENSE needs some setup in the PHY to work and Amlogic never enabled it, but this version is clearly better. Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Tue May 19 00:23:35 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Tue, 19 May 2026 09:23:35 +0200 Subject: [PATCH 02/13] drm/meson: dw-hdmi: Protect from possible NULL pointer dereference In-Reply-To: <20260518194744.2483580-3-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> <20260518194744.2483580-3-jonas@kwiboo.se> Message-ID: <6db86d54-b463-49bb-ae7f-35c694e624bc@linaro.org> On 5/18/26 21:47, Jonas Karlman wrote: > The IRQ handler can be called at any time after the call to > devm_request_threaded_irq() completes, even before dw_hdmi->bridge has > been assigned later in meson_dw_hdmi_bind(). > > Protect from a possible NULL pointer dereference in IRQ handler by only > calling drm_helper_hpd_irq_event() when bridge has been assigned. > > Fixes: e67f6037ae1b ("drm/meson: split out encoder from meson_dw_hdmi") > Signed-off-by: Jonas Karlman > --- > I only observed this NULL pointer dereference one time, without being > able to reliably re-create a similar timing scenario. I still think this > is an issue that possible could happen and likely should be fixed. > > Note that patches later in this series will fully replace this change. > --- > drivers/gpu/drm/meson/meson_dw_hdmi.c | 3 ++- > 1 file changed, 2 insertions(+), 1 deletion(-) > > diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c > index 993f6d5d4b29..eafe7daf6ff1 100644 > --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c > @@ -520,7 +520,8 @@ static irqreturn_t dw_hdmi_top_thread_irq(int irq, void *dev_id) > u32 stat = dw_hdmi->irq_stat; > > /* HPD Events */ > - if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL)) { > + if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL) && > + dw_hdmi->bridge) { > bool hpd_connected = false; > > if (stat & HDMITX_TOP_INTR_HPD_RISE) Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Tue May 19 00:24:39 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Tue, 19 May 2026 09:24:39 +0200 Subject: [PATCH 03/13] drm/meson: dw-hdmi: Call dw_hdmi_remove() consistently In-Reply-To: <20260518194744.2483580-4-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> <20260518194744.2483580-4-jonas@kwiboo.se> Message-ID: On 5/18/26 21:47, Jonas Karlman wrote: > dw-hdmi export two similar pair of functions to probe()/remove() and > bind()/unbind(), update to use dw_hdmi_remove() for consistency. > > dw_hdmi_probe() will drm_bridge_add() the dw-hdmi bridge, so it is > expected that of_drm_find_and_get_bridge() always return the dw-hdmi > bridge. Regardless, validate that the dw-hdmi can be found for > consistent error handling. > > Also always ensure the IRQ handler and bridge is released before > dw_hdmi_remove() is called. > > Signed-off-by: Jonas Karlman > --- > drivers/gpu/drm/meson/meson_dw_hdmi.c | 11 +++++++++-- > 1 file changed, 9 insertions(+), 2 deletions(-) > > diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c > index eafe7daf6ff1..9aafdc768f2b 100644 > --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c > @@ -775,10 +775,17 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, > platform_set_drvdata(pdev, meson_dw_hdmi); > > meson_dw_hdmi->hdmi = dw_hdmi_probe(pdev, &meson_dw_hdmi->dw_plat_data); > - if (IS_ERR(meson_dw_hdmi->hdmi)) > + if (IS_ERR(meson_dw_hdmi->hdmi)) { > + devm_free_irq(dev, irq, meson_dw_hdmi); > return PTR_ERR(meson_dw_hdmi->hdmi); > + } > > meson_dw_hdmi->bridge = of_drm_find_and_get_bridge(pdev->dev.of_node); > + if (!meson_dw_hdmi->bridge) { > + devm_free_irq(dev, irq, meson_dw_hdmi); > + dw_hdmi_remove(meson_dw_hdmi->hdmi); > + return -ENODEV; > + } > > DRM_DEBUG_DRIVER("HDMI controller initialized\n"); > > @@ -793,8 +800,8 @@ static void meson_dw_hdmi_unbind(struct device *dev, struct device *master, > int irq = platform_get_irq(pdev, 0); > > devm_free_irq(dev, irq, meson_dw_hdmi); > - dw_hdmi_unbind(meson_dw_hdmi->hdmi); > drm_bridge_put(meson_dw_hdmi->bridge); > + dw_hdmi_remove(meson_dw_hdmi->hdmi); > } > > static const struct component_ops meson_dw_hdmi_ops = { Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Tue May 19 00:25:55 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Tue, 19 May 2026 09:25:55 +0200 Subject: [PATCH 04/13] drm/meson: dw-hdmi: Drop call to drm_bridge_hpd_notify() In-Reply-To: <20260518194744.2483580-5-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> <20260518194744.2483580-5-jonas@kwiboo.se> Message-ID: <0a91bf03-c94f-4519-a69c-b7f5e505ec1d@linaro.org> On 5/18/26 21:47, Jonas Karlman wrote: > Calls to both drm_helper_hpd_irq_event() and drm_bridge_hpd_notify() in > the IRQ handler causes multiple hotplug uevents and modesets during an > HPD interrupt. > > Change to only call drm_helper_hpd_irq_event() in IRQ handler to ensure > only one hotplug uevent is triggered when the connection status or EDID > has changed. > > The bridge connectors detect() func help ensure that any hpd_notify() > func is called for all bridges in the chain. > > Signed-off-by: Jonas Karlman > --- > drivers/gpu/drm/meson/meson_dw_hdmi.c | 11 +---------- > 1 file changed, 1 insertion(+), 10 deletions(-) > > diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c > index 9aafdc768f2b..30099bf71aad 100644 > --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c > @@ -521,17 +521,8 @@ static irqreturn_t dw_hdmi_top_thread_irq(int irq, void *dev_id) > > /* HPD Events */ > if (stat & (HDMITX_TOP_INTR_HPD_RISE | HDMITX_TOP_INTR_HPD_FALL) && > - dw_hdmi->bridge) { > - bool hpd_connected = false; > - > - if (stat & HDMITX_TOP_INTR_HPD_RISE) > - hpd_connected = true; > - > + dw_hdmi->bridge) > drm_helper_hpd_irq_event(dw_hdmi->bridge->dev); > - drm_bridge_hpd_notify(dw_hdmi->bridge, > - hpd_connected ? connector_status_connected > - : connector_status_disconnected); > - } > > return IRQ_HANDLED; > } Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Tue May 19 00:27:41 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Tue, 19 May 2026 09:27:41 +0200 Subject: [PATCH 05/13] drm/meson: encoder_hdmi: Use CEC phys addr from display_info In-Reply-To: <20260518194744.2483580-6-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> <20260518194744.2483580-6-jonas@kwiboo.se> Message-ID: <21609bd9-7a94-41cc-bbe2-2478278b0497@linaro.org> On 5/18/26 21:47, Jonas Karlman wrote: > The dw-hdmi bridge detect() func now updates EDID for the connector. > Something that ensures that display_info.source_physical_address has an > updated CEC phys addr when the hpd_notify() func is called. > > Change to use display_info source_physical_address directly instead of > re-reading EDID to set the CEC phys addr at HPD interrupt. > > Signed-off-by: Jonas Karlman > --- > drivers/gpu/drm/meson/meson_encoder_hdmi.c | 26 ++++------------------ > 1 file changed, 4 insertions(+), 22 deletions(-) > > diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > index 55c0601df3c6..1b9a1d9ed3d3 100644 > --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > @@ -330,28 +330,10 @@ static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge, > if (!encoder_hdmi->cec_notifier) > return; > > - if (status == connector_status_connected) { > - const struct drm_edid *drm_edid; > - const struct edid *edid; > - > - drm_edid = drm_bridge_edid_read(encoder_hdmi->bridge.next_bridge, > - encoder_hdmi->connector); > - if (!drm_edid) > - return; > - > - /* > - * FIXME: The CEC physical address should be set using > - * cec_notifier_set_phys_addr(encoder_hdmi->cec_notifier, > - * connector->display_info.source_physical_address) from a path > - * that has read the EDID and called > - * drm_edid_connector_update(). > - */ > - edid = drm_edid_raw(drm_edid); > - > - cec_notifier_set_phys_addr_from_edid(encoder_hdmi->cec_notifier, edid); > - > - drm_edid_free(drm_edid); > - } else > + if (status == connector_status_connected) > + cec_notifier_set_phys_addr(encoder_hdmi->cec_notifier, > + connector->display_info.source_physical_address); > + else > cec_notifier_phys_addr_invalidate(encoder_hdmi->cec_notifier); > } > Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Tue May 19 00:30:30 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Tue, 19 May 2026 09:30:30 +0200 Subject: [PATCH 06/13] drm/meson: encoder_hdmi: Use bridge connector CEC notifier In-Reply-To: <20260518194744.2483580-7-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> <20260518194744.2483580-7-jonas@kwiboo.se> Message-ID: <78f3211e-cdb8-4f76-a311-d4d66fc68d3d@linaro.org> On 5/18/26 21:47, Jonas Karlman wrote: > The dw-hdmi bridge detect() func now updates EDID and CEC phys addr for > the connector and any registered generic CEC notifier. > > Replace the open-coded CEC notifier handling with use of the bridge CEC > notifier op. > > Signed-off-by: Jonas Karlman > --- > drivers/gpu/drm/meson/Kconfig | 1 + > drivers/gpu/drm/meson/meson_encoder_hdmi.c | 85 +++++----------------- > 2 files changed, 19 insertions(+), 67 deletions(-) > > diff --git a/drivers/gpu/drm/meson/Kconfig b/drivers/gpu/drm/meson/Kconfig > index 417f79829cf8..0dc5ca21e4fc 100644 > --- a/drivers/gpu/drm/meson/Kconfig > +++ b/drivers/gpu/drm/meson/Kconfig > @@ -13,6 +13,7 @@ config DRM_MESON > select REGMAP_MMIO > select MESON_CANVAS > select CEC_CORE if CEC_NOTIFIER > + select DRM_DISPLAY_HDMI_CEC_NOTIFIER_HELPER if CEC_NOTIFIER > > config DRM_MESON_DW_HDMI > tristate "HDMI Synopsys Controller support for Amlogic Meson Display" > diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > index 1b9a1d9ed3d3..45104ef35344 100644 > --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > @@ -16,8 +16,6 @@ > #include > #include > > -#include > - > #include > #include > #include > @@ -41,7 +39,6 @@ struct meson_encoder_hdmi { > struct drm_connector *connector; > struct meson_drm *priv; > unsigned long output_bus_fmt; > - struct cec_notifier *cec_notifier; > }; > > #define bridge_to_meson_encoder_hdmi(x) \ > @@ -57,14 +54,6 @@ static int meson_encoder_hdmi_attach(struct drm_bridge *bridge, > &encoder_hdmi->bridge, flags); > } > > -static void meson_encoder_hdmi_detach(struct drm_bridge *bridge) > -{ > - struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); > - > - cec_notifier_conn_unregister(encoder_hdmi->cec_notifier); > - encoder_hdmi->cec_notifier = NULL; > -} > - > static void meson_encoder_hdmi_set_vclk(struct meson_encoder_hdmi *encoder_hdmi, > const struct drm_display_mode *mode) > { > @@ -321,27 +310,9 @@ static int meson_encoder_hdmi_atomic_check(struct drm_bridge *bridge, > return 0; > } > > -static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge, > - struct drm_connector *connector, > - enum drm_connector_status status) > -{ > - struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); > - > - if (!encoder_hdmi->cec_notifier) > - return; > - > - if (status == connector_status_connected) > - cec_notifier_set_phys_addr(encoder_hdmi->cec_notifier, > - connector->display_info.source_physical_address); > - else > - cec_notifier_phys_addr_invalidate(encoder_hdmi->cec_notifier); > -} > - > static const struct drm_bridge_funcs meson_encoder_hdmi_bridge_funcs = { > .attach = meson_encoder_hdmi_attach, > - .detach = meson_encoder_hdmi_detach, > .mode_valid = meson_encoder_hdmi_mode_valid, > - .hpd_notify = meson_encoder_hdmi_hpd_notify, > .atomic_enable = meson_encoder_hdmi_atomic_enable, > .atomic_disable = meson_encoder_hdmi_atomic_disable, > .atomic_get_input_bus_fmts = meson_encoder_hdmi_get_inp_bus_fmts, > @@ -374,9 +345,9 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) > > meson_encoder_hdmi->bridge.next_bridge = of_drm_find_and_get_bridge(remote); > if (!meson_encoder_hdmi->bridge.next_bridge) { > - ret = dev_err_probe(priv->dev, -EPROBE_DEFER, > - "Failed to find HDMI transceiver bridge\n"); > - goto err_put_node; > + of_node_put(remote); > + return dev_err_probe(priv->dev, -EPROBE_DEFER, > + "Failed to find HDMI transceiver bridge\n"); > } > > /* HDMI Encoder Bridge */ > @@ -384,6 +355,13 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) > meson_encoder_hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA; > meson_encoder_hdmi->bridge.interlace_allowed = true; > > + pdev = of_find_device_by_node(remote); > + of_node_put(remote); > + if (pdev) { > + meson_encoder_hdmi->bridge.ops |= DRM_BRIDGE_OP_HDMI_CEC_NOTIFIER; > + meson_encoder_hdmi->bridge.hdmi_cec_dev = &pdev->dev; > + } > + > drm_bridge_add(&meson_encoder_hdmi->bridge); > > meson_encoder_hdmi->priv = priv; > @@ -391,30 +369,24 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) > /* Encoder */ > ret = drm_simple_encoder_init(priv->drm, &meson_encoder_hdmi->encoder, > DRM_MODE_ENCODER_TMDS); > - if (ret) { > - dev_err_probe(priv->dev, ret, "Failed to init HDMI encoder\n"); > - goto err_put_node; > - } > + if (ret) > + return dev_err_probe(priv->dev, ret, "Failed to init HDMI encoder\n"); > > meson_encoder_hdmi->encoder.possible_crtcs = BIT(0); > > /* Attach HDMI Encoder Bridge to Encoder */ > ret = drm_bridge_attach(&meson_encoder_hdmi->encoder, &meson_encoder_hdmi->bridge, NULL, > DRM_BRIDGE_ATTACH_NO_CONNECTOR); > - if (ret) { > - dev_err_probe(priv->dev, ret, "Failed to attach bridge\n"); > - goto err_put_node; > - } > + if (ret) > + return dev_err_probe(priv->dev, ret, "Failed to attach bridge\n"); > > /* Initialize & attach Bridge Connector */ > meson_encoder_hdmi->connector = drm_bridge_connector_init(priv->drm, > &meson_encoder_hdmi->encoder); > - if (IS_ERR(meson_encoder_hdmi->connector)) { > - ret = dev_err_probe(priv->dev, > - PTR_ERR(meson_encoder_hdmi->connector), > - "Unable to create HDMI bridge connector\n"); > - goto err_put_node; > - } > + if (IS_ERR(meson_encoder_hdmi->connector)) > + return dev_err_probe(priv->dev, > + PTR_ERR(meson_encoder_hdmi->connector), > + "Unable to create HDMI bridge connector\n"); > > /* > * We should have now in place: > @@ -437,32 +409,11 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) > /* Handle this here until handled by drm_bridge_connector_init() */ > meson_encoder_hdmi->connector->ycbcr_420_allowed = true; > > - pdev = of_find_device_by_node(remote); > - of_node_put(remote); > - if (pdev) { > - struct cec_connector_info conn_info; > - struct cec_notifier *notifier; > - > - cec_fill_conn_info_from_drm(&conn_info, meson_encoder_hdmi->connector); > - > - notifier = cec_notifier_conn_register(&pdev->dev, NULL, &conn_info); > - if (!notifier) { > - put_device(&pdev->dev); > - return -ENOMEM; > - } > - > - meson_encoder_hdmi->cec_notifier = notifier; > - } > - > priv->encoders[MESON_ENC_HDMI] = meson_encoder_hdmi; > > dev_dbg(priv->dev, "HDMI encoder initialized\n"); > > return 0; > - > -err_put_node: > - of_node_put(remote); > - return ret; > } > > void meson_encoder_hdmi_remove(struct meson_drm *priv) Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Tue May 19 00:31:39 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Tue, 19 May 2026 09:31:39 +0200 Subject: [PATCH 07/13] drm/meson: encoder_hdmi: Report ycbcr_420_allowed from encoder In-Reply-To: <20260518194744.2483580-8-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> <20260518194744.2483580-8-jonas@kwiboo.se> Message-ID: On 5/18/26 21:47, Jonas Karlman wrote: > The bridge connector report ycbcr_420_allowed support when all bridges > in the chain support ycbcr_420_allowed. > > Report ycbcr_420_allowed on the encoder bridge so that the bridge > connector automatically can report correct ycbcr_420_allowed support. > > Signed-off-by: Jonas Karlman > --- > drivers/gpu/drm/meson/meson_encoder_hdmi.c | 4 +--- > 1 file changed, 1 insertion(+), 3 deletions(-) > > diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > index 45104ef35344..484675cb8284 100644 > --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > @@ -354,6 +354,7 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) > meson_encoder_hdmi->bridge.of_node = priv->dev->of_node; > meson_encoder_hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA; > meson_encoder_hdmi->bridge.interlace_allowed = true; > + meson_encoder_hdmi->bridge.ycbcr_420_allowed = true; > > pdev = of_find_device_by_node(remote); > of_node_put(remote); > @@ -406,9 +407,6 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) > > drm_connector_attach_max_bpc_property(meson_encoder_hdmi->connector, 8, 8); > > - /* Handle this here until handled by drm_bridge_connector_init() */ > - meson_encoder_hdmi->connector->ycbcr_420_allowed = true; > - > priv->encoders[MESON_ENC_HDMI] = meson_encoder_hdmi; > > dev_dbg(priv->dev, "HDMI encoder initialized\n"); Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Tue May 19 00:33:08 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Tue, 19 May 2026 09:33:08 +0200 Subject: [PATCH 08/13] drm/meson: dw-hdmi: Use local dev variable consistently in bind() In-Reply-To: <20260518194744.2483580-9-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> <20260518194744.2483580-9-jonas@kwiboo.se> Message-ID: <4ed0c9c4-6a48-4c25-9caa-16914587e580@linaro.org> On 5/18/26 21:47, Jonas Karlman wrote: > Replace indirect struct device accesses via pdev->dev with the local dev > parameter already available in meson_dw_hdmi_bind(), for consistency and > readability. > > Signed-off-by: Jonas Karlman > --- > drivers/gpu/drm/meson/meson_dw_hdmi.c | 6 +++--- > 1 file changed, 3 insertions(+), 3 deletions(-) > > diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c > index 30099bf71aad..fcd2426af9fc 100644 > --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c > @@ -671,9 +671,9 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, > > DRM_DEBUG_DRIVER("\n"); > > - match = of_device_get_match_data(&pdev->dev); > + match = of_device_get_match_data(dev); > if (!match) { > - dev_err(&pdev->dev, "failed to get match data\n"); > + dev_err(dev, "failed to get match data\n"); > return -ENODEV; > } > > @@ -771,7 +771,7 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, > return PTR_ERR(meson_dw_hdmi->hdmi); > } > > - meson_dw_hdmi->bridge = of_drm_find_and_get_bridge(pdev->dev.of_node); > + meson_dw_hdmi->bridge = of_drm_find_and_get_bridge(dev->of_node); > if (!meson_dw_hdmi->bridge) { > devm_free_irq(dev, irq, meson_dw_hdmi); > dw_hdmi_remove(meson_dw_hdmi->hdmi); Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Tue May 19 00:33:22 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Tue, 19 May 2026 09:33:22 +0200 Subject: [PATCH 09/13] drm/meson: dw-hdmi: Use devm_clk_get_enabled() helper In-Reply-To: <20260518194744.2483580-10-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> <20260518194744.2483580-10-jonas@kwiboo.se> Message-ID: <59760925-a19d-460e-afc9-015dd210f193@linaro.org> On 5/18/26 21:47, Jonas Karlman wrote: > Change to use the devm_clk_get_enabled() helper instead of using an > open-coded variant. > > Signed-off-by: Jonas Karlman > --- > drivers/gpu/drm/meson/meson_dw_hdmi.c | 48 +++++++++------------------ > 1 file changed, 16 insertions(+), 32 deletions(-) > > diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c > index fcd2426af9fc..d0cf2042d41c 100644 > --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c > @@ -634,29 +634,6 @@ static void meson_dw_hdmi_init(struct meson_dw_hdmi *meson_dw_hdmi) > > } > > -static void meson_disable_clk(void *data) > -{ > - clk_disable_unprepare(data); > -} > - > -static int meson_enable_clk(struct device *dev, char *name) > -{ > - struct clk *clk; > - int ret; > - > - clk = devm_clk_get(dev, name); > - if (IS_ERR(clk)) { > - dev_err(dev, "Unable to get %s pclk\n", name); > - return PTR_ERR(clk); > - } > - > - ret = clk_prepare_enable(clk); > - if (!ret) > - ret = devm_add_action_or_reset(dev, meson_disable_clk, clk); > - > - return ret; > -} > - > static int meson_dw_hdmi_bind(struct device *dev, struct device *master, > void *data) > { > @@ -666,6 +643,7 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, > struct drm_device *drm = data; > struct meson_drm *priv = drm->dev_private; > struct dw_hdmi_plat_data *dw_plat_data; > + struct clk *clk; > int irq; > int ret; > > @@ -716,17 +694,23 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, > if (IS_ERR(meson_dw_hdmi->hdmitx)) > return PTR_ERR(meson_dw_hdmi->hdmitx); > > - ret = meson_enable_clk(dev, "isfr"); > - if (ret) > - return ret; > + clk = devm_clk_get_enabled(dev, "isfr"); > + if (IS_ERR(clk)) { > + dev_err(dev, "Unable to get isfr pclk\n"); > + return PTR_ERR(clk); > + } > > - ret = meson_enable_clk(dev, "iahb"); > - if (ret) > - return ret; > + clk = devm_clk_get_enabled(dev, "iahb"); > + if (IS_ERR(clk)) { > + dev_err(dev, "Unable to get iahb pclk\n"); > + return PTR_ERR(clk); > + } > > - ret = meson_enable_clk(dev, "venci"); > - if (ret) > - return ret; > + clk = devm_clk_get_enabled(dev, "venci"); > + if (IS_ERR(clk)) { > + dev_err(dev, "Unable to get venci pclk\n"); > + return PTR_ERR(clk); > + } > > dw_plat_data->regm = devm_regmap_init(dev, NULL, meson_dw_hdmi, > &meson_dw_hdmi_regmap_config); Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Tue May 19 00:34:33 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Tue, 19 May 2026 09:34:33 +0200 Subject: [PATCH 10/13] drm/meson: dw-hdmi: Use dev_err_probe() to report errors In-Reply-To: <20260518194744.2483580-11-jonas@kwiboo.se> References: <20260518194744.2483580-1-jonas@kwiboo.se> <20260518194744.2483580-11-jonas@kwiboo.se> Message-ID: On 5/18/26 21:47, Jonas Karlman wrote: > Change to use dev_err_probe() to report bind() errors consistently. > > Signed-off-by: Jonas Karlman > --- > drivers/gpu/drm/meson/meson_dw_hdmi.c | 65 +++++++++++++-------------- > 1 file changed, 30 insertions(+), 35 deletions(-) > > diff --git a/drivers/gpu/drm/meson/meson_dw_hdmi.c b/drivers/gpu/drm/meson/meson_dw_hdmi.c > index d0cf2042d41c..1dd59196ff7f 100644 > --- a/drivers/gpu/drm/meson/meson_dw_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_dw_hdmi.c > @@ -650,10 +650,9 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, > DRM_DEBUG_DRIVER("\n"); > > match = of_device_get_match_data(dev); > - if (!match) { > - dev_err(dev, "failed to get match data\n"); > - return -ENODEV; > - } > + if (!match) > + return dev_err_probe(dev, -ENODEV, > + "Failed to get match data\n"); > > meson_dw_hdmi = devm_kzalloc(dev, sizeof(*meson_dw_hdmi), > GFP_KERNEL); > @@ -667,50 +666,45 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, > > ret = devm_regulator_get_enable_optional(dev, "hdmi"); > if (ret < 0 && ret != -ENODEV) > - return ret; > + return dev_err_probe(dev, ret, > + "Failed to get/enable hdmi regulator\n"); > > meson_dw_hdmi->hdmitx_apb = devm_reset_control_get_exclusive(dev, > "hdmitx_apb"); > - if (IS_ERR(meson_dw_hdmi->hdmitx_apb)) { > - dev_err(dev, "Failed to get hdmitx_apb reset\n"); > - return PTR_ERR(meson_dw_hdmi->hdmitx_apb); > - } > + if (IS_ERR(meson_dw_hdmi->hdmitx_apb)) > + return dev_err_probe(dev, PTR_ERR(meson_dw_hdmi->hdmitx_apb), > + "Failed to get hdmitx_apb reset\n"); > > meson_dw_hdmi->hdmitx_ctrl = devm_reset_control_get_exclusive(dev, > "hdmitx"); > - if (IS_ERR(meson_dw_hdmi->hdmitx_ctrl)) { > - dev_err(dev, "Failed to get hdmitx reset\n"); > - return PTR_ERR(meson_dw_hdmi->hdmitx_ctrl); > - } > + if (IS_ERR(meson_dw_hdmi->hdmitx_ctrl)) > + return dev_err_probe(dev, PTR_ERR(meson_dw_hdmi->hdmitx_ctrl), > + "Failed to get hdmitx reset\n"); > > meson_dw_hdmi->hdmitx_phy = devm_reset_control_get_exclusive(dev, > "hdmitx_phy"); > - if (IS_ERR(meson_dw_hdmi->hdmitx_phy)) { > - dev_err(dev, "Failed to get hdmitx_phy reset\n"); > - return PTR_ERR(meson_dw_hdmi->hdmitx_phy); > - } > + if (IS_ERR(meson_dw_hdmi->hdmitx_phy)) > + return dev_err_probe(dev, PTR_ERR(meson_dw_hdmi->hdmitx_phy), > + "Failed to get hdmitx_phy reset\n"); > > meson_dw_hdmi->hdmitx = devm_platform_ioremap_resource(pdev, 0); > if (IS_ERR(meson_dw_hdmi->hdmitx)) > return PTR_ERR(meson_dw_hdmi->hdmitx); > > clk = devm_clk_get_enabled(dev, "isfr"); > - if (IS_ERR(clk)) { > - dev_err(dev, "Unable to get isfr pclk\n"); > - return PTR_ERR(clk); > - } > + if (IS_ERR(clk)) > + return dev_err_probe(dev, PTR_ERR(clk), > + "Failed to get isfr pclk\n"); > > clk = devm_clk_get_enabled(dev, "iahb"); > - if (IS_ERR(clk)) { > - dev_err(dev, "Unable to get iahb pclk\n"); > - return PTR_ERR(clk); > - } > + if (IS_ERR(clk)) > + return dev_err_probe(dev, PTR_ERR(clk), > + "Failed to get iahb pclk\n"); > > clk = devm_clk_get_enabled(dev, "venci"); > - if (IS_ERR(clk)) { > - dev_err(dev, "Unable to get venci pclk\n"); > - return PTR_ERR(clk); > - } > + if (IS_ERR(clk)) > + return dev_err_probe(dev, PTR_ERR(clk), > + "Failed to get venci pclk\n"); > > dw_plat_data->regm = devm_regmap_init(dev, NULL, meson_dw_hdmi, > &meson_dw_hdmi_regmap_config); > @@ -724,10 +718,9 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, > ret = devm_request_threaded_irq(dev, irq, dw_hdmi_top_irq, > dw_hdmi_top_thread_irq, IRQF_SHARED, > "dw_hdmi_top_irq", meson_dw_hdmi); > - if (ret) { > - dev_err(dev, "Failed to request hdmi top irq\n"); > - return ret; > - } > + if (ret) > + return dev_err_probe(dev, ret, > + "Failed to request hdmi top irq\n"); > > meson_dw_hdmi_init(meson_dw_hdmi); > > @@ -752,14 +745,16 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master, > meson_dw_hdmi->hdmi = dw_hdmi_probe(pdev, &meson_dw_hdmi->dw_plat_data); > if (IS_ERR(meson_dw_hdmi->hdmi)) { > devm_free_irq(dev, irq, meson_dw_hdmi); > - return PTR_ERR(meson_dw_hdmi->hdmi); > + return dev_err_probe(dev, PTR_ERR(meson_dw_hdmi->hdmi), > + "Failed to probe dw-hdmi bridge\n"); > } > > meson_dw_hdmi->bridge = of_drm_find_and_get_bridge(dev->of_node); > if (!meson_dw_hdmi->bridge) { > devm_free_irq(dev, irq, meson_dw_hdmi); > dw_hdmi_remove(meson_dw_hdmi->hdmi); > - return -ENODEV; > + return dev_err_probe(dev, -ENODEV, > + "Failed to find dw-hdmi bridge\n"); > } > > DRM_DEBUG_DRIVER("HDMI controller initialized\n"); Reviewed-by: Neil Armstrong Thanks, Neil From luca.ceresoli at bootlin.com Tue May 19 05:06:59 2026 From: luca.ceresoli at bootlin.com (Luca Ceresoli) Date: Tue, 19 May 2026 14:06:59 +0200 Subject: [PATCH v7 04/23] drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup In-Reply-To: <20260518180206.2480119-5-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-5-jonas@kwiboo.se> Message-ID: <177919241969.545972.18169905222569864569.b4-review@b4> On Mon, 18 May 2026 18:01:40 +0000, Jonas Karlman wrote: Hello Jonas, > > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index b7bfc0e9a6b2..9d795c550f8a 100644 > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > @@ -2600,10 +2609,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); I'm not fully following the code paths, but both the report and the fix make sense to me. Only I'd move the drm_bridge_get() before drm_connector_init_with_ddc(), to avoid a short window where no reference is held and the bridge might be destroyed before drm_bridge_get() is called. I'm not sure this can happen, but it's better to write the code in a way that clearly makes it impossible. Otherwise looks good. Luca -- Luca Ceresoli, Bootlin Embedded Linux and Kernel engineering https://bootlin.com From luca.ceresoli at bootlin.com Tue May 19 05:06:59 2026 From: luca.ceresoli at bootlin.com (Luca Ceresoli) Date: Tue, 19 May 2026 14:06:59 +0200 Subject: [PATCH v7 09/23] drm: bridge: dw_hdmi: Unregister CEC notifier during connector cleanup In-Reply-To: <20260518180206.2480119-10-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-10-jonas@kwiboo.se> Message-ID: <177919241969.545972.1258246873508613782.b4-review@b4> On Mon, 18 May 2026 18:01:45 +0000, Jonas Karlman wrote: > 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. > > [...] Reviewed-by: Luca Ceresoli -- Luca Ceresoli, Bootlin Embedded Linux and Kernel engineering https://bootlin.com From linux.amoon at gmail.com Tue May 19 05:51:58 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Tue, 19 May 2026 18:21:58 +0530 Subject: [PATCH v2] media: meson: vdec: Fix memory leak in error path of vdec_open In-Reply-To: <49260ed9ce09b0684ed72787b5635e2c26059297.camel@ndufresne.ca> References: <20260321065408.209723-1-linux.amoon@gmail.com> <49260ed9ce09b0684ed72787b5635e2c26059297.camel@ndufresne.ca> Message-ID: Hi Nicolas. Thanks for your review comments On Fri, 8 May 2026 at 23:28, Nicolas Dufresne wrote: > > Hi, > > sorry I missed your patch, catching up now. > > > Le samedi 21 mars 2026 ? 12:24 +0530, Anand Moon a ?crit : > > The vdec_open and vdec_close functions in the Meson VDEC driver failed > > to release several resources, leading to memory leaks and potential > > use-after-free scenarios. > > > > This patch addresses: > > - Missing v4l2_ctrl_handler_free() in both the close path and error > > exit of the open path, preventing control memory leaks. > > - A leak of the M2M context if vdec_init_ctrls() failed. > > > > The error labels in vdec_open() have been reordered to ensure a proper > > Last-In-First-Out (LIFO) teardown of all initialized resources. > > > > This was identified via kmemleak: > > unreferenced object 0xffff0000205d6878 (size 8): > > comm "v4l_id", pid 5289, jiffies 4294938580 > > hex dump (first 8 bytes): > > 40 d2 49 18 00 00 ff ff @.I..... > > backtrace (crc d3204599): > > kmemleak_alloc+0xc8/0xf0 > > __kvmalloc_node_noprof+0x60c/0x850 > > v4l2_ctrl_handler_init_class+0x1b4/0x2e8 [videodev] > > vdec_open+0x1f4/0x788 [meson_vdec] > > v4l2_open+0x144/0x460 [videodev] > > chrdev_open+0x1ac/0x500 > > do_dentry_open+0x3f0/0xfe8 > > vfs_open+0x68/0x320 > > do_open+0x2d8/0x9a8 > > path_openat+0x1d0/0x4f0 > > do_filp_open+0x190/0x380 > > do_sys_openat2+0xf8/0x1b0 > > __arm64_sys_openat+0x13c/0x1e8 > > invoke_syscall+0xdc/0x268 > > el0_svc_common.constprop.0+0x178/0x258 > > do_el0_svc+0x4c/0x70 > > > > Cc: Nicolas Dufresne > > Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") > > Signed-off-by: Anand Moon > > --- > > v1: https://lore.kernel.org/all/20260304100557.126488-1-linux.amoon at gmail.com/ > > tried to address the issue reported by Nicolas > > improve the commit message. > > --- > > drivers/staging/media/meson/vdec/vdec.c | 9 ++++++--- > > 1 file changed, 6 insertions(+), 3 deletions(-) > > > > diff --git a/drivers/staging/media/meson/vdec/vdec.c > > b/drivers/staging/media/meson/vdec/vdec.c > > index 4b77ec1af5a76..3a5e4ebe0b34c 100644 > > --- a/drivers/staging/media/meson/vdec/vdec.c > > +++ b/drivers/staging/media/meson/vdec/vdec.c > > @@ -877,7 +877,7 @@ static int vdec_open(struct file *file) > > if (IS_ERR(sess->m2m_dev)) { > > dev_err(dev, "Fail to v4l2_m2m_init\n"); > > ret = PTR_ERR(sess->m2m_dev); > > - goto err_free_sess; > > + goto err_m2m_release; > > If m2m_dev creation failed, why do you want to call v4l2_m2m_release() ? > I don?t recall the exact details, but the current handling appears incorrect. I?ve prepared the following fix to resolve the issue, based on sashiko?s suggestion. [1] https://sashiko.dev/#/patchset/20260321065408.209723-1-linux.amoon%40gmail.com -----8<----------8<-------- $ git diff drivers/staging/media/meson/vdec/vdec.c diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 4b77ec1af5a7..a039d925c0fe 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -889,7 +889,7 @@ static int vdec_open(struct file *file) ret = vdec_init_ctrls(sess); if (ret) - goto err_m2m_release; + goto err_m2m_ctx_release; sess->pixfmt_cap = formats[0].pixfmts_cap[0]; sess->fmt_out = &formats[0]; @@ -913,6 +913,8 @@ static int vdec_open(struct file *file) return 0; +err_m2m_ctx_release: + v4l2_m2m_ctx_release(sess->m2m_ctx); err_m2m_release: v4l2_m2m_release(sess->m2m_dev); err_free_sess: -----8<----------8<-------- Thanks -Anand From mani at kernel.org Tue May 19 06:57:53 2026 From: mani at kernel.org (Manivannan Sadhasivam) Date: Tue, 19 May 2026 19:27:53 +0530 Subject: [PATCH 0/3] PCI: dwc: Cache PCIe capability offset and simplify drivers In-Reply-To: <20260509135152.2241235-1-18255117159@163.com> References: <20260509135152.2241235-1-18255117159@163.com> Message-ID: On Sat, May 09, 2026 at 09:51:49PM +0800, Hans Zhang wrote: > The DWC PCIe core and its many platform drivers repeatedly call > dw_pcie_find_capability(pci, PCI_CAP_ID_EXP) to obtain the offset of the > PCI Express Capability structure. This is wasteful and makes the code > verbose. And some even search for the PCI_CAP_ID_EXP offset value within > the suspend/resume functions. > Sashiko has flagged some real issues with this series in accessing DBI space very early and 'pci->pcie_cap' being 0. Those needs to be fixed. - Mani > Add a cached pcie_cap field in struct dw_pcie and a helper > dw_pcie_get_pcie_cap() to initialize it once at probe time. Then replace > all explicit capability searches with the cached value across the > entire dwc subtree. > > Hans Zhang (3): > PCI: dwc: Add pcie_cap field and helper in designware header > PCI: dwc: Use cached PCIe capability offset in core > PCI: dwc: Simplify platform drivers using cached capability offset > > drivers/pci/controller/dwc/pci-imx6.c | 6 +-- > .../pci/controller/dwc/pci-layerscape-ep.c | 4 +- > drivers/pci/controller/dwc/pci-meson.c | 4 +- > .../pci/controller/dwc/pcie-designware-ep.c | 4 +- > .../pci/controller/dwc/pcie-designware-host.c | 4 +- > drivers/pci/controller/dwc/pcie-designware.c | 16 +++--- > drivers/pci/controller/dwc/pcie-designware.h | 17 +++++++ > drivers/pci/controller/dwc/pcie-dw-rockchip.c | 15 +++--- > drivers/pci/controller/dwc/pcie-eswin.c | 3 +- > drivers/pci/controller/dwc/pcie-fu740.c | 2 +- > drivers/pci/controller/dwc/pcie-intel-gw.c | 2 +- > drivers/pci/controller/dwc/pcie-qcom-ep.c | 11 ++-- > drivers/pci/controller/dwc/pcie-qcom.c | 24 ++++----- > drivers/pci/controller/dwc/pcie-sophgo.c | 8 ++- > drivers/pci/controller/dwc/pcie-spacemit-k1.c | 5 +- > drivers/pci/controller/dwc/pcie-spear13xx.c | 6 +-- > drivers/pci/controller/dwc/pcie-tegra194.c | 51 +++++++------------ > 17 files changed, 85 insertions(+), 97 deletions(-) > > > base-commit: 70390501d1944d4e5b8f7352be180fceb3a44132 > -- > 2.34.1 > -- ????????? ???????? From jonas at kwiboo.se Tue May 19 08:18:40 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Tue, 19 May 2026 17:18:40 +0200 Subject: [PATCH v7 04/23] drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup In-Reply-To: <177919241969.545972.18169905222569864569.b4-review@b4> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-5-jonas@kwiboo.se> <177919241969.545972.18169905222569864569.b4-review@b4> Message-ID: Hello Luca, On 5/19/2026 2:06 PM, Luca Ceresoli wrote: > On Mon, 18 May 2026 18:01:40 +0000, Jonas Karlman wrote: > > Hello Jonas, > >> >> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >> index b7bfc0e9a6b2..9d795c550f8a 100644 >> --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >> +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >> @@ -2600,10 +2609,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); > > I'm not fully following the code paths, but both the report and the fix > make sense to me. Only I'd move the drm_bridge_get() before > drm_connector_init_with_ddc(), to avoid a short window where no reference > is held and the bridge might be destroyed before drm_bridge_get() is > called. I'm not sure this can happen, but it's better to write the code in > a way that clearly makes it impossible. dw_hdmi_connector_create() is only called from dw_hdmi_bridge_attach() so the bridge should already have a ref for the lifetime of this call. I explicitly chose the placement after drm_connector_init_with_ddc() to ensure ref count is correctly balanced without having to add a drm_bridge_put() call in any error path. I.e. connector destroy() is only called when drm_connector_init_with_ddc() succeeds. This code/call is also planned to be removed in a future series, so I do not think moving drm_bridge_get() is necessary unless you think we need to protect against possible bad behavior from DRM core? Regards, Jonas > > Otherwise looks good. > > Luca > From 18255117159 at 163.com Tue May 19 09:09:28 2026 From: 18255117159 at 163.com (Hans Zhang) Date: Wed, 20 May 2026 00:09:28 +0800 Subject: [PATCH 0/3] PCI: dwc: Cache PCIe capability offset and simplify drivers In-Reply-To: References: <20260509135152.2241235-1-18255117159@163.com> Message-ID: <5cc6fbcc-98eb-4da5-b123-2c04c4d39326@163.com> On 5/19/26 21:57, Manivannan Sadhasivam wrote: > On Sat, May 09, 2026 at 09:51:49PM +0800, Hans Zhang wrote: >> The DWC PCIe core and its many platform drivers repeatedly call >> dw_pcie_find_capability(pci, PCI_CAP_ID_EXP) to obtain the offset of the >> PCI Express Capability structure. This is wasteful and makes the code >> verbose. And some even search for the PCI_CAP_ID_EXP offset value within >> the suspend/resume functions. >> > > Sashiko has flagged some real issues with this series in accessing DBI space > very early and 'pci->pcie_cap' being 0. Hi Mani, We have discussed this issue in the Cadence driver. I think it won't cause any problems. Specifically as follows: https://lore.kernel.org/linux-pci/5823faec-d972-4c77-90ec-a215c686e0a8 at 163.com/ """ As per PCIe r7.0, sec 7.5.1.1.11, Since all PCI Express Functions are required to implement the PCI Express Capability structure, which must be included somewhere in this linked list. """ Bjorn also responded as follows: https://lore.kernel.org/linux-pci/20260505212306.GA744158 at bhelgaas/ """ It's true that all Root Ports must have a PCIe Capability. """ Mani, should I continue to make the judgment that 'pci->pcie_cap' might be 0 as per your instructions? Best regards, Hans > > Those needs to be fixed. > > - Mani > >> Add a cached pcie_cap field in struct dw_pcie and a helper >> dw_pcie_get_pcie_cap() to initialize it once at probe time. Then replace >> all explicit capability searches with the cached value across the >> entire dwc subtree. >> >> Hans Zhang (3): >> PCI: dwc: Add pcie_cap field and helper in designware header >> PCI: dwc: Use cached PCIe capability offset in core >> PCI: dwc: Simplify platform drivers using cached capability offset >> >> drivers/pci/controller/dwc/pci-imx6.c | 6 +-- >> .../pci/controller/dwc/pci-layerscape-ep.c | 4 +- >> drivers/pci/controller/dwc/pci-meson.c | 4 +- >> .../pci/controller/dwc/pcie-designware-ep.c | 4 +- >> .../pci/controller/dwc/pcie-designware-host.c | 4 +- >> drivers/pci/controller/dwc/pcie-designware.c | 16 +++--- >> drivers/pci/controller/dwc/pcie-designware.h | 17 +++++++ >> drivers/pci/controller/dwc/pcie-dw-rockchip.c | 15 +++--- >> drivers/pci/controller/dwc/pcie-eswin.c | 3 +- >> drivers/pci/controller/dwc/pcie-fu740.c | 2 +- >> drivers/pci/controller/dwc/pcie-intel-gw.c | 2 +- >> drivers/pci/controller/dwc/pcie-qcom-ep.c | 11 ++-- >> drivers/pci/controller/dwc/pcie-qcom.c | 24 ++++----- >> drivers/pci/controller/dwc/pcie-sophgo.c | 8 ++- >> drivers/pci/controller/dwc/pcie-spacemit-k1.c | 5 +- >> drivers/pci/controller/dwc/pcie-spear13xx.c | 6 +-- >> drivers/pci/controller/dwc/pcie-tegra194.c | 51 +++++++------------ >> 17 files changed, 85 insertions(+), 97 deletions(-) >> >> >> base-commit: 70390501d1944d4e5b8f7352be180fceb3a44132 >> -- >> 2.34.1 >> > From mani at kernel.org Tue May 19 09:15:51 2026 From: mani at kernel.org (Manivannan Sadhasivam) Date: Tue, 19 May 2026 21:45:51 +0530 Subject: [PATCH 0/3] PCI: dwc: Cache PCIe capability offset and simplify drivers In-Reply-To: <5cc6fbcc-98eb-4da5-b123-2c04c4d39326@163.com> References: <20260509135152.2241235-1-18255117159@163.com> <5cc6fbcc-98eb-4da5-b123-2c04c4d39326@163.com> Message-ID: On Wed, May 20, 2026 at 12:09:28AM +0800, Hans Zhang wrote: > > > On 5/19/26 21:57, Manivannan Sadhasivam wrote: > > On Sat, May 09, 2026 at 09:51:49PM +0800, Hans Zhang wrote: > > > The DWC PCIe core and its many platform drivers repeatedly call > > > dw_pcie_find_capability(pci, PCI_CAP_ID_EXP) to obtain the offset of the > > > PCI Express Capability structure. This is wasteful and makes the code > > > verbose. And some even search for the PCI_CAP_ID_EXP offset value within > > > the suspend/resume functions. > > > > > > > Sashiko has flagged some real issues with this series in accessing DBI space > > very early and 'pci->pcie_cap' being 0. > > > Hi Mani, > > We have discussed this issue in the Cadence driver. I think it won't cause > any problems. Specifically as follows: > > https://lore.kernel.org/linux-pci/5823faec-d972-4c77-90ec-a215c686e0a8 at 163.com/ > """ > As per PCIe r7.0, sec 7.5.1.1.11, Since all PCI Express Functions are > required to implement the PCI Express Capability structure, which > must be included somewhere in this linked list. > """ > > > > Bjorn also responded as follows: > https://lore.kernel.org/linux-pci/20260505212306.GA744158 at bhelgaas/ > """ > It's true that all Root Ports must have a PCIe Capability. > """ > Ok, what about reading the DBI registers very early? - Mani -- ????????? ???????? From 18255117159 at 163.com Tue May 19 09:27:21 2026 From: 18255117159 at 163.com (Hans Zhang) Date: Wed, 20 May 2026 00:27:21 +0800 Subject: [PATCH 0/3] PCI: dwc: Cache PCIe capability offset and simplify drivers In-Reply-To: References: <20260509135152.2241235-1-18255117159@163.com> <5cc6fbcc-98eb-4da5-b123-2c04c4d39326@163.com> Message-ID: <3464ded9-721a-4eb2-afb6-bbca6fdc8a46@163.com> On 5/20/26 00:15, Manivannan Sadhasivam wrote: > On Wed, May 20, 2026 at 12:09:28AM +0800, Hans Zhang wrote: >> >> >> On 5/19/26 21:57, Manivannan Sadhasivam wrote: >>> On Sat, May 09, 2026 at 09:51:49PM +0800, Hans Zhang wrote: >>>> The DWC PCIe core and its many platform drivers repeatedly call >>>> dw_pcie_find_capability(pci, PCI_CAP_ID_EXP) to obtain the offset of the >>>> PCI Express Capability structure. This is wasteful and makes the code >>>> verbose. And some even search for the PCI_CAP_ID_EXP offset value within >>>> the suspend/resume functions. >>>> >>> >>> Sashiko has flagged some real issues with this series in accessing DBI space >>> very early and 'pci->pcie_cap' being 0. >> >> >> Hi Mani, >> >> We have discussed this issue in the Cadence driver. I think it won't cause >> any problems. Specifically as follows: >> >> https://lore.kernel.org/linux-pci/5823faec-d972-4c77-90ec-a215c686e0a8 at 163.com/ >> """ >> As per PCIe r7.0, sec 7.5.1.1.11, Since all PCI Express Functions are >> required to implement the PCI Express Capability structure, which >> must be included somewhere in this linked list. >> """ >> >> >> >> Bjorn also responded as follows: >> https://lore.kernel.org/linux-pci/20260505212306.GA744158 at bhelgaas/ >> """ >> It's true that all Root Ports must have a PCIe Capability. >> """ >> > > Ok, what about reading the DBI registers very early? Hi Mani, Yes. I have performed the DBI read register operation at the very beginning of the following code. dw_pcie_ep_init() dw_pcie_get_pcie_cap(pci); dw_pcie_host_init dw_pcie_get_pcie_cap(pci); However, for some glue drivers, they need to configure the registers of the PCIe Express Capability earlier than calling dw_pcie_host_init()/dw_pcie_ep_init(). So, for example, in the file: drivers/pci/controller/dwc/pcie-tegra194.c. Here, it is necessary to find the value of 'pci->pcie_cap' earlier. Then, dw_pcie_host_init()/dw_pcie_ep_init() will no longer search for the offset value of the PCIe Express Capability. Best regards, Hans From mani at kernel.org Tue May 19 09:49:33 2026 From: mani at kernel.org (Manivannan Sadhasivam) Date: Tue, 19 May 2026 22:19:33 +0530 Subject: [PATCH 0/3] PCI: dwc: Cache PCIe capability offset and simplify drivers In-Reply-To: <3464ded9-721a-4eb2-afb6-bbca6fdc8a46@163.com> References: <20260509135152.2241235-1-18255117159@163.com> <5cc6fbcc-98eb-4da5-b123-2c04c4d39326@163.com> <3464ded9-721a-4eb2-afb6-bbca6fdc8a46@163.com> Message-ID: On Wed, May 20, 2026 at 12:27:21AM +0800, Hans Zhang wrote: > > > On 5/20/26 00:15, Manivannan Sadhasivam wrote: > > On Wed, May 20, 2026 at 12:09:28AM +0800, Hans Zhang wrote: > > > > > > > > > On 5/19/26 21:57, Manivannan Sadhasivam wrote: > > > > On Sat, May 09, 2026 at 09:51:49PM +0800, Hans Zhang wrote: > > > > > The DWC PCIe core and its many platform drivers repeatedly call > > > > > dw_pcie_find_capability(pci, PCI_CAP_ID_EXP) to obtain the offset of the > > > > > PCI Express Capability structure. This is wasteful and makes the code > > > > > verbose. And some even search for the PCI_CAP_ID_EXP offset value within > > > > > the suspend/resume functions. > > > > > > > > > > > > > Sashiko has flagged some real issues with this series in accessing DBI space > > > > very early and 'pci->pcie_cap' being 0. > > > > > > > > > Hi Mani, > > > > > > We have discussed this issue in the Cadence driver. I think it won't cause > > > any problems. Specifically as follows: > > > > > > https://lore.kernel.org/linux-pci/5823faec-d972-4c77-90ec-a215c686e0a8 at 163.com/ > > > """ > > > As per PCIe r7.0, sec 7.5.1.1.11, Since all PCI Express Functions are > > > required to implement the PCI Express Capability structure, which > > > must be included somewhere in this linked list. > > > """ > > > > > > > > > > > > Bjorn also responded as follows: > > > https://lore.kernel.org/linux-pci/20260505212306.GA744158 at bhelgaas/ > > > """ > > > It's true that all Root Ports must have a PCIe Capability. > > > """ > > > > > > > Ok, what about reading the DBI registers very early? > > Hi Mani, > > Yes. I have performed the DBI read register operation at the very beginning > of the following code. > > > dw_pcie_ep_init() > dw_pcie_get_pcie_cap(pci); > > dw_pcie_host_init > dw_pcie_get_pcie_cap(pci); > These both calls will cause crash on a lot of platforms because these will be reading the DBI registers while the resources are not enabled. - Mani -- ????????? ???????? From 18255117159 at 163.com Tue May 19 09:52:49 2026 From: 18255117159 at 163.com (Hans Zhang) Date: Wed, 20 May 2026 00:52:49 +0800 Subject: [PATCH 0/3] PCI: dwc: Cache PCIe capability offset and simplify drivers In-Reply-To: References: <20260509135152.2241235-1-18255117159@163.com> <5cc6fbcc-98eb-4da5-b123-2c04c4d39326@163.com> <3464ded9-721a-4eb2-afb6-bbca6fdc8a46@163.com> Message-ID: On 5/20/26 00:49, Manivannan Sadhasivam wrote: > On Wed, May 20, 2026 at 12:27:21AM +0800, Hans Zhang wrote: >> >> >> On 5/20/26 00:15, Manivannan Sadhasivam wrote: >>> On Wed, May 20, 2026 at 12:09:28AM +0800, Hans Zhang wrote: >>>> >>>> >>>> On 5/19/26 21:57, Manivannan Sadhasivam wrote: >>>>> On Sat, May 09, 2026 at 09:51:49PM +0800, Hans Zhang wrote: >>>>>> The DWC PCIe core and its many platform drivers repeatedly call >>>>>> dw_pcie_find_capability(pci, PCI_CAP_ID_EXP) to obtain the offset of the >>>>>> PCI Express Capability structure. This is wasteful and makes the code >>>>>> verbose. And some even search for the PCI_CAP_ID_EXP offset value within >>>>>> the suspend/resume functions. >>>>>> >>>>> >>>>> Sashiko has flagged some real issues with this series in accessing DBI space >>>>> very early and 'pci->pcie_cap' being 0. >>>> >>>> >>>> Hi Mani, >>>> >>>> We have discussed this issue in the Cadence driver. I think it won't cause >>>> any problems. Specifically as follows: >>>> >>>> https://lore.kernel.org/linux-pci/5823faec-d972-4c77-90ec-a215c686e0a8 at 163.com/ >>>> """ >>>> As per PCIe r7.0, sec 7.5.1.1.11, Since all PCI Express Functions are >>>> required to implement the PCI Express Capability structure, which >>>> must be included somewhere in this linked list. >>>> """ >>>> >>>> >>>> >>>> Bjorn also responded as follows: >>>> https://lore.kernel.org/linux-pci/20260505212306.GA744158 at bhelgaas/ >>>> """ >>>> It's true that all Root Ports must have a PCIe Capability. >>>> """ >>>> >>> >>> Ok, what about reading the DBI registers very early? >> >> Hi Mani, >> >> Yes. I have performed the DBI read register operation at the very beginning >> of the following code. >> >> >> dw_pcie_ep_init() >> dw_pcie_get_pcie_cap(pci); >> >> dw_pcie_host_init >> dw_pcie_get_pcie_cap(pci); >> > > These both calls will cause crash on a lot of platforms because these will be > reading the DBI registers while the resources are not enabled. Hi Mani, Next, I will check all the DBI register-related issues. It will be fixed in the next version. Thank you very much. Best regards, Hans From jian.hu at amlogic.com Tue May 19 20:16:49 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Wed, 20 May 2026 11:16:49 +0800 Subject: [PATCH 03/10] dt-bindings: clock: Add Amlogic A9 peripherals clock controller In-Reply-To: <1jbjei6k75.fsf@starbuckisacylon.baylibre.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-3-41cb4071b7c9@amlogic.com> <1jbjei6k75.fsf@starbuckisacylon.baylibre.com> Message-ID: <609d9fb6-13e3-4105-bbab-19744b73fd82@amlogic.com> Hi Jerome, Thanks for your review. On 5/15/2026 12:15 AM, Jerome Brunet wrote: > [ EXTERNAL EMAIL ] > > On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > >> From: Jian Hu >> >> Add the peripherals clock controller dt-bindings for the Amlogic A9 >> SoC family. >> >> Signed-off-by: Jian Hu >> --- >> .../clock/amlogic,a9-peripherals-clkc.yaml | 150 +++++++++ >> .../clock/amlogic,a9-peripherals-clkc.h | 352 +++++++++++++++++++++ >> 2 files changed, 502 insertions(+) >> >> diff --git >> a/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml >> b/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml >> new file mode 100644 >> index 000000000000..97e2c44d8630 >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml >> @@ -0,0 +1,150 @@ >> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) >> +# Copyright (C) 2026 Amlogic, Inc. All rights reserved >> +%YAML 1.2 >> +--- >> +$id: http://devicetree.org/schemas/clock/amlogic,a9-peripherals-clkc.yaml# >> +$schema: http://devicetree.org/meta-schemas/core.yaml# >> + >> +title: Amlogic A9 Series Peripherals Clock Controller >> + >> +maintainers: >> + - Neil Armstrong >> + - Jerome Brunet >> + - Jian Hu >> + - Xianwei Zhao >> + >> +properties: >> + compatible: >> + const: amlogic,a9-peripherals-clkc >> + >> + reg: >> + maxItems: 1 >> + >> + '#clock-cells': >> + const: 1 >> + >> + clocks: >> + minItems: 20 >> + items: >> + - description: input oscillator >> + - description: input fclk div 2 >> + - description: input fclk div 3 >> + - description: input fclk div 4 >> + - description: input fclk div 5 >> + - description: input fclk div 7 >> + - description: input fclk div 2p5 >> + - description: input sys clk >> + - description: input gp1 pll >> + - description: input gp2 pll >> + - description: input sys pll div 16 >> + - description: input cpu clk div 16 >> + - description: input a78 clk div 16 >> + - description: input dsu clk div 16 >> + - description: input rtc clk >> + - description: input gp0 pll >> + - description: input hifi0 pll >> + - description: input hifi1 pll >> + - description: input mclk0 pll >> + - description: input mclk1 pll >> + - description: input video1 pll (optional) >> + - description: input video2 pll (optional) >> + - description: input hdmi out2 clk (optional) >> + - description: input hdmi pixel clk (optional) >> + - description: input pixel0 pll (optional) >> + - description: input pixel1 pll (optional) >> + - description: input usb2 drd clk (optional) > Why are those optional ? they seem internal to the SoC. > If so, they don't have a reason to be optional Yes , these clocks are sourced from other analog modules and will be added in the future. I will remove the optional in the next version. >> + - description: external input rmii oscillator (optional) >> + [...] > -- > Jerome Best regards, Jian From jian.hu at amlogic.com Tue May 19 20:25:35 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Wed, 20 May 2026 11:25:35 +0800 Subject: [PATCH 05/10] clk: amlogic: PLL l_detect signal supports active-high configuration In-Reply-To: <1jse7u6n3q.fsf@starbuckisacylon.baylibre.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-5-41cb4071b7c9@amlogic.com> <1jse7u6n3q.fsf@starbuckisacylon.baylibre.com> Message-ID: On 5/14/2026 11:13 PM, Jerome Brunet wrote: > [ EXTERNAL EMAIL ] > > On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > >> From: Jian Hu >> >> l_detect controls the enable/disable of the PLL lock-detect module. >> >> For A9, the l_detect signal is active-high: >> 0 -> Disable lock-detect module; >> 1 -> Enable lock-detect module. >> >> Here, a flag CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH is added to handle cases >> like A9, where the signal is active-high. >> >> Signed-off-by: Jian Hu >> --- >> drivers/clk/meson/clk-pll.c | 9 +++++++-- >> drivers/clk/meson/clk-pll.h | 2 ++ >> 2 files changed, 9 insertions(+), 2 deletions(-) >> >> diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c >> index 1ea6579a760f..5a0bd75f85a9 100644 >> --- a/drivers/clk/meson/clk-pll.c >> +++ b/drivers/clk/meson/clk-pll.c >> @@ -388,8 +388,13 @@ static int meson_clk_pll_enable(struct clk_hw *hw) >> } >> >> if (MESON_PARM_APPLICABLE(&pll->l_detect)) { >> - meson_parm_write(clk->map, &pll->l_detect, 1); >> - meson_parm_write(clk->map, &pll->l_detect, 0); >> + if (pll->flags & CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH) { >> + meson_parm_write(clk->map, &pll->l_detect, 0); >> + meson_parm_write(clk->map, &pll->l_detect, 1); >> + } else { >> + meson_parm_write(clk->map, &pll->l_detect, 1); >> + meson_parm_write(clk->map, &pll->l_detect, 0); >> + } > I'm not a fan of this code duplication. > Use the introduced CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH to compute the > first value, then flip the bit. Ok, I will update this in the next version. Here is the updated code: ? ? ? ? if (MESON_PARM_APPLICABLE(&pll->l_detect)) { ? ? ? ? ? ? ? ? ? ? ? ? meson_parm_write(clk->map, &pll->l_detect, ? ? ? ? ? ? ? ? ? ? ? ? !(pll->flags & CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH)); ? ? ? ? ? ? ? ? ? ? ? ? meson_parm_write(clk->map, &pll->l_detect, ? ? ? ? ? ? ? ? ? ? ? ? !!(pll->flags & CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH)); ? ? ? ? } >> } >> >> if (meson_clk_pll_wait_lock(hw)) >> diff --git a/drivers/clk/meson/clk-pll.h b/drivers/clk/meson/clk-pll.h >> index 949157fb7bf5..97b7c70376a3 100644 >> --- a/drivers/clk/meson/clk-pll.h >> +++ b/drivers/clk/meson/clk-pll.h >> @@ -29,6 +29,8 @@ struct pll_mult_range { >> >> #define CLK_MESON_PLL_ROUND_CLOSEST BIT(0) >> #define CLK_MESON_PLL_NOINIT_ENABLED BIT(1) >> +/* l_detect signal is active-high */ >> +#define CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH BIT(2) >> >> struct meson_clk_pll_data { >> struct parm en; > -- > Jerome Best regards, Jian From jian.hu at amlogic.com Tue May 19 20:35:28 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Wed, 20 May 2026 11:35:28 +0800 Subject: [PATCH 06/10] clk: amlogic: PLL reset signal supports active-low configuration In-Reply-To: <1jmry26my3.fsf@starbuckisacylon.baylibre.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-6-41cb4071b7c9@amlogic.com> <1jmry26my3.fsf@starbuckisacylon.baylibre.com> Message-ID: <26738e81-97cf-406a-94e6-b4a02f0b9609@amlogic.com> On 5/14/2026 11:16 PM, Jerome Brunet wrote: > [ EXTERNAL EMAIL ] > > On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > >> From: Jian Hu >> >> In the A9 design, the PLL reset signal is configured as active-low. >> >> Add the flag 'CLK_MESON_PLL_RST_N' to indicate that the PLL reset signal >> is active-low. >> >> Signed-off-by: Jian Hu >> --- >> drivers/clk/meson/clk-pll.c | 42 +++++++++++++++++++++++++++++++----------- >> drivers/clk/meson/clk-pll.h | 2 ++ >> 2 files changed, 33 insertions(+), 11 deletions(-) >> >> diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c >> index 5a0bd75f85a9..8568ad6ba7b6 100644 >> --- a/drivers/clk/meson/clk-pll.c >> +++ b/drivers/clk/meson/clk-pll.c >> @@ -295,10 +295,14 @@ static int meson_clk_pll_is_enabled(struct clk_hw *hw) >> { >> struct clk_regmap *clk = to_clk_regmap(hw); >> struct meson_clk_pll_data *pll = meson_clk_pll_data(clk); >> + unsigned int rst; >> >> - if (MESON_PARM_APPLICABLE(&pll->rst) && >> - meson_parm_read(clk->map, &pll->rst)) >> - return 0; >> + if (MESON_PARM_APPLICABLE(&pll->rst)) { >> + rst = meson_parm_read(clk->map, &pll->rst); >> + if ((rst && !(pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW)) || >> + (!rst && (pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW))) > Again not a great usage of binary ops. What you've written above is the > verbose version of a XOR. > > The code duplication remarks applies to the rest of the patch too Ok, I will update this and the other similar instances below in the next version. Here is the updated code for it: ? ? int active_low = !!(pll->flags & CLK_MESON_PLL_RST_ACTIVE_LOW); ? ? if (MESON_PARM_APPLICABLE(&pll->rst) && ? ? ? ? ? ? ? ? (meson_parm_read(clk->map, &pll->rst) ^ active_low)) [...] Best regards, Jian From linux.amoon at gmail.com Tue May 19 21:40:41 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Wed, 20 May 2026 10:10:41 +0530 Subject: [PATCH v3] media: meson: vdec: Fix memory leak in error path of vdec_open Message-ID: <20260520044046.7553-1-linux.amoon@gmail.com> The vdec_open() function previously jumped directly to err_m2m_release when vdec_init_ctrls() failed, skipping release of the m2m context. This caused a resource leak. Fix it by introducing a proper err_m2m_ctx_release label that calls v4l2_m2m_ctx_release(sess->m2m_ctx) before releasing the m2m device. This was identified via kmemleak: unreferenced object 0xffff0000205d6878 (size 8): comm "v4l_id", pid 5289, jiffies 4294938580 hex dump (first 8 bytes): 40 d2 49 18 00 00 ff ff @.I..... backtrace (crc d3204599): kmemleak_alloc+0xc8/0xf0 __kvmalloc_node_noprof+0x60c/0x850 v4l2_ctrl_handler_init_class+0x1b4/0x2e8 [videodev] vdec_open+0x1f4/0x788 [meson_vdec] v4l2_open+0x144/0x460 [videodev] chrdev_open+0x1ac/0x500 do_dentry_open+0x3f0/0xfe8 vfs_open+0x68/0x320 do_open+0x2d8/0x9a8 path_openat+0x1d0/0x4f0 do_filp_open+0x190/0x380 do_sys_openat2+0xf8/0x1b0 __arm64_sys_openat+0x13c/0x1e8 invoke_syscall+0xdc/0x268 el0_svc_common.constprop.0+0x178/0x258 do_el0_svc+0x4c/0x70 Cc: Nicolas Dufresne Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- v2: https://lore.kernel.org/all/20260321065408.209723-1-linux.amoon at gmail.com/ updated the commit message, applied the suggestion from sashiko below. [3] https://sashiko.dev/#/patchset/20260321065408.209723-1-linux.amoon%40gmail.com v1: https://lore.kernel.org/all/20260304100557.126488-1-linux.amoon at gmail.com/ tried to address the issue reported by Nicolas improve the commit message. --- drivers/staging/media/meson/vdec/vdec.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 4b77ec1af5a7..a039d925c0fe 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -889,7 +889,7 @@ static int vdec_open(struct file *file) ret = vdec_init_ctrls(sess); if (ret) - goto err_m2m_release; + goto err_m2m_ctx_release; sess->pixfmt_cap = formats[0].pixfmts_cap[0]; sess->fmt_out = &formats[0]; @@ -913,6 +913,8 @@ static int vdec_open(struct file *file) return 0; +err_m2m_ctx_release: + v4l2_m2m_ctx_release(sess->m2m_ctx); err_m2m_release: v4l2_m2m_release(sess->m2m_dev); err_free_sess: base-commit: 27fa82620cbaa89a7fc11ac3057701d598813e87 -- 2.50.1 From sashiko-bot at kernel.org Tue May 19 21:59:05 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Wed, 20 May 2026 04:59:05 +0000 Subject: [PATCH v3] media: meson: vdec: Fix memory leak in error path of vdec_open In-Reply-To: <20260520044046.7553-1-linux.amoon@gmail.com> References: <20260520044046.7553-1-linux.amoon@gmail.com> Message-ID: <20260520045905.6ACBA1F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: Pre-existing issues: - [Critical] The `sess->esparser_queue_work` work item is not canceled before freeing the session context, leading to a potential Use-After-Free vulnerability. - [High] The patch attempts to fix a memory leak reported by kmemleak, but misdiagnoses the root cause and leaves the primary memory leak (the V4L2 control handler) unresolved. - [High] The driver does not verify if `kthread_run()` returns an `ERR_PTR`, leading to a kernel panic when `kthread_stop()` is called. -- commit acccc4fc02fe5e1be0c545d1722179667c078475 Author: Anand Moon media: meson: vdec: Fix memory leak in error path of vdec_open This commit introduces a proper err_m2m_ctx_release label in the vdec_open function. It ensures v4l2_m2m_ctx_release is called when vdec_init_ctrls fails, preventing a memory leak of the m2m context. > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index 4b77ec1af5a76..a039d925c0fe5 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c > @@ -889,7 +889,7 @@ static int vdec_open(struct file *file) > > ret = vdec_init_ctrls(sess); > if (ret) > - goto err_m2m_release; > + goto err_m2m_ctx_release; [Severity: High] This isn't a bug introduced by this patch, but does vdec_close() properly free the v4l2 control handler memory allocated by vdec_init_ctrls() here? While this patch fixes the m2m context leak on the error path, if vdec_close() fails to call v4l2_ctrl_handler_free(&sess->ctrl_handler), could this control handler memory still leak on every successful open and close cycle? [Severity: Critical] This is a pre-existing issue, but is the sess->esparser_queue_work item canceled before freeing the session context? The work item is initialized in vdec_open(): vdec_open() { ... INIT_WORK(&sess->esparser_queue_work, esparser_queue_all_src); ... } If vdec_close() calls kfree(sess) without first stopping or synchronizing with this background work via cancel_work_sync(), could a concurrently running esparser_queue_all_src() dereference the freed sess structure and trigger a use-after-free? [Severity: High] This isn't a bug introduced by this patch, but does the driver verify if kthread_run() returns an ERR_PTR when starting the recycle thread? If thread creation fails in vdec_start_streaming() and returns an ERR_PTR, could a later call to kthread_stop(sess->recycle_thread) in vdec_stop_streaming() attempt to dereference that ERR_PTR and cause a kernel panic? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260520044046.7553-1-linux.amoon at gmail.com?part=1 From linux.amoon at gmail.com Tue May 19 22:45:34 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Wed, 20 May 2026 11:15:34 +0530 Subject: [PATCH v3] media: meson: vdec: Fix memory leak in error path of vdec_open In-Reply-To: <20260520045905.6ACBA1F000E9@smtp.kernel.org> References: <20260520044046.7553-1-linux.amoon@gmail.com> <20260520045905.6ACBA1F000E9@smtp.kernel.org> Message-ID: Hi Sashiko, On Wed, 20 May 2026 at 10:29, wrote: > > Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: Thanks for your review comments > > Pre-existing issues: > - [Critical] The `sess->esparser_queue_work` work item is not canceled before freeing the session context, leading to a potential Use-After-Free vulnerability. > - [High] The patch attempts to fix a memory leak reported by kmemleak, but misdiagnoses the root cause and leaves the primary memory leak (the V4L2 control handler) unresolved. > - [High] The driver does not verify if `kthread_run()` returns an `ERR_PTR`, leading to a kernel panic when `kthread_stop()` is called. > -- > > commit acccc4fc02fe5e1be0c545d1722179667c078475 > Author: Anand Moon > > media: meson: vdec: Fix memory leak in error path of vdec_open > > This commit introduces a proper err_m2m_ctx_release label in the > vdec_open function. It ensures v4l2_m2m_ctx_release is called when > vdec_init_ctrls fails, preventing a memory leak of the m2m context. > > > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > > index 4b77ec1af5a76..a039d925c0fe5 100644 > > --- a/drivers/staging/media/meson/vdec/vdec.c > > +++ b/drivers/staging/media/meson/vdec/vdec.c > > @@ -889,7 +889,7 @@ static int vdec_open(struct file *file) > > > > ret = vdec_init_ctrls(sess); > > if (ret) > > - goto err_m2m_release; > > + goto err_m2m_ctx_release; > > [Severity: High] > This isn't a bug introduced by this patch, but does vdec_close() properly > free the v4l2 control handler memory allocated by vdec_init_ctrls() here? > > While this patch fixes the m2m context leak on the error path, if > vdec_close() fails to call v4l2_ctrl_handler_free(&sess->ctrl_handler), > could this control handler memory still leak on every successful open and > close cycle? > > [Severity: Critical] > This is a pre-existing issue, but is the sess->esparser_queue_work item > canceled before freeing the session context? > > The work item is initialized in vdec_open(): > > vdec_open() { > ... > INIT_WORK(&sess->esparser_queue_work, esparser_queue_all_src); > ... > } > > If vdec_close() calls kfree(sess) without first stopping or synchronizing > with this background work via cancel_work_sync(), could a concurrently > running esparser_queue_all_src() dereference the freed sess structure and > trigger a use-after-free? > > [Severity: High] > This isn't a bug introduced by this patch, but does the driver verify if > kthread_run() returns an ERR_PTR when starting the recycle thread? > > If thread creation fails in vdec_start_streaming() and returns an ERR_PTR, > could a later call to kthread_stop(sess->recycle_thread) in > vdec_stop_streaming() attempt to dereference that ERR_PTR and cause a > kernel panic? > I will try to address these issues in the next version. > -- > Sashiko AI review ? https://sashiko.dev/#/patchset/20260520044046.7553-1-linux.amoon at gmail.com?part=1 Thanks -Anand From jian.hu at amlogic.com Tue May 19 22:47:27 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Wed, 20 May 2026 13:47:27 +0800 Subject: [PATCH 07/10] clk: amlogic: Support POWER_OF_TWO for PLL pre-divider In-Reply-To: <1jy0hm6n7e.fsf@starbuckisacylon.baylibre.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-7-41cb4071b7c9@amlogic.com> <1jy0hm6n7e.fsf@starbuckisacylon.baylibre.com> Message-ID: <8d89b669-e72e-4663-9596-999a12922d32@amlogic.com> On 5/14/2026 11:11 PM, Jerome Brunet wrote: > [ EXTERNAL EMAIL ] > > On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > >> From: Jian Hu >> >> The A9 PLL pre-divider uses a division factor of 2^n to ensure a clock >> duty cycle of 50% after predivision. >> >> Add flag 'CLK_MESON_PLL_N_POWER_OF_TWO' to indicate that the PLL >> pre-divider division factor is 2^n. > I understand what you are doing here but I have to ask why this can't be > implemented with independent dividers that already supports power of 2 ? If we use independent dividers, the n member would have to be removed from meson_clk_pll_data. However, n is referenced 35 times in clk-pll.c, which means we would need to modify all related logic across the file. This would be a relatively large change. Moreover, for all Amlogic chips, the n divider is an indispensable part of the DCO clock. The difference between SoC generations is as follows: ? ? Previous SoCs PLL: n = 1, 2, 3, 4... (linear divider) ? ? A9 SoC PLL:? ? ? ? ? ? n = 2^0, 2^1, 2^2, 2^3, 2^4... (power-of-two divider) Therefore, splitting out the n divider from the DCO clock might not be a good design choice. [...] Best regards, Jian From luca.ceresoli at bootlin.com Tue May 19 23:45:49 2026 From: luca.ceresoli at bootlin.com (Luca Ceresoli) Date: Wed, 20 May 2026 08:45:49 +0200 Subject: [PATCH v7 04/23] drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup In-Reply-To: References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-5-jonas@kwiboo.se> <177919241969.545972.18169905222569864569.b4-review@b4> Message-ID: <177925954999.1337464.14745526881417951688.b4-reply@b4> Hello Jonas, On 2026-05-19 17:18 +0200, Jonas Karlman wrote: > Hello Luca, > > On 5/19/2026 2:06 PM, Luca Ceresoli wrote: > > On Mon, 18 May 2026 18:01:40 +0000, Jonas Karlman wrote: > > > > Hello Jonas, > > > >> > >> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > >> index b7bfc0e9a6b2..9d795c550f8a 100644 > >> --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > >> +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > >> @@ -2600,10 +2609,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); > > > > I'm not fully following the code paths, but both the report and the fix > > make sense to me. Only I'd move the drm_bridge_get() before > > drm_connector_init_with_ddc(), to avoid a short window where no reference > > is held and the bridge might be destroyed before drm_bridge_get() is > > called. I'm not sure this can happen, but it's better to write the code in > > a way that clearly makes it impossible. > > dw_hdmi_connector_create() is only called from dw_hdmi_bridge_attach() > so the bridge should already have a ref for the lifetime of this call. Ah, that's true. So the patch is correct. Reviewed-by: Luca Ceresoli In case you send a new iteration, please add this extra explanation to the commit message, similar to the above paragraph. > I explicitly chose the placement after drm_connector_init_with_ddc() > to ensure ref count is correctly balanced without having to add a > drm_bridge_put() call in any error path. I.e. connector destroy() is > only called when drm_connector_init_with_ddc() succeeds. > > This code/call is also planned to be removed in a future series, In order to remove the !DRM_BRIDGE_ATTACH_NO_CONNECTOR case? That would be welcome! Luca From jbrunet at baylibre.com Wed May 20 00:24:12 2026 From: jbrunet at baylibre.com (Jerome Brunet) Date: Wed, 20 May 2026 09:24:12 +0200 Subject: [PATCH 05/10] clk: amlogic: PLL l_detect signal supports active-high configuration In-Reply-To: (Jian Hu's message of "Wed, 20 May 2026 11:25:35 +0800") References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-5-41cb4071b7c9@amlogic.com> <1jse7u6n3q.fsf@starbuckisacylon.baylibre.com> Message-ID: <1jwlwy5ysj.fsf@starbuckisacylon.baylibre.com> On mer. 20 mai 2026 at 11:25, Jian Hu wrote: > On 5/14/2026 11:13 PM, Jerome Brunet wrote: >> [ EXTERNAL EMAIL ] >> >> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: >> >>> From: Jian Hu >>> >>> l_detect controls the enable/disable of the PLL lock-detect module. >>> >>> For A9, the l_detect signal is active-high: >>> 0 -> Disable lock-detect module; >>> 1 -> Enable lock-detect module. >>> >>> Here, a flag CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH is added to handle cases >>> like A9, where the signal is active-high. >>> >>> Signed-off-by: Jian Hu >>> --- >>> drivers/clk/meson/clk-pll.c | 9 +++++++-- >>> drivers/clk/meson/clk-pll.h | 2 ++ >>> 2 files changed, 9 insertions(+), 2 deletions(-) >>> >>> diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c >>> index 1ea6579a760f..5a0bd75f85a9 100644 >>> --- a/drivers/clk/meson/clk-pll.c >>> +++ b/drivers/clk/meson/clk-pll.c >>> @@ -388,8 +388,13 @@ static int meson_clk_pll_enable(struct clk_hw *hw) >>> } >>> >>> if (MESON_PARM_APPLICABLE(&pll->l_detect)) { >>> - meson_parm_write(clk->map, &pll->l_detect, 1); >>> - meson_parm_write(clk->map, &pll->l_detect, 0); >>> + if (pll->flags & CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH) { >>> + meson_parm_write(clk->map, &pll->l_detect, 0); >>> + meson_parm_write(clk->map, &pll->l_detect, 1); >>> + } else { >>> + meson_parm_write(clk->map, &pll->l_detect, 1); >>> + meson_parm_write(clk->map, &pll->l_detect, 0); >>> + } >> I'm not a fan of this code duplication. >> Use the introduced CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH to compute the >> first value, then flip the bit. > > > Ok, I will update this in the next version. > > Here is the updated code: > > ? ? ? ? if (MESON_PARM_APPLICABLE(&pll->l_detect)) { > ? ? ? ? ? ? ? ? ? ? ? ? meson_parm_write(clk->map, &pll->l_detect, > ? ? ? ? ? ? ? ? ? ? ? ? !(pll->flags & > CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH)); > ? ? ? ? ? ? ? ? ? ? ? ? meson_parm_write(clk->map, &pll->l_detect, > ? ? ? ? ? ? ? ? ? ? ? ? !!(pll->flags & > CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH)); Please use a variable. Make it clean > ? ? ? ? } > >>> } >>> >>> if (meson_clk_pll_wait_lock(hw)) >>> diff --git a/drivers/clk/meson/clk-pll.h b/drivers/clk/meson/clk-pll.h >>> index 949157fb7bf5..97b7c70376a3 100644 >>> --- a/drivers/clk/meson/clk-pll.h >>> +++ b/drivers/clk/meson/clk-pll.h >>> @@ -29,6 +29,8 @@ struct pll_mult_range { >>> >>> #define CLK_MESON_PLL_ROUND_CLOSEST BIT(0) >>> #define CLK_MESON_PLL_NOINIT_ENABLED BIT(1) >>> +/* l_detect signal is active-high */ >>> +#define CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH BIT(2) >>> >>> struct meson_clk_pll_data { >>> struct parm en; >> -- >> Jerome > > Best regards, > > Jian -- Jerome From jian.hu at amlogic.com Wed May 20 00:33:07 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Wed, 20 May 2026 15:33:07 +0800 Subject: [PATCH 08/10] clk: amlogic: Add A9 PLL clock controller driver In-Reply-To: <1jh5oa6kcm.fsf@starbuckisacylon.baylibre.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-8-41cb4071b7c9@amlogic.com> <1jh5oa6kcm.fsf@starbuckisacylon.baylibre.com> Message-ID: <96aac0a2-7531-4a40-8a55-630e92012bb5@amlogic.com> On 5/15/2026 12:12 AM, Jerome Brunet wrote: > [ EXTERNAL EMAIL ] > > On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > >> From: Jian Hu >> >> Add the PLL clock controller driver for the Amlogic A9 SoC family. >> >> Signed-off-by: Jian Hu [...] >> + >> +/* >> + * Compared with previous SoC PLLs, the A9 PLL input path has an inherent >> + * 2-divider. The N pre-divider follows the same calculation rule as OD, >> + * where the pre-divider ratio equals 2^N. >> + * >> + * A9 PLL is composed as follows: >> + * >> + * PLL >> + * +---------------------------------+ >> + * | | >> + * | +--+ | >> + * in/2 >>---[ /2^N ]-->| | +-----+ | >> + * | | |------| DCO |----->> out >> + * | +--------->| | +--v--+ | >> + * | | +--+ | | >> + * | | | | >> + * | +--[ *(M + (F/Fmax) ]<--+ | >> + * | | >> + * +---------------------------------+ >> + * >> + * out = in / 2 * (m + frac / frac_max) / 2^n >> + */ >> + >> +static struct clk_fixed_factor a9_gp0_in_div2_div = { >> + .mult = 1, >> + .div = 2, >> + .hw.init = &(struct clk_init_data){ >> + .name = "gp0_in_div2_div", >> + .ops = &clk_fixed_factor_ops, >> + .parent_data = &(const struct clk_parent_data) { >> + .fw_name = "in0", >> + }, >> + .num_parents = 1, >> + }, >> +}; >> + >> +static struct clk_regmap a9_gp0_in_div2 = { >> + .data = &(struct clk_regmap_gate_data) { >> + .offset = GP0PLL_CTRL0, >> + .bit_idx = 27, >> + }, >> + .hw.init = &(struct clk_init_data) { >> + .name = "gp0_in_div2", >> + .ops = &clk_regmap_gate_ops, >> + .parent_hws = (const struct clk_hw *[]) { >> + &a9_gp0_in_div2_div.hw >> + }, >> + .num_parents = 1, >> + }, >> +}; > When document something, be sure it matches what you are doing > afterward. It is confusing otherwise. Your comments above clearly miss > this gate. > > A fixed 2 divider followed by a power of 2 divider ? Is it actually how > the HW works or your modelisation power of 2 that's shifted by 1, > mapping : > * 0 -> 2 > * 1 -> 4 > * etc ... > > ? Sorry for missing the gate in PLL block diagram, above block diagram focuses on mathematical formulas. A9 PLL is composed as follows in fact, M and frac have a 0.5 weight factor: ? ? ? ? ? ? ? ? ? ? ? ?PLL ? ? ? ? ? +-----------------------------------------------------+ ? ? ? ? ? |? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| ? ? ? ? ? |? ? ? ? ? ? ?+--+? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ?in? ?>>---[ /N ]-->? |? |? ? ? ? ? ? ? ? ? ? ?+-----+ ? ? ? | ? ? ? ? ? |? ? ? ? ? ? ?|? |---------------------| DCO | |----->> out ? ? ? ? ? |? +--------->|? |? ? ? ? ? ? ? ? ? ? ?+--v--+ | ? ? ? ? ? |? |? ? ? ? ? +--+? ? ? ? ? ? ? ? ? ? ? ? |? ? ? ? ? ?| ? ? ? ? ? |? |? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |? ? ? ? ? ?| ? ? ? ? ? |? +--[ *(M + (F/Fmax) ] * 0.5 + Enable<--+ ?| ? ? ? ? ? |? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| ? ? ? ? ? +-----------------------------------------------------+ ? ? ? ? out = in? * (M + frac / frac_max) * 0.5 / N ?If we ignore frac and set N = 1, it simplifies to: ? ? ? ? out = in? * M? * 0.5 This can be rewritten as: ? ? ? ? out = (in / 2) * M The 0.5 weight is hardware-controlled: ? ? ? ? For GP0/HIFI PLL: controlled by CTRL0 bit27 (gate) ? ? ? ? For MCLK PLL: enabled by default, no gate bit To model this in the clock tree, we add: ? ? ? ? A fixed /2 divider after input clock to represent the 0.5 weight ? ? ? ? A gate clock to represent the enable control The resulting structure is: ? ? ? ? input --> fixed div2 --> gate--> dco I would appreciate your guidance If this is not appropriate. >> + >> +/* The output frequency range of the A9 PLL_DCO is 1.4 GHz to 2.8 GHz. */ >> +static const struct pll_mult_range a9_pll_mult_range = { >> + .min = 117, >> + .max = 233, >> +}; > If PLL restriction is actually the DCO output rate, and only the reason > to keep the pre-devider in the range above, I would definitely welcome a > rework to express the constraints properly and split the pre-divider out. > >> + >> +static const struct reg_sequence a9_gp0_pll_init_regs[] = { >> + { .reg = GP0PLL_CTRL0, .def = 0x00010000 }, >> + { .reg = GP0PLL_CTRL1, .def = 0x11480000 }, >> + { .reg = GP0PLL_CTRL2, .def = 0x1219b010 }, >> + { .reg = GP0PLL_CTRL3, .def = 0x00008010 } >> +}; >> + >> +static struct clk_regmap a9_gp0_pll_dco = { >> + .data = &(struct meson_clk_pll_data) { >> + .en = { >> + .reg_off = GP0PLL_CTRL0, >> + .shift = 28, >> + .width = 1, >> + }, >> + .m = { >> + .reg_off = GP0PLL_CTRL0, >> + .shift = 0, >> + .width = 9, >> + }, >> + .n = { >> + .reg_off = GP0PLL_CTRL0, >> + .shift = 12, >> + .width = 3, >> + }, >> + .frac = { >> + .reg_off = GP0PLL_CTRL1, >> + .shift = 0, >> + .width = 17, >> + }, >> + .l = { >> + .reg_off = GP0PLL_CTRL0, >> + .shift = 31, >> + .width = 1, >> + }, >> + .rst = { >> + .reg_off = GP0PLL_CTRL0, >> + .shift = 29, >> + .width = 1, >> + }, >> + .l_detect = { >> + .reg_off = GP0PLL_CTRL0, >> + .shift = 30, >> + .width = 1, >> + }, >> + .range = &a9_pll_mult_range, >> + .init_regs = a9_gp0_pll_init_regs, >> + .init_count = ARRAY_SIZE(a9_gp0_pll_init_regs), >> + .flags = CLK_MESON_PLL_RST_ACTIVE_LOW | >> + CLK_MESON_PLL_N_POWER_OF_TWO | >> + CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH, >> + }, >> + .hw.init = &(struct clk_init_data) { >> + .name = "gp0_pll_dco", >> + .ops = &meson_clk_pll_ops, >> + .parent_hws = (const struct clk_hw *[]) { >> + &a9_gp0_in_div2.hw >> + }, >> + .num_parents = 1, >> + }, >> +}; >> + >> +/* For gp0, hifi and mclk pll, the maximum value of od is 4. */ >> +static const struct clk_div_table a9_pll_od_table[] = { >> + { 0, 1 }, >> + { 1, 2 }, >> + { 2, 4 }, >> + { 3, 8 }, >> + { 4, 16 }, >> + { /* sentinel */ } >> +}; >> + >> +static struct clk_regmap a9_gp0_pll = { >> + .data = &(struct clk_regmap_div_data) { >> + .offset = GP0PLL_CTRL0, >> + .shift = 20, >> + .width = 3, >> + .table = a9_pll_od_table, >> + }, >> + .hw.init = &(struct clk_init_data) { >> + .name = "gp0_pll", >> + .ops = &clk_regmap_divider_ops, >> + .parent_hws = (const struct clk_hw *[]) { >> + &a9_gp0_pll_dco.hw >> + }, >> + .num_parents = 1, >> + .flags = CLK_SET_RATE_PARENT, >> + }, >> +}; >> + >> +static struct clk_fixed_factor a9_hifi0_in_div2_div = { >> + .mult = 1, >> + .div = 2, >> + .hw.init = &(struct clk_init_data){ >> + .name = "hifi0_in_div2_div", >> + .ops = &clk_fixed_factor_ops, >> + .parent_data = &(const struct clk_parent_data) { >> + .fw_name = "in0", >> + }, >> + .num_parents = 1, >> + }, >> +}; >> + >> +static struct clk_regmap a9_hifi0_in_div2 = { >> + .data = &(struct clk_regmap_gate_data) { >> + .offset = HIFIPLL_CTRL0, >> + .bit_idx = 27, >> + }, >> + .hw.init = &(struct clk_init_data) { >> + .name = "hifi0_in_div2", >> + .ops = &clk_regmap_gate_ops, >> + .parent_hws = (const struct clk_hw *[]) { >> + &a9_hifi0_in_div2_div.hw >> + }, >> + .num_parents = 1, >> + }, >> +}; >> + >> +static const struct reg_sequence a9_hifi0_pll_init_regs[] = { >> + { .reg = HIFIPLL_CTRL0, .def = 0x00010000 }, >> + { .reg = HIFIPLL_CTRL1, .def = 0x11480000 }, >> + { .reg = HIFIPLL_CTRL2, .def = 0x1219b010 }, >> + { .reg = HIFIPLL_CTRL3, .def = 0x00008010 } >> +}; > It look like GP0 and HIFI PLL are exactly the same IP, you've even > documented it as such. Yet all the code is duplicated. That's not OK. > > I understand that way we statically declared the clocks so far pushed > you in that direction. That's something I'd like to fix properly > someday. > > In the meantime, you could at least duplicate the memory at runtime to > avoid copy/pasting the code. A minor change to clkc utils as suggested > at the end of this message could help you do so. > > Same probably applies to mclks. You're right, the GP0 and HIFI PLLs are indeed the same IP, differing only by frac_max: ? ? GP0: frac_max = 2^17 ? ? HIFI: frac_max = 100000 Each clock requires its own clk_regmap and clk_hw structure, though the data in clk_regmap can be shared between HIFI0 and HIFI1. I have tried duplicating HIFI1's clock structure from HIFI0 at runtime. Most members of clk_init_data (except parent_hws / parent_data) can be easily copied. However, I have a question regarding dynamic parent assignment: For example: Clock B is created dynamically, and its parent is clock A (also created dynamically). How should I properly assign this parent relationship? Furthermore, how to handle more complex parent configurations dynamically? For example: Clock D has three parents: C, B, A (in an irregular order). I would appreciate your guidance on how to handle these dynamic clock relationships properly. > > [...] Best regards, Jian From jbrunet at baylibre.com Wed May 20 00:35:34 2026 From: jbrunet at baylibre.com (Jerome Brunet) Date: Wed, 20 May 2026 09:35:34 +0200 Subject: [PATCH 07/10] clk: amlogic: Support POWER_OF_TWO for PLL pre-divider In-Reply-To: <8d89b669-e72e-4663-9596-999a12922d32@amlogic.com> (Jian Hu's message of "Wed, 20 May 2026 13:47:27 +0800") References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-7-41cb4071b7c9@amlogic.com> <1jy0hm6n7e.fsf@starbuckisacylon.baylibre.com> <8d89b669-e72e-4663-9596-999a12922d32@amlogic.com> Message-ID: <1jqzn65y9l.fsf@starbuckisacylon.baylibre.com> On mer. 20 mai 2026 at 13:47, Jian Hu wrote: > On 5/14/2026 11:11 PM, Jerome Brunet wrote: >> [ EXTERNAL EMAIL ] >> >> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: >> >>> From: Jian Hu >>> >>> The A9 PLL pre-divider uses a division factor of 2^n to ensure a clock >>> duty cycle of 50% after predivision. >>> >>> Add flag 'CLK_MESON_PLL_N_POWER_OF_TWO' to indicate that the PLL >>> pre-divider division factor is 2^n. >> I understand what you are doing here but I have to ask why this can't be >> implemented with independent dividers that already supports power of 2 ? > > > If we use independent dividers, the n member would have to be removed from > meson_clk_pll_data. > > However, n is referenced 35 times in clk-pll.c, which means we would need > to modify all > related logic across the file. This would be a relatively large > change. Yes > > > Moreover, for all Amlogic chips, the n divider is an indispensable part of > the DCO clock. There is hardly a justification here > The difference between SoC generations is as follows: > ? ? Previous SoCs PLL: n = 1, 2, 3, 4... (linear divider) > ? ? A9 SoC PLL:? ? ? ? ? ? n = 2^0, 2^1, 2^2, 2^3, 2^4... (power-of-two > divider) Yes that was fairly obvious > > Therefore, splitting out the n divider from the DCO clock might not be a > good design choice. I'm not sure I agree and you've only stated your point of view without providing any technical justification here. >From the datasheets of the different SoC we have, the documented limitation is always the DCO output rate range. Nothing related to n (or m, or the mult-range for that matter). This is a legacy problem, we started with monolithic driver and slowly simplified it. As far as I can see now, reworking the PLL driver to be a simple multiplier driver with range output rate constraint could actually be simpler than the current code. I would also make simpler to accomodate differences such as the one presented here. Unless you can provide technical reasons why going in this direction would be incorrect, that's where I'd prefer to go. > > [...] > > Best regards, > > Jian -- Jerome From jian.hu at amlogic.com Wed May 20 00:37:08 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Wed, 20 May 2026 15:37:08 +0800 Subject: [PATCH 10/10] clk: amlogic: Add A9 AO clock controller driver In-Reply-To: <1j33zu6jnl.fsf@starbuckisacylon.baylibre.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-10-41cb4071b7c9@amlogic.com> <1j33zu6jnl.fsf@starbuckisacylon.baylibre.com> Message-ID: <5b6ce98a-f27e-4777-8a86-99a7facbefd0@amlogic.com> On 5/15/2026 12:27 AM, Jerome Brunet wrote: > [ EXTERNAL EMAIL ] > > On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > >> From: Jian Hu >> >> Add the Always-on clock controller driver for the Amlogic A9 SoC family. >> >> Signed-off-by: Jian Hu >> --- >> drivers/clk/meson/Makefile | 2 +- >> drivers/clk/meson/a9-aoclk.c | 494 +++++++++++++++++++++++++++++++++++++++++++ >> 2 files changed, 495 insertions(+), 1 deletion(-) >> >> diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile >> index 2b5b67b14efc..91af609ce815 100644 >> --- a/drivers/clk/meson/Makefile >> +++ b/drivers/clk/meson/Makefile >> @@ -20,7 +20,7 @@ obj-$(CONFIG_COMMON_CLK_AXG_AUDIO) += axg-audio.o >> obj-$(CONFIG_COMMON_CLK_A1_PLL) += a1-pll.o >> obj-$(CONFIG_COMMON_CLK_A1_PERIPHERALS) += a1-peripherals.o >> obj-$(CONFIG_COMMON_CLK_A9_PLL) += a9-pll.o >> -obj-$(CONFIG_COMMON_CLK_A9_PERIPHERALS) += a9-peripherals.o >> +obj-$(CONFIG_COMMON_CLK_A9_PERIPHERALS) += a9-peripherals.o a9-aoclk.o >> obj-$(CONFIG_COMMON_CLK_C3_PLL) += c3-pll.o >> obj-$(CONFIG_COMMON_CLK_C3_PERIPHERALS) += c3-peripherals.o >> obj-$(CONFIG_COMMON_CLK_GXBB) += gxbb.o gxbb-aoclk.o >> diff --git a/drivers/clk/meson/a9-aoclk.c b/drivers/clk/meson/a9-aoclk.c >> new file mode 100644 >> index 000000000000..3c42eaf585d2 >> --- /dev/null >> +++ b/drivers/clk/meson/a9-aoclk.c >> @@ -0,0 +1,494 @@ >> +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) >> +/* >> + * Copyright (C) 2026 Amlogic, Inc. All rights reserved >> + */ >> + >> +#include >> +#include >> +#include >> +#include "clk-regmap.h" >> +#include "clk-dualdiv.h" >> +#include "meson-clkc-utils.h" >> + >> +#define AO_OSCIN_CTRL 0x00 >> +#define AO_SYS_CLK0 0x04 >> +#define AO_PWM_CLK_A_CTRL 0x1c >> +#define AO_PWM_CLK_B_CTRL 0x20 >> +#define AO_PWM_CLK_C_CTRL 0x24 >> +#define AO_PWM_CLK_D_CTRL 0x28 >> +#define AO_PWM_CLK_E_CTRL 0x2c >> +#define AO_PWM_CLK_F_CTRL 0x30 >> +#define AO_PWM_CLK_G_CTRL 0x34 >> +#define AO_CEC_CTRL0 0x38 >> +#define AO_CEC_CTRL1 0x3c >> +#define AO_RTC_BY_OSCIN_CTRL0 0x50 >> +#define AO_RTC_BY_OSCIN_CTRL1 0x54 >> + >> +#define A9_COMP_SEL(_name, _reg, _shift, _mask, _pdata) \ >> + MESON_COMP_SEL(a9_, _name, _reg, _shift, _mask, _pdata, NULL, 0, 0) > a9_ao_ ? > Ok, I will replace it with the prefix a9_ao_ [...] Best regards, Jian From jian.hu at amlogic.com Wed May 20 01:46:59 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Wed, 20 May 2026 16:46:59 +0800 Subject: [PATCH 05/10] clk: amlogic: PLL l_detect signal supports active-high configuration In-Reply-To: <1jwlwy5ysj.fsf@starbuckisacylon.baylibre.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-5-41cb4071b7c9@amlogic.com> <1jse7u6n3q.fsf@starbuckisacylon.baylibre.com> <1jwlwy5ysj.fsf@starbuckisacylon.baylibre.com> Message-ID: <1bea1b19-635a-49ba-9859-3074607eefad@amlogic.com> On 5/20/2026 3:24 PM, Jerome Brunet wrote: > [ EXTERNAL EMAIL ] > > On mer. 20 mai 2026 at 11:25, Jian Hu wrote: > >> On 5/14/2026 11:13 PM, Jerome Brunet wrote: >>> [ EXTERNAL EMAIL ] >>> >>> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: >>> >>>> From: Jian Hu >>>> >>>> l_detect controls the enable/disable of the PLL lock-detect module. >>>> >>>> For A9, the l_detect signal is active-high: >>>> 0 -> Disable lock-detect module; >>>> 1 -> Enable lock-detect module. >>>> >>>> Here, a flag CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH is added to handle cases >>>> like A9, where the signal is active-high. >>>> >>>> Signed-off-by: Jian Hu >>>> --- >>>> drivers/clk/meson/clk-pll.c | 9 +++++++-- >>>> drivers/clk/meson/clk-pll.h | 2 ++ >>>> 2 files changed, 9 insertions(+), 2 deletions(-) >>>> >>>> diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c >>>> index 1ea6579a760f..5a0bd75f85a9 100644 >>>> --- a/drivers/clk/meson/clk-pll.c >>>> +++ b/drivers/clk/meson/clk-pll.c >>>> @@ -388,8 +388,13 @@ static int meson_clk_pll_enable(struct clk_hw *hw) >>>> } >>>> >>>> if (MESON_PARM_APPLICABLE(&pll->l_detect)) { >>>> - meson_parm_write(clk->map, &pll->l_detect, 1); >>>> - meson_parm_write(clk->map, &pll->l_detect, 0); >>>> + if (pll->flags & CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH) { >>>> + meson_parm_write(clk->map, &pll->l_detect, 0); >>>> + meson_parm_write(clk->map, &pll->l_detect, 1); >>>> + } else { >>>> + meson_parm_write(clk->map, &pll->l_detect, 1); >>>> + meson_parm_write(clk->map, &pll->l_detect, 0); >>>> + } >>> I'm not a fan of this code duplication. >>> Use the introduced CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH to compute the >>> first value, then flip the bit. >> >> Ok, I will update this in the next version. >> >> Here is the updated code: >> >> if (MESON_PARM_APPLICABLE(&pll->l_detect)) { >> meson_parm_write(clk->map, &pll->l_detect, >> !(pll->flags & >> CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH)); >> meson_parm_write(clk->map, &pll->l_detect, >> !!(pll->flags & >> CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH)); > Please use a variable. Make it clean Ok, I will use a variable for it. >> } >> >>>> } >>>> >>>> if (meson_clk_pll_wait_lock(hw)) >>>> diff --git a/drivers/clk/meson/clk-pll.h b/drivers/clk/meson/clk-pll.h >>>> index 949157fb7bf5..97b7c70376a3 100644 >>>> --- a/drivers/clk/meson/clk-pll.h >>>> +++ b/drivers/clk/meson/clk-pll.h >>>> @@ -29,6 +29,8 @@ struct pll_mult_range { >>>> >>>> #define CLK_MESON_PLL_ROUND_CLOSEST BIT(0) >>>> #define CLK_MESON_PLL_NOINIT_ENABLED BIT(1) >>>> +/* l_detect signal is active-high */ >>>> +#define CLK_MESON_PLL_L_DETECT_ACTIVE_HIGH BIT(2) >>>> >>>> struct meson_clk_pll_data { >>>> struct parm en; >>> -- >>> Jerome >> Best regards, >> >> Jian > -- > Jerome Best regards, Jian From neil.armstrong at linaro.org Wed May 20 02:17:15 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 20 May 2026 11:17:15 +0200 Subject: [PATCH v7 16/23] drm: bridge: dw_hdmi: Update EDID and CEC phys addr in bridge detect() In-Reply-To: <20260518180206.2480119-17-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-17-jonas@kwiboo.se> Message-ID: On 5/18/26 20:01, Jonas Karlman wrote: > 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. > > This change introduce a slight delay to the bridge connector detect() > and get_modes() funcs due to multiple EDID reads. This is an acceptable > added delay to help ensure EDID and CEC phys addr always are correct. > > Signed-off-by: Jonas Karlman > --- > v7: Update commit message > 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. > > The patch "drm/bridge-connector: Use cached connector status in > .get_modes()" [1] can help remove one unnecessary EDID read until > dw-hdmi is fully converted into a HDMI bridge. > > [1] https://lore.kernel.org/dri-devel/20260426-dw-hdmi-qp-scramb-v5-3-d778e70c317b at collabora.com/ > --- > 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 37406555af7b..0c4388e7aa5e 100644 > --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > @@ -2947,8 +2947,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, Reviewed-by: Neil Armstrong Thanks, Neil From jonas at kwiboo.se Wed May 20 02:38:21 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Wed, 20 May 2026 11:38:21 +0200 Subject: [PATCH v7 04/23] drm: bridge: dw_hdmi: Hold bridge ref until connector cleanup In-Reply-To: <177925954999.1337464.14745526881417951688.b4-reply@b4> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-5-jonas@kwiboo.se> <177919241969.545972.18169905222569864569.b4-review@b4> <177925954999.1337464.14745526881417951688.b4-reply@b4> Message-ID: <1bc6969c-110a-46e0-99fb-1a2e0cfef842@kwiboo.se> Hello Luca, On 5/20/2026 8:45 AM, Luca Ceresoli wrote: > Hello Jonas, > > On 2026-05-19 17:18 +0200, Jonas Karlman wrote: >> Hello Luca, >> >> On 5/19/2026 2:06 PM, Luca Ceresoli wrote: >>> On Mon, 18 May 2026 18:01:40 +0000, Jonas Karlman wrote: >>> >>> Hello Jonas, >>> >>>> >>>> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >>>> index b7bfc0e9a6b2..9d795c550f8a 100644 >>>> --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >>>> +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >>>> @@ -2600,10 +2609,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); >>> >>> I'm not fully following the code paths, but both the report and the fix >>> make sense to me. Only I'd move the drm_bridge_get() before >>> drm_connector_init_with_ddc(), to avoid a short window where no reference >>> is held and the bridge might be destroyed before drm_bridge_get() is >>> called. I'm not sure this can happen, but it's better to write the code in >>> a way that clearly makes it impossible. >> >> dw_hdmi_connector_create() is only called from dw_hdmi_bridge_attach() >> so the bridge should already have a ref for the lifetime of this call. > > Ah, that's true. So the patch is correct. > > Reviewed-by: Luca Ceresoli Thanks. > In case you send a new iteration, please add this extra explanation to the > commit message, similar to the above paragraph. Sure, I will include a note about this if/when I need to re-spin. >> I explicitly chose the placement after drm_connector_init_with_ddc() >> to ensure ref count is correctly balanced without having to add a >> drm_bridge_put() call in any error path. I.e. connector destroy() is >> only called when drm_connector_init_with_ddc() succeeds. >> >> This code/call is also planned to be removed in a future series, > > In order to remove the !DRM_BRIDGE_ATTACH_NO_CONNECTOR case? That would be > welcome! That would be an end goal, however initial plan/step was to just change to use drm_bridge_connector_init() inside this driver [1] or possible move that to the consuming driver (imx6, rockchip and sun8i), in an unpolished future series [2]. Fully change to use ATTACH_NO_CONNECTOR for those affected drivers may possibly be pushed to a follow-up future series. Main end goal of my current effort is to enable support for Deep Color and YCbCr output modes on Rockchip RK32xx/RK33xx/RK356x devices [3]. [1] https://github.com/Kwiboo/linux-rockchip/commit/813b55961e5a8fa864ea157e2793e76ca4967bac [2] https://github.com/Kwiboo/linux-rockchip/compare/7e9084cc75011ce28b1ceafec804091438eed1ff...3b5507aa260eb8306554c34a0c362e514ea41c3b [3] https://github.com/Kwiboo/linux-rockchip/commits/next-20260518-rk-hdmi-v5/ Regards, Jonas > > Luca > From neil.armstrong at linaro.org Wed May 20 02:58:26 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 20 May 2026 11:58:26 +0200 Subject: [PATCH v7 19/23] drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event In-Reply-To: <20260518180206.2480119-20-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-20-jonas@kwiboo.se> Message-ID: Hi, On 5/18/26 20:01, Jonas Karlman wrote: > 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. The logic looks ok, but I'm puzzled by the 1.1 sec debounce, which after plugging in a monitor will only send an irq event after 1.1s which is very long. Since the spec says 100ms and the real worls values are more like 200-300ms, I would first reduce this to 500ms. But as I understand the code right now, on the first HPD front the irq work is programmed to run after the debounce time, but if it's a pulse the irq would also trigger on the second HPD front and then delay again the work after the debounce time. My understanding of a debounce was that we "ignore" the pulse by only generating a single irq event when the pulse is finished. The current code does that, we will only have a single irq event and the HPD will return as connected state, good. But this delays the irq event 1.1s _after_ the end of the pulse, which I would expect the event to be send at tht debounce time after the start of the pulse. Like, program the work at the beginning of the pulse, if somehow the pulse ends before the debounce time, send the irq event immediately, otherwise let the debounce work run after the debounce time which will trigger a disconnect event. But the delay is too high, 1.1s could be a manual unplug/plug or bad connector with false contact on the hpd pin. I would rather reduce this to something more realistic like 500ms or less and try to better handle the pulse somehow. But I don't have any idea if the scheme I described is doable. Neil > > Signed-off-by: Jonas Karlman > --- > v7: Change to free irq before mute and clear using IH regs, also include > clear of STAT0_RX_SENSE > 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 | 80 +++++++++++++++++++++-- > 1 file changed, 75 insertions(+), 5 deletions(-) > > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index 8afc9d240121..270db58a0e7c 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); > @@ -3532,6 +3593,18 @@ 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); > + > + /* Free, mute and clear phy interrupts */ > + devm_free_irq(hdmi->dev, irq, hdmi); > + hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); > + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, > + HDMI_IH_PHY_STAT0); > + > + /* Cancel any pending hot plug work */ > + cancel_delayed_work_sync(&hdmi->hpd_work); > + > drm_bridge_remove(&hdmi->bridge); > > if (hdmi->audio && !IS_ERR(hdmi->audio)) > @@ -3539,9 +3612,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 From neil.armstrong at linaro.org Wed May 20 02:59:53 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Wed, 20 May 2026 11:59:53 +0200 Subject: [PATCH v7 20/23] drm: bridge: dw_hdmi: Rework HDP and RXSENSE interrupt handling In-Reply-To: <20260518180206.2480119-21-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-21-jonas@kwiboo.se> Message-ID: <24594ab4-6865-48b7-9e92-ae6a744c8d58@linaro.org> On 5/18/26 20:01, Jonas Karlman wrote: > 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. I'm still puzzled of the removal of RX_SENSE entirely as v1, and I since the rx_sense code is not easy to understand I don't have an opinion on that. Can someone with more knowledge can comment on that ? Neil > > Tested-by: Diederik de Haas # Rock64, RockPro64, Quartz64-B > Signed-off-by: Jonas Karlman > --- > v7: Remove clear of STAT0_RX_SENSE in dw_hdmi_remove() added in prior > patch > 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 | 147 ++++------------------ > 1 file changed, 22 insertions(+), 125 deletions(-) > > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c > index 270db58a0e7c..2e09bff5faf7 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; > > @@ -3599,8 +3497,7 @@ void dw_hdmi_remove(struct dw_hdmi *hdmi) > /* Free, mute and clear phy interrupts */ > devm_free_irq(hdmi->dev, irq, hdmi); > hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); > - 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); > > /* Cancel any pending hot plug work */ > cancel_delayed_work_sync(&hdmi->hpd_work); From dmitry.baryshkov at oss.qualcomm.com Wed May 20 05:26:49 2026 From: dmitry.baryshkov at oss.qualcomm.com (Dmitry Baryshkov) Date: Wed, 20 May 2026 15:26:49 +0300 Subject: [PATCH RESEND v3 1/6] drm/connector: report IRQ_HPD events to drm_connector_oob_hotplug_event() In-Reply-To: <20260513-hpd-irq-events-v3-1-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> <20260513-hpd-irq-events-v3-1-086857017f16@oss.qualcomm.com> Message-ID: On Wed, May 13, 2026 at 09:23:21PM +0300, Dmitry Baryshkov wrote: > The DisplayPort standard defines a special kind of events called IRQ. > These events are used to notify DP Source about the events on the Sink > side. It is extremely important for DP MST handling, where the MST > events are reported through this IRQ. > > In case of the USB-C DP AltMode there is no actual HPD pulse, but the > events are ported through the bits in the AltMode VDOs. > > Extend the drm_connector_oob_hotplug_event() interface and report IRQ > events to the DisplayPort Sink drivers. > > Signed-off-by: Dmitry Baryshkov > --- > drivers/gpu/drm/drm_connector.c | 5 ++++- > drivers/usb/typec/altmodes/displayport.c | 15 +++++++++++---- > include/drm/drm_connector.h | 19 ++++++++++++++++++- > 3 files changed, 33 insertions(+), 6 deletions(-) Greg, Heikki, would you please ack merging this through the drm tree? > > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c > index 47dc53c4a738..edee9daccd51 100644 > --- a/drivers/gpu/drm/drm_connector.c > +++ b/drivers/gpu/drm/drm_connector.c > @@ -3510,6 +3510,8 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) > * drm_connector_oob_hotplug_event - Report out-of-band hotplug event to connector > * @connector_fwnode: fwnode_handle to report the event on > * @status: hot plug detect logical state > + * @extra_status: additional information provided by the sink without changing > + * the HPD state (or in addition to such a change). > * > * On some hardware a hotplug event notification may come from outside the display > * driver / device. An example of this is some USB Type-C setups where the hardware > @@ -3520,7 +3522,8 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) > * a drm_connector reference through calling drm_connector_find_by_fwnode(). > */ > void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, > - enum drm_connector_status status) > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > struct drm_connector *connector; > > diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c > index 35d9c3086990..7182a8e2e710 100644 > --- a/drivers/usb/typec/altmodes/displayport.c > +++ b/drivers/usb/typec/altmodes/displayport.c > @@ -189,7 +189,9 @@ static int dp_altmode_status_update(struct dp_altmode *dp) > } else { > drm_connector_oob_hotplug_event(dp->connector_fwnode, > hpd ? connector_status_connected : > - connector_status_disconnected); > + connector_status_disconnected, > + (hpd && irq_hpd) ? DRM_CONNECTOR_DP_IRQ_HPD : > + DRM_CONNECTOR_NO_EXTRA_STATUS); > dp->hpd = hpd; > sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); > if (hpd && irq_hpd) { > @@ -212,7 +214,10 @@ static int dp_altmode_configured(struct dp_altmode *dp) > */ > if (dp->pending_hpd) { > drm_connector_oob_hotplug_event(dp->connector_fwnode, > - connector_status_connected); > + connector_status_connected, > + dp->pending_irq_hpd ? > + DRM_CONNECTOR_DP_IRQ_HPD : > + DRM_CONNECTOR_NO_EXTRA_STATUS); > sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); > dp->pending_hpd = false; > if (dp->pending_irq_hpd) { > @@ -397,7 +402,8 @@ static int dp_altmode_vdm(struct typec_altmode *alt, > dp->data.conf = 0; > if (dp->hpd) { > drm_connector_oob_hotplug_event(dp->connector_fwnode, > - connector_status_disconnected); > + connector_status_disconnected, > + DRM_CONNECTOR_NO_EXTRA_STATUS); > dp->hpd = false; > sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); > } > @@ -827,7 +833,8 @@ void dp_altmode_remove(struct typec_altmode *alt) > > if (dp->connector_fwnode) { > drm_connector_oob_hotplug_event(dp->connector_fwnode, > - connector_status_disconnected); > + connector_status_disconnected, > + DRM_CONNECTOR_NO_EXTRA_STATUS); > > fwnode_handle_put(dp->connector_fwnode); > } > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h > index f83f28cae207..e05197e970d3 100644 > --- a/include/drm/drm_connector.h > +++ b/include/drm/drm_connector.h > @@ -91,6 +91,22 @@ enum drm_connector_status { > connector_status_unknown = 3, > }; > > +/** > + * enum drm_connector_status_extra - additional events sent by the sink / > + * display together or in replacement of the HPD status changes. > + */ > +enum drm_connector_status_extra { > + /** > + * @DRM_CONNECTOR_NO_EXTRA_STATUS: No additional status reported. > + */ > + DRM_CONNECTOR_NO_EXTRA_STATUS, > + /** > + * @DRM_CONNECTOR_DP_IRQ_HPD: DisplayPort Sink has sent the > + * IRQ_HPD (either by the HPD short pulse or via the AltMode event). > + */ > + DRM_CONNECTOR_DP_IRQ_HPD, > +}; > + > /** > * enum drm_connector_registration_state - userspace registration status for > * a &drm_connector > @@ -2521,7 +2537,8 @@ drm_connector_is_unregistered(struct drm_connector *connector) > } > > void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, > - enum drm_connector_status status); > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status); > const char *drm_get_connector_type_name(unsigned int connector_type); > const char *drm_get_connector_status_name(enum drm_connector_status status); > const char *drm_get_subpixel_order_name(enum subpixel_order order); > > -- > 2.47.3 > -- With best wishes Dmitry From linux.amoon at gmail.com Thu May 21 00:34:10 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Thu, 21 May 2026 13:04:10 +0530 Subject: [PATCH v4 0/3] media: meson: Fix memory leak in error path in Message-ID: <20260521073449.10057-1-linux.amoon@gmail.com> Following chamges try to fix the memory leak reported by Sashiko Pre-existing issues: - [Critical] The `sess->esparser_queue_work` work item is not canceled before freeing the session context, leading to a potential Use-After-Free vulnerability. - [High] The patch attempts to fix a memory leak reported by kmemleak, but misdiagnoses the root cause and leaves the primary memory leak (the V4L2 control handler) unresolved. - [High] The driver does not verify if `kthread_run()` returns an `ERR_PTR`, leading to a kernel panic when `kthread_stop()` is called. Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t Thanks -Anand Anand Moon (3): media: meson: vdec: Fix memory leak in error path of vdec_open media: meson: vdec: Add error handling for recycle thread creation media: meson: vdec: Cancel esparser work in error and stop paths drivers/staging/media/meson/vdec/vdec.c | 27 +++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) base-commit: 8bc67e4db64aa72732c474b44ea8622062c903f0 -- 2.50.1 From linux.amoon at gmail.com Thu May 21 00:34:11 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Thu, 21 May 2026 13:04:11 +0530 Subject: [PATCH v4 1/3] media: meson: vdec: Fix memory leak in error path of vdec_open In-Reply-To: <20260521073449.10057-1-linux.amoon@gmail.com> References: <20260521073449.10057-1-linux.amoon@gmail.com> Message-ID: <20260521073449.10057-2-linux.amoon@gmail.com> The vdec_open() function previously jumped directly to err_m2m_release when vdec_init_ctrls() failed, skipping release of the m2m context. This caused a resource leak. Fix it by introducing a proper err_m2m_ctx_release label that calls v4l2_m2m_ctx_release(sess->m2m_ctx) before releasing the m2m device. Also free the v4l2 control handler memory allocated by vdec_init_ctrls() in vdec_close(). This was identified via kmemleak: unreferenced object 0xffff0000205d6878 (size 8): comm "v4l_id", pid 5289, jiffies 4294938580 hex dump (first 8 bytes): 40 d2 49 18 00 00 ff ff @.I..... backtrace (crc d3204599): kmemleak_alloc+0xc8/0xf0 __kvmalloc_node_noprof+0x60c/0x850 v4l2_ctrl_handler_init_class+0x1b4/0x2e8 [videodev] vdec_open+0x1f4/0x788 [meson_vdec] v4l2_open+0x144/0x460 [videodev] chrdev_open+0x1ac/0x500 do_dentry_open+0x3f0/0xfe8 vfs_open+0x68/0x320 do_open+0x2d8/0x9a8 path_openat+0x1d0/0x4f0 do_filp_open+0x190/0x380 do_sys_openat2+0xf8/0x1b0 __arm64_sys_openat+0x13c/0x1e8 invoke_syscall+0xdc/0x268 el0_svc_common.constprop.0+0x178/0x258 do_el0_svc+0x4c/0x70 Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- v4: update the commit message to add v4l2_ctrl_handler_free() in vdec_close() to adderss the issue: This isn't a bug introduced by this patch, but does vdec_close() properly free the v4l2 control handler memory allocated by vdec_init_ctrls() here? v3: https://lore.kernel.org/all/20260520044046.7553-1-linux.amoon at gmail.com/ update the commit messagee. v2: https://lore.kernel.org/all/20260321065408.209723-1-linux.amoon at gmail.com/ updated the commit message, applied the suggestion from sashiko below. [3] https://sashiko.dev/#/patchset/20260321065408.209723-1-linux.amoon%40gmail.com v1: https://lore.kernel.org/all/20260304100557.126488-1-linux.amoon at gmail.com/ tried to address the issue reported by Nicolas improve the commit message. --- drivers/staging/media/meson/vdec/vdec.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 4b77ec1af5a7..9244fb09eb36 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -889,7 +889,7 @@ static int vdec_open(struct file *file) ret = vdec_init_ctrls(sess); if (ret) - goto err_m2m_release; + goto err_m2m_ctx_release; sess->pixfmt_cap = formats[0].pixfmts_cap[0]; sess->fmt_out = &formats[0]; @@ -913,6 +913,8 @@ static int vdec_open(struct file *file) return 0; +err_m2m_ctx_release: + v4l2_m2m_ctx_release(sess->m2m_ctx); err_m2m_release: v4l2_m2m_release(sess->m2m_dev); err_free_sess: @@ -926,6 +928,7 @@ static int vdec_close(struct file *file) v4l2_m2m_ctx_release(sess->m2m_ctx); v4l2_m2m_release(sess->m2m_dev); + v4l2_ctrl_handler_free(&sess->ctrl_handler); v4l2_fh_del(&sess->fh, file); v4l2_fh_exit(&sess->fh); -- 2.50.1 From linux.amoon at gmail.com Thu May 21 00:34:12 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Thu, 21 May 2026 13:04:12 +0530 Subject: [PATCH v4 2/3] media: meson: vdec: Add error handling for recycle thread creation In-Reply-To: <20260521073449.10057-1-linux.amoon@gmail.com> References: <20260521073449.10057-1-linux.amoon@gmail.com> Message-ID: <20260521073449.10057-3-linux.amoon@gmail.com> Add proper error handling for kthread_run() in vdec_start_streaming(). If thread creation fails and returns an ERR_PTR, record the error, reset sess->recycle_thread to NULL, and unwind resources via err_cleanup. This prevents later calls to kthread_stop() in vdec_stop_streaming() from dereferencing an ERR_PTR and causing a kernel panic. Fix this by adding the label and invoking vdec_poweroff() to prevent hardware power leaks. Additionally, reorder the error path to properly mirror the allocation sequence clear the streamon status flags before emptying the M2M buffers to avoid race conditions, and ensure DMA buffers are released gracefully relative to the hardware state lifecycle. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- v4: new patch [Severity: High] This isn't a bug introduced by this patch, but does the driver verify if kthread_run() returns an ERR_PTR when starting the recycle thread? If thread creation fails in vdec_start_streaming() and returns an ERR_PTR, could a later call to kthread_stop(sess->recycle_thread) in vdec_stop_streaming() attempt to dereference that ERR_PTR and cause a kernel panic? --- drivers/staging/media/meson/vdec/vdec.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 9244fb09eb36..8615a935e86d 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -337,29 +337,37 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) sess->sequence_cap = 0; sess->sequence_out = 0; - if (vdec_codec_needs_recycle(sess)) + if (vdec_codec_needs_recycle(sess)) { sess->recycle_thread = kthread_run(vdec_recycle_thread, sess, "vdec_recycle"); + if (IS_ERR(sess->recycle_thread)) { + ret = PTR_ERR(sess->recycle_thread); + sess->recycle_thread = NULL; + goto err_cleanup; + } + } sess->status = STATUS_INIT; core->cur_sess = sess; schedule_work(&sess->esparser_queue_work); return 0; +err_cleanup: + vdec_poweroff(sess); vififo_free: dma_free_coherent(sess->core->dev, sess->vififo_size, sess->vififo_vaddr, sess->vififo_paddr); bufs_done: - while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) - v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); - while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) - v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); - if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) sess->streamon_out = 0; else sess->streamon_cap = 0; + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); + return ret; } -- 2.50.1 From linux.amoon at gmail.com Thu May 21 00:34:13 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Thu, 21 May 2026 13:04:13 +0530 Subject: [PATCH v4 3/3] media: meson: vdec: Cancel esparser work in error and stop paths In-Reply-To: <20260521073449.10057-1-linux.amoon@gmail.com> References: <20260521073449.10057-1-linux.amoon@gmail.com> Message-ID: <20260521073449.10057-4-linux.amoon@gmail.com> Ensure that esparser_queue_work is canceled before freeing the session context. Add cancel_work_sync() in both the error path of vdec_close() and vdec_start_streaming() and in vdec_stop_streaming(). This prevents background work from dereferencing a freed sess structure and triggering a use-after-free. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- v4: new patch If vdec_close() calls kfree(sess) without first stopping or synchronizing with this background work via cancel_work_sync(), could a concurrently running esparser_queue_all_src() dereference the freed sess structure and trigger a use-after-free? --- drivers/staging/media/meson/vdec/vdec.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 8615a935e86d..a57bd4a8e33c 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -358,6 +358,8 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) dma_free_coherent(sess->core->dev, sess->vififo_size, sess->vififo_vaddr, sess->vififo_paddr); bufs_done: + cancel_work_sync(&sess->esparser_queue_work); + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) sess->streamon_out = 0; else @@ -415,6 +417,7 @@ static void vdec_stop_streaming(struct vb2_queue *q) if (vdec_codec_needs_recycle(sess)) kthread_stop(sess->recycle_thread); + cancel_work_sync(&sess->esparser_queue_work); vdec_poweroff(sess); vdec_free_canvas(sess); dma_free_coherent(sess->core->dev, sess->vififo_size, @@ -937,6 +940,7 @@ static int vdec_close(struct file *file) v4l2_m2m_ctx_release(sess->m2m_ctx); v4l2_m2m_release(sess->m2m_dev); v4l2_ctrl_handler_free(&sess->ctrl_handler); + cancel_work_sync(&sess->esparser_queue_work); v4l2_fh_del(&sess->fh, file); v4l2_fh_exit(&sess->fh); -- 2.50.1 From mripard at kernel.org Thu May 21 00:47:29 2026 From: mripard at kernel.org (Maxime Ripard) Date: Thu, 21 May 2026 09:47:29 +0200 Subject: [PATCH RESEND v3 1/6] drm/connector: report IRQ_HPD events to drm_connector_oob_hotplug_event() In-Reply-To: <20260513-hpd-irq-events-v3-1-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> <20260513-hpd-irq-events-v3-1-086857017f16@oss.qualcomm.com> Message-ID: <20260521-funny-astonishing-mackerel-cc5a01@penduick> On Wed, May 13, 2026 at 09:23:21PM +0300, Dmitry Baryshkov wrote: > The DisplayPort standard defines a special kind of events called IRQ. > These events are used to notify DP Source about the events on the Sink > side. It is extremely important for DP MST handling, where the MST > events are reported through this IRQ. > > In case of the USB-C DP AltMode there is no actual HPD pulse, but the > events are ported through the bits in the AltMode VDOs. > > Extend the drm_connector_oob_hotplug_event() interface and report IRQ > events to the DisplayPort Sink drivers. > > Signed-off-by: Dmitry Baryshkov > --- > drivers/gpu/drm/drm_connector.c | 5 ++++- > drivers/usb/typec/altmodes/displayport.c | 15 +++++++++++---- > include/drm/drm_connector.h | 19 ++++++++++++++++++- > 3 files changed, 33 insertions(+), 6 deletions(-) > > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c > index 47dc53c4a738..edee9daccd51 100644 > --- a/drivers/gpu/drm/drm_connector.c > +++ b/drivers/gpu/drm/drm_connector.c > @@ -3510,6 +3510,8 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) > * drm_connector_oob_hotplug_event - Report out-of-band hotplug event to connector > * @connector_fwnode: fwnode_handle to report the event on > * @status: hot plug detect logical state > + * @extra_status: additional information provided by the sink without changing > + * the HPD state (or in addition to such a change). > * > * On some hardware a hotplug event notification may come from outside the display > * driver / device. An example of this is some USB Type-C setups where the hardware > @@ -3520,7 +3522,8 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) > * a drm_connector reference through calling drm_connector_find_by_fwnode(). > */ > void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, > - enum drm_connector_status status) > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > struct drm_connector *connector; > > diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c > index 35d9c3086990..7182a8e2e710 100644 > --- a/drivers/usb/typec/altmodes/displayport.c > +++ b/drivers/usb/typec/altmodes/displayport.c > @@ -189,7 +189,9 @@ static int dp_altmode_status_update(struct dp_altmode *dp) > } else { > drm_connector_oob_hotplug_event(dp->connector_fwnode, > hpd ? connector_status_connected : > - connector_status_disconnected); > + connector_status_disconnected, > + (hpd && irq_hpd) ? DRM_CONNECTOR_DP_IRQ_HPD : > + DRM_CONNECTOR_NO_EXTRA_STATUS); Since the extra status itself, and what the options mean, are DP specific, do we really want to extend drm_connector_oob_hotplug_event()? I think I'd prefer to have a DP specific variant, with its own set of parameters. Maxime -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 273 bytes Desc: not available URL: From sashiko-bot at kernel.org Thu May 21 01:09:27 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Thu, 21 May 2026 08:09:27 +0000 Subject: [PATCH v4 1/3] media: meson: vdec: Fix memory leak in error path of vdec_open In-Reply-To: <20260521073449.10057-2-linux.amoon@gmail.com> References: <20260521073449.10057-2-linux.amoon@gmail.com> Message-ID: <20260521080928.26A3E1F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Incorrect teardown order in `vdec_close()` causes use-after-destroy of the V4L2 control handler's mutex. -- commit 82e916771a779b02500f97d58e344bb93ca8e9ca Author: Anand Moon media: meson: vdec: Fix memory leak in error path of vdec_open This commit introduces a proper err_m2m_ctx_release label in the error path of vdec_open() to ensure the m2m context is released before releasing the m2m device. It also attempts to free the V4L2 control handler memory in vdec_close() to fix a resource leak. Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c [ ... ] > @@ -926,6 +928,7 @@ static int vdec_close(struct file *file) > > v4l2_m2m_ctx_release(sess->m2m_ctx); > v4l2_m2m_release(sess->m2m_dev); > + v4l2_ctrl_handler_free(&sess->ctrl_handler); [Severity: High] Could this specific ordering cause a use-after-destroy of the V4L2 control handler's mutex? If an application has subscribed to a V4L2 control event, v4l2_fh_exit() unconditionally calls v4l2_event_unsubscribe_all(). This unsubscribe logic eventually invokes v4l2_ctrl_del_event(), which attempts to lock the control handler's mutex. Since v4l2_ctrl_handler_free() destroys the mutex: v4l2_ctrl_handler_free() mutex_destroy(&hdl->_lock); Calling it before v4l2_fh_exit() could lead to operating on a destroyed mutex and potentially cause a kernel panic if mutex debugging is enabled. Should v4l2_ctrl_handler_free() be called after v4l2_fh_del() and v4l2_fh_exit() instead? > v4l2_fh_del(&sess->fh, file); > v4l2_fh_exit(&sess->fh); > -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260521073449.10057-1-linux.amoon at gmail.com?part=1 From devnull+xianwei.zhao.amlogic.com at kernel.org Thu May 21 01:12:43 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Thu, 21 May 2026 08:12:43 +0000 Subject: [PATCH v8 1/3] dt-bindings: dma: Add Amlogic A9 SoC DMA In-Reply-To: <20260521-amlogic-dma-v8-0-86cc2ce94142@amlogic.com> References: <20260521-amlogic-dma-v8-0-86cc2ce94142@amlogic.com> Message-ID: <20260521-amlogic-dma-v8-1-86cc2ce94142@amlogic.com> From: Xianwei Zhao Add documentation describing the Amlogic A9 SoC DMA. And add the properties specific values defines into a new include file. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Xianwei Zhao --- .../devicetree/bindings/dma/amlogic,a9-dma.yaml | 65 ++++++++++++++++++++++ include/dt-bindings/dma/amlogic,a9-dma.h | 8 +++ 2 files changed, 73 insertions(+) diff --git a/Documentation/devicetree/bindings/dma/amlogic,a9-dma.yaml b/Documentation/devicetree/bindings/dma/amlogic,a9-dma.yaml new file mode 100644 index 000000000000..efd7b2602c33 --- /dev/null +++ b/Documentation/devicetree/bindings/dma/amlogic,a9-dma.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/dma/amlogic,a9-dma.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic general DMA controller + +description: + This is a general-purpose peripheral DMA controller. It currently supports + major peripherals including I2C, I3C, PIO, and CAN-BUS. Transmit and receive + for the same peripheral use two separate channels, controlled by different + register sets. I2C and I3C transfer data in 1-byte units, while PIO and + CAN-BUS transfer data in 4-byte units. From the controller?s perspective, + there is no significant difference. + +maintainers: + - Xianwei Zhao + +properties: + compatible: + const: amlogic,a9-dma + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + const: sys + + '#dma-cells': + const: 2 + + dma-channels: + maximum: 64 + +required: + - compatible + - reg + - interrupts + - clocks + - '#dma-cells' + - dma-channels + +allOf: + - $ref: dma-controller.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + dma-controller at fe400000{ + compatible = "amlogic,a9-dma"; + reg = <0xfe400000 0x4000>; + interrupts = ; + clocks = <&clkc 45>; + #dma-cells = <2>; + dma-channels = <28>; + }; diff --git a/include/dt-bindings/dma/amlogic,a9-dma.h b/include/dt-bindings/dma/amlogic,a9-dma.h new file mode 100644 index 000000000000..c59c2fd4b956 --- /dev/null +++ b/include/dt-bindings/dma/amlogic,a9-dma.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ + +#ifndef __DT_BINDINGS_DMA_AMLOGIC_DMA_H__ +#define __DT_BINDINGS_DMA_AMLOGIC_DMA_H__ + +#define DMA_TX 0 +#define DMA_RX 1 +#endif /* __DT_BINDINGS_DMA_AMLOGIC_DMA_H__ */ -- 2.52.0 From devnull+xianwei.zhao.amlogic.com at kernel.org Thu May 21 01:12:45 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Thu, 21 May 2026 08:12:45 +0000 Subject: [PATCH v8 3/3] MAINTAINERS: Add an entry for Amlogic DMA driver In-Reply-To: <20260521-amlogic-dma-v8-0-86cc2ce94142@amlogic.com> References: <20260521-amlogic-dma-v8-0-86cc2ce94142@amlogic.com> Message-ID: <20260521-amlogic-dma-v8-3-86cc2ce94142@amlogic.com> From: Xianwei Zhao Add Amlogic DMA controller entry to MAINTAINERS to clarify the maintainers. Signed-off-by: Xianwei Zhao --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index b38452804a2d..e7530b1e7152 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1316,6 +1316,13 @@ F: Documentation/devicetree/bindings/perf/amlogic,g12-ddr-pmu.yaml F: drivers/perf/amlogic/ F: include/soc/amlogic/ +AMLOGIC DMA DRIVER +M: Xianwei Zhao +L: linux-amlogic at lists.infradead.org +S: Maintained +F: Documentation/devicetree/bindings/dma/amlogic,a9-dma.yaml +F: drivers/dma/amlogic-dma.c + AMLOGIC ISP DRIVER M: Keke Li L: linux-media at vger.kernel.org -- 2.52.0 From devnull+xianwei.zhao.amlogic.com at kernel.org Thu May 21 01:12:42 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Thu, 21 May 2026 08:12:42 +0000 Subject: [PATCH v8 0/3] Add Amlogic general DMA Message-ID: <20260521-amlogic-dma-v8-0-86cc2ce94142@amlogic.com> Add DMA driver and bindigns for the Amlogic SoCs. Signed-off-by: Xianwei Zhao --- Changes in v8: - Use kzalloc instead of kmalloc. - Initialize the temporary variable and fix a spelling mistake. - Link to v7: https://lore.kernel.org/r/20260324-amlogic-dma-v7-0-f8b91ee192c1 at amlogic.com Changes in v7: - Take use vchan to support mltiple txns. - Link to v6: https://lore.kernel.org/r/20260309-amlogic-dma-v6-0-63349d23bd4b at amlogic.com Changes in v6: - Some minor modifications according to Frank's suggestion. - Link to v5: https://lore.kernel.org/r/20260304-amlogic-dma-v5-0-aa453d14fd43 at amlogic.com Changes in v5: - Rename head file and rename macro definition. - Rename the subject in [2/3] from "dma" to "dmaengine". - Link to v4: https://lore.kernel.org/r/20260227-amlogic-dma-v4-0-f25e4614e9b7 at amlogic.com Changes in v4: - Support split transfer when data len > MAX_LEN. - When a module fails or exits, perform de-initialization. - Some other minor modifications. - Link to v3: https://lore.kernel.org/r/20260206-amlogic-dma-v3-0-56fb9f59ed22 at amlogic.com Changes in v3: - Adjust the format of binding according to Frank's suggestion. - Some code format modified according to Frank's suggestion. - Support one prep_sg and one submit, drop multi prep_sg and one submit. - Keep pre state when resume from pause status. - Link to v2: https://lore.kernel.org/r/20260127-amlogic-dma-v2-0-4525d327d74d at amlogic.com Changes in v2: - Introduce what the DMA is used for in the A9 SoC. - Some minor modifications were made according to Krzysztof's suggestions. - Some modifications were made according to Neil's suggestions. - Fix a build error. - Link to v1: https://lore.kernel.org/r/20251216-amlogic-dma-v1-0-e289e57e96a7 at amlogic.com --- Xianwei Zhao (3): dt-bindings: dma: Add Amlogic A9 SoC DMA dmaengine: amlogic: Add general DMA driver for A9 MAINTAINERS: Add an entry for Amlogic DMA driver .../devicetree/bindings/dma/amlogic,a9-dma.yaml | 65 ++ MAINTAINERS | 7 + drivers/dma/Kconfig | 10 + drivers/dma/Makefile | 1 + drivers/dma/amlogic-dma.c | 682 +++++++++++++++++++++ include/dt-bindings/dma/amlogic,a9-dma.h | 8 + 6 files changed, 773 insertions(+) --- base-commit: 0b1f98df9cf024e9f1a43e0ef9c16d3466d17746 change-id: 20251215-amlogic-dma-79477d5cd264 Best regards, -- Xianwei Zhao From devnull+xianwei.zhao.amlogic.com at kernel.org Thu May 21 01:12:44 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Thu, 21 May 2026 08:12:44 +0000 Subject: [PATCH v8 2/3] dmaengine: amlogic: Add general DMA driver for A9 In-Reply-To: <20260521-amlogic-dma-v8-0-86cc2ce94142@amlogic.com> References: <20260521-amlogic-dma-v8-0-86cc2ce94142@amlogic.com> Message-ID: <20260521-amlogic-dma-v8-2-86cc2ce94142@amlogic.com> From: Xianwei Zhao Amlogic A9 SoCs include a general-purpose DMA controller that can be used by multiple peripherals, such as I2C PIO and I3C. Each peripheral group is associated with a dedicated DMA channel in hardware. Reviewed-by: Frank Li Signed-off-by: Xianwei Zhao --- drivers/dma/Kconfig | 10 + drivers/dma/Makefile | 1 + drivers/dma/amlogic-dma.c | 682 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 693 insertions(+) diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index e98e3e8c5036..400ae0f11f89 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -85,6 +85,16 @@ config AMCC_PPC440SPE_ADMA help Enable support for the AMCC PPC440SPe RAID engines. +config AMLOGIC_DMA + tristate "Amlogic general DMA support" + depends on ARCH_MESON || COMPILE_TEST + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + select REGMAP_MMIO + help + Enable support for the Amlogic general DMA engines. THis DMA + controller is used some Amlogic SoCs, such as A9. + config APPLE_ADMAC tristate "Apple ADMAC support" depends on ARCH_APPLE || COMPILE_TEST diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index df566c4958b6..8457383281a8 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_DMATEST) += dmatest.o obj-$(CONFIG_ALTERA_MSGDMA) += altera-msgdma.o obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/ +obj-$(CONFIG_AMLOGIC_DMA) += amlogic-dma.o obj-$(CONFIG_APPLE_ADMAC) += apple-admac.o obj-$(CONFIG_ARM_DMA350) += arm-dma350.o obj-$(CONFIG_AT_HDMAC) += at_hdmac.o diff --git a/drivers/dma/amlogic-dma.c b/drivers/dma/amlogic-dma.c new file mode 100644 index 000000000000..7dae5ba15c7e --- /dev/null +++ b/drivers/dma/amlogic-dma.c @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + * Author: Xianwei Zhao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "virt-dma.h" + +#define RCH_REG_BASE 0x0 +#define WCH_REG_BASE 0x2000 +/* + * Each rch (read from memory) REG offset Rch_offset 0x0 each channel total 0x40 + * rch addr = DMA_base + Rch_offset+ chan_id * 0x40 + reg_offset + */ +#define RCH_READY 0x0 +#define RCH_STATUS 0x4 +#define RCH_CFG 0x8 +#define CFG_CLEAR BIT(25) +#define CFG_PAUSE BIT(26) +#define CFG_ENABLE BIT(27) +#define CFG_DONE BIT(28) +#define RCH_ADDR 0xc +#define RCH_LEN 0x10 +#define RCH_RD_LEN 0x14 +#define RCH_PRT 0x18 +#define RCH_SYCN_STAT 0x1c +#define RCH_ADDR_LOW 0x20 +#define RCH_ADDR_HIGH 0x24 +/* if work on 64, it work with RCH_PRT */ +#define RCH_PTR_HIGH 0x28 + +/* + * Each wch (write to memory) REG offset Wch_offset 0x2000 each channel total 0x40 + * wch addr = DMA_base + Wch_offset+ chan_id * 0x40 + reg_offset + */ +#define WCH_READY 0x0 +#define WCH_TOTAL_LEN 0x4 +#define WCH_CFG 0x8 +#define WCH_ADDR 0xc +#define WCH_LEN 0x10 +#define WCH_RD_LEN 0x14 +#define WCH_PRT 0x18 +#define WCH_CMD_CNT 0x1c +#define WCH_ADDR_LOW 0x20 +#define WCH_ADDR_HIGH 0x24 +/* if work on 64, it work with RCH_PRT */ +#define WCH_PTR_HIGH 0x28 + +/* DMA controller reg */ +#define RCH_INT_MASK 0x1000 +#define WCH_INT_MASK 0x1004 +#define CLEAR_W_BATCH 0x1014 +#define CLEAR_RCH 0x1024 +#define CLEAR_WCH 0x1028 +#define RCH_ACTIVE 0x1038 +#define WCH_ACTIVE 0x103c +#define RCH_DONE 0x104c +#define WCH_DONE 0x1050 +#define RCH_ERR 0x1060 +#define RCH_LEN_ERR 0x1064 +#define WCH_ERR 0x1068 +#define DMA_BATCH_END 0x1078 +#define WCH_EOC_DONE 0x1088 +#define WDMA_RESP_ERR 0x1098 +#define UPT_PKT_SYNC 0x10a8 +#define RCHN_CFG 0x10ac +#define WCHN_CFG 0x10b0 +#define MEM_PD_CFG 0x10b4 +#define MEM_BUS_CFG 0x10b8 +#define DMA_GMV_CFG 0x10bc +#define DMA_GMR_CFG 0x10c0 + +#define MAX_CHAN_ID 32 +#define SG_MAX_LEN GENMASK(26, 0) + +struct aml_dma_sg_link { +#define LINK_LEN GENMASK(26, 0) +#define LINK_IRQ BIT(27) +#define LINK_EOC BIT(28) +#define LINK_LOOP BIT(29) +#define LINK_ERR BIT(30) +#define LINK_OWNER BIT(31) + u32 ctl; + u64 address; + u32 revered; +} __packed; + +/* 1 page for link 256*16 */ +#define DMA_MAX_LINK 256 +/* sizeof(struct aml_dma_sg_link) */ +#define DMA_LINK_SIZE 16 +#define DMA_LINK_MAX_SIZE (DMA_LINK_SIZE * DMA_MAX_LINK) + +struct aml_dma_desc { + struct virt_dma_desc vd; + int idx; + int data_len; +}; + +struct aml_dma_chan { + struct virt_dma_chan vchan; + struct aml_dma_dev *aml_dma; + struct aml_dma_desc *cur_desc; + struct aml_dma_sg_link *sg_link; + dma_addr_t sg_link_phys; + int idx_next; + enum dma_status pre_status; + enum dma_status status; + enum dma_transfer_direction direction; + int chan_id; + /* reg_base (direction + chan_id) */ + int reg_offs; +}; + +struct aml_dma_dev { + struct dma_device dma_device; + void __iomem *base; + struct regmap *regmap; + struct clk *clk; + int irq; + struct platform_device *pdev; + struct aml_dma_chan *aml_rch[MAX_CHAN_ID]; + struct aml_dma_chan *aml_wch[MAX_CHAN_ID]; + unsigned int chan_nr; + unsigned int chan_used; + struct aml_dma_chan aml_chans[]__counted_by(chan_nr); +}; + +static inline struct aml_dma_chan *to_aml_dma_chan(struct dma_chan *chan) +{ + return container_of(chan, struct aml_dma_chan, vchan.chan); +} + +static inline struct aml_dma_desc *to_aml_dma_desc(struct virt_dma_desc *vd) +{ + return container_of(vd, struct aml_dma_desc, vd); +} + +static void aml_dma_free_desc(struct virt_dma_desc *vd) +{ + struct aml_dma_desc *aml_desc = to_aml_dma_desc(vd); + + kfree(aml_desc); +} + +static int aml_dma_alloc_chan_resources(struct dma_chan *chan) +{ + struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); + struct aml_dma_dev *aml_dma = aml_chan->aml_dma; + + /* offset is the same RCH_CFG and WCH_CFG */ + regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_CLEAR); + regmap_clear_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); + regmap_clear_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_CLEAR); + aml_chan->sg_link = dma_alloc_coherent(aml_dma->dma_device.dev, DMA_LINK_MAX_SIZE, + &aml_chan->sg_link_phys, GFP_KERNEL); + if (!aml_chan->sg_link) + return -ENOMEM; + aml_chan->idx_next = 0; + aml_chan->status = DMA_COMPLETE; + aml_chan->cur_desc = NULL; + + return 0; +} + +static void aml_dma_free_chan_resources(struct dma_chan *chan) +{ + struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); + struct aml_dma_dev *aml_dma = aml_chan->aml_dma; + struct virt_dma_desc *cur_vd = NULL; + unsigned long flags; + + spin_lock_irqsave(&aml_chan->vchan.lock, flags); + regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); + regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_CLEAR); + if (aml_chan->cur_desc) + cur_vd = &aml_chan->cur_desc->vd; + aml_chan->cur_desc = NULL; + spin_unlock_irqrestore(&aml_chan->vchan.lock, flags); + if (cur_vd) + aml_dma_free_desc(cur_vd); + + vchan_free_chan_resources(&aml_chan->vchan); + + dma_free_coherent(aml_dma->dma_device.dev, + DMA_LINK_MAX_SIZE, aml_chan->sg_link, + aml_chan->sg_link_phys); +} + +/* DMA transfer state update how many data reside it */ +static enum dma_status aml_dma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, + struct dma_tx_state *txstate) +{ + struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); + struct aml_dma_dev *aml_dma = aml_chan->aml_dma; + struct aml_dma_desc *aml_desc = NULL; + struct virt_dma_desc *vd; + u32 residue = 0, done; + unsigned long flags; + enum dma_status ret; + + ret = dma_cookie_status(chan, cookie, txstate); + if (ret == DMA_COMPLETE) + return ret; + + if (aml_chan->status != DMA_COMPLETE) + ret = aml_chan->status; + if (!txstate) + return ret; + + spin_lock_irqsave(&aml_chan->vchan.lock, flags); + vd = vchan_find_desc(&aml_chan->vchan, cookie); + if (vd) { + list_for_each_entry(vd, &aml_chan->vchan.desc_issued, node) { + aml_desc = to_aml_dma_desc(vd); + residue += aml_desc->data_len; + if (vd->tx.cookie == cookie) + break; + } + aml_desc = aml_chan->cur_desc; + if (aml_desc) { + regmap_read(aml_dma->regmap, aml_chan->reg_offs + RCH_RD_LEN, &done); + residue += aml_desc->data_len - done; + } + } else if (aml_chan->cur_desc && aml_chan->cur_desc->vd.tx.cookie == cookie) { + aml_desc = aml_chan->cur_desc; + regmap_read(aml_dma->regmap, aml_chan->reg_offs + RCH_RD_LEN, &done); + residue = aml_desc->data_len - done; + + } else { + residue = 0; + } + spin_unlock_irqrestore(&aml_chan->vchan.lock, flags); + dma_set_residue(txstate, residue); + + return ret; +} + +static int find_dma_chan_link(struct aml_dma_chan *aml_chan, u32 num) +{ + int idx; + unsigned long flags; + + spin_lock_irqsave(&aml_chan->vchan.lock, flags); + if ((aml_chan->idx_next + num) >= DMA_MAX_LINK) + idx = 0; + else + idx = aml_chan->idx_next; + aml_chan->idx_next = idx + num; + spin_unlock_irqrestore(&aml_chan->vchan.lock, flags); + + return idx; +} + +static struct dma_async_tx_descriptor *aml_dma_prep_slave_sg + (struct dma_chan *chan, struct scatterlist *sgl, + unsigned int sg_len, enum dma_transfer_direction direction, + unsigned long flags, void *context) +{ + struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); + struct aml_dma_dev *aml_dma = aml_chan->aml_dma; + struct aml_dma_desc *aml_desc = NULL; + struct aml_dma_sg_link *sg_link = NULL; + struct scatterlist *sg = NULL; + u64 paddr; + u32 link_count, avail; + u32 i; + + if (aml_chan->direction != direction) { + dev_err(aml_dma->dma_device.dev, "direction not support\n"); + return NULL; + } + + aml_desc = kzalloc(sizeof(*aml_desc), GFP_KERNEL); + if (!aml_desc) + return NULL; + link_count = sg_nents_for_dma(sgl, sg_len, SG_MAX_LEN); + aml_desc->idx = find_dma_chan_link(aml_chan, link_count); + sg_link = aml_chan->sg_link + aml_desc->idx; + for_each_sg(sgl, sg, sg_len, i) { + avail = sg_dma_len(sg); + paddr = sg->dma_address; + while (avail > SG_MAX_LEN) { + /* set dma address and len to sglink*/ + sg_link->address = paddr; + sg_link->ctl = FIELD_PREP(LINK_LEN, SG_MAX_LEN); + paddr = paddr + SG_MAX_LEN; + avail = avail - SG_MAX_LEN; + sg_link++; + } + /* set dma address and len to sglink*/ + sg_link->address = paddr; + sg_link->ctl = FIELD_PREP(LINK_LEN, avail); + + aml_desc->data_len += sg_dma_len(sg); + sg_link++; + } + + /* the last sg set eoc flag */ + sg_link--; + sg_link->ctl |= LINK_EOC; + + return vchan_tx_prep(&aml_chan->vchan, &aml_desc->vd, flags); +} + +static int aml_dma_chan_pause(struct dma_chan *chan) +{ + struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); + struct aml_dma_dev *aml_dma = aml_chan->aml_dma; + + regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); + aml_chan->pre_status = aml_chan->status; + aml_chan->status = DMA_PAUSED; + + return 0; +} + +static int aml_dma_chan_resume(struct dma_chan *chan) +{ + struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); + struct aml_dma_dev *aml_dma = aml_chan->aml_dma; + + regmap_clear_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); + aml_chan->status = aml_chan->pre_status; + + return 0; +} + +static int aml_dma_terminate_all(struct dma_chan *chan) +{ + struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); + struct aml_dma_dev *aml_dma = aml_chan->aml_dma; + int chan_id = aml_chan->chan_id; + struct virt_dma_desc *cur_vd; + unsigned long flags; + LIST_HEAD(head); + + spin_lock_irqsave(&aml_chan->vchan.lock, flags); + regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); + regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_CLEAR); + + if (aml_chan->direction == DMA_MEM_TO_DEV) + regmap_set_bits(aml_dma->regmap, RCH_INT_MASK, BIT(chan_id)); + else if (aml_chan->direction == DMA_DEV_TO_MEM) + regmap_set_bits(aml_dma->regmap, WCH_INT_MASK, BIT(chan_id)); + + vchan_get_all_descriptors(&aml_chan->vchan, &head); + cur_vd = &aml_chan->cur_desc->vd; + aml_chan->cur_desc = NULL; + spin_unlock_irqrestore(&aml_chan->vchan.lock, flags); + if (cur_vd) + aml_dma_free_desc(cur_vd); + + vchan_dma_desc_free_list(&aml_chan->vchan, &head); + + return 0; +} + +static void aml_dma_start(struct aml_dma_chan *aml_chan) +{ + struct virt_dma_desc *vd = vchan_next_desc(&aml_chan->vchan); + struct aml_dma_dev *aml_dma = aml_chan->aml_dma; + struct aml_dma_desc *aml_desc = NULL; + int chan_id = aml_chan->chan_id; + + if (!vd) + return; + if (aml_chan->status != DMA_COMPLETE) + return; + + list_del(&vd->node); + aml_desc = to_aml_dma_desc(vd); + aml_chan->cur_desc = aml_desc; + + if (aml_chan->direction == DMA_MEM_TO_DEV) { + regmap_write(aml_dma->regmap, aml_chan->reg_offs + RCH_ADDR, + (aml_chan->sg_link_phys + aml_desc->idx * DMA_LINK_SIZE)); + regmap_write(aml_dma->regmap, aml_chan->reg_offs + RCH_LEN, aml_desc->data_len); + regmap_clear_bits(aml_dma->regmap, RCH_INT_MASK, BIT(chan_id)); + /* for rch (tx) need set cfg 0 to trigger start */ + regmap_write(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, 0); + } else if (aml_chan->direction == DMA_DEV_TO_MEM) { + regmap_write(aml_dma->regmap, aml_chan->reg_offs + WCH_ADDR, + (aml_chan->sg_link_phys + aml_desc->idx * DMA_LINK_SIZE)); + regmap_write(aml_dma->regmap, aml_chan->reg_offs + WCH_LEN, aml_desc->data_len); + regmap_clear_bits(aml_dma->regmap, WCH_INT_MASK, BIT(chan_id)); + } +} + +static void aml_dma_issue_pending(struct dma_chan *chan) +{ + struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); + unsigned long flags; + + spin_lock_irqsave(&aml_chan->vchan.lock, flags); + if (vchan_issue_pending(&aml_chan->vchan) && !aml_chan->cur_desc) + aml_dma_start(aml_chan); + spin_unlock_irqrestore(&aml_chan->vchan.lock, flags); +} + +static irqreturn_t aml_dma_interrupt_handler(int irq, void *dev_id) +{ + struct aml_dma_dev *aml_dma = dev_id; + struct aml_dma_chan *aml_chan; + struct aml_dma_desc *aml_desc; + u32 done, eoc_done, err, err_l, end; + u32 cpl_data; + int i = 0; + + /* deal with rch normal complete and error */ + regmap_read(aml_dma->regmap, RCH_DONE, &done); + regmap_read(aml_dma->regmap, RCH_ERR, &err); + regmap_read(aml_dma->regmap, RCH_LEN_ERR, &err_l); + err = err | err_l; + + done = done | err; + + while (done) { + i = ffs(done) - 1; + regmap_write(aml_dma->regmap, CLEAR_RCH, BIT(i)); + done &= ~BIT(i); + aml_chan = aml_dma->aml_rch[i]; + if (!aml_chan) { + dev_err(aml_dma->dma_device.dev, "idx %d rch not initialized\n", i); + continue; + } + aml_chan->status = (err & (1 << i)) ? DMA_ERROR : DMA_COMPLETE; + spin_lock(&aml_chan->vchan.lock); + aml_desc = aml_chan->cur_desc; + if (aml_chan->status == DMA_ERROR) { + aml_desc->vd.tx_result.result = DMA_TRANS_READ_FAILED; + regmap_read(aml_dma->regmap, aml_chan->reg_offs + RCH_RD_LEN, &cpl_data); + aml_desc->vd.tx_result.residue = aml_desc->data_len - cpl_data; + } + vchan_cookie_complete(&aml_desc->vd); + aml_chan->cur_desc = NULL; + aml_dma_start(aml_chan); + spin_unlock(&aml_chan->vchan.lock); + } + + /* deal with wch normal complete and error */ + regmap_read(aml_dma->regmap, DMA_BATCH_END, &end); + if (end) + regmap_write(aml_dma->regmap, CLEAR_W_BATCH, end); + + regmap_read(aml_dma->regmap, WCH_DONE, &done); + regmap_read(aml_dma->regmap, WCH_EOC_DONE, &eoc_done); + done = done | eoc_done; + + regmap_read(aml_dma->regmap, WCH_ERR, &err); + regmap_read(aml_dma->regmap, WDMA_RESP_ERR, &err_l); + err = err | err_l; + + done = done | err; + i = 0; + while (done) { + i = ffs(done) - 1; + done &= ~BIT(i); + regmap_write(aml_dma->regmap, CLEAR_WCH, BIT(i)); + aml_chan = aml_dma->aml_wch[i]; + if (!aml_chan) { + dev_err(aml_dma->dma_device.dev, "idx %d wch not initialized\n", i); + continue; + } + aml_chan->status = (err & (1 << i)) ? DMA_ERROR : DMA_COMPLETE; + spin_lock(&aml_chan->vchan.lock); + aml_desc = aml_chan->cur_desc; + if (aml_chan->status == DMA_ERROR) { + aml_desc->vd.tx_result.result = DMA_TRANS_WRITE_FAILED; + regmap_read(aml_dma->regmap, aml_chan->reg_offs + RCH_RD_LEN, &cpl_data); + aml_desc->vd.tx_result.residue = aml_desc->data_len - cpl_data; + } + vchan_cookie_complete(&aml_desc->vd); + aml_chan->cur_desc = NULL; + aml_dma_start(aml_chan); + spin_unlock(&aml_chan->vchan.lock); + } + + return IRQ_HANDLED; +} + +static struct dma_chan *aml_of_dma_xlate(struct of_phandle_args *dma_spec, struct of_dma *ofdma) +{ + struct aml_dma_dev *aml_dma = (struct aml_dma_dev *)ofdma->of_dma_data; + struct aml_dma_chan *aml_chan = NULL; + u32 type; + u32 phy_chan_id; + + if (dma_spec->args_count != 2) + return NULL; + + type = dma_spec->args[0]; + phy_chan_id = dma_spec->args[1]; + + if (phy_chan_id >= MAX_CHAN_ID) + return NULL; + + if (type == DMA_TX) { + aml_chan = aml_dma->aml_rch[phy_chan_id]; + if (!aml_chan) { + if (aml_dma->chan_used >= aml_dma->chan_nr) { + dev_err(aml_dma->dma_device.dev, "some dma clients err used\n"); + return NULL; + } + aml_chan = &aml_dma->aml_chans[aml_dma->chan_used]; + aml_dma->chan_used++; + aml_chan->direction = DMA_MEM_TO_DEV; + aml_chan->chan_id = phy_chan_id; + aml_chan->reg_offs = RCH_REG_BASE + 0x40 * aml_chan->chan_id; + aml_dma->aml_rch[phy_chan_id] = aml_chan; + } + } else if (type == DMA_RX) { + aml_chan = aml_dma->aml_wch[phy_chan_id]; + if (!aml_chan) { + if (aml_dma->chan_used >= aml_dma->chan_nr) { + dev_err(aml_dma->dma_device.dev, "some dma clients err used\n"); + return NULL; + } + aml_chan = &aml_dma->aml_chans[aml_dma->chan_used]; + aml_dma->chan_used++; + aml_chan->direction = DMA_DEV_TO_MEM; + aml_chan->chan_id = phy_chan_id; + aml_chan->reg_offs = WCH_REG_BASE + 0x40 * aml_chan->chan_id; + aml_dma->aml_wch[phy_chan_id] = aml_chan; + } + } else { + dev_err(aml_dma->dma_device.dev, "type %d not supported\n", type); + return NULL; + } + + return dma_get_slave_channel(&aml_chan->vchan.chan); +} + +static int aml_dma_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct dma_device *dma_dev; + struct aml_dma_dev *aml_dma; + int ret, i, len; + u32 chan_nr; + + const struct regmap_config aml_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x3000, + }; + + ret = of_property_read_u32(np, "dma-channels", &chan_nr); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to read dma-channels\n"); + + len = sizeof(struct aml_dma_dev) + sizeof(struct aml_dma_chan) * chan_nr; + aml_dma = devm_kzalloc(&pdev->dev, len, GFP_KERNEL); + if (!aml_dma) + return -ENOMEM; + + aml_dma->chan_nr = chan_nr; + + aml_dma->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(aml_dma->base)) + return PTR_ERR(aml_dma->base); + + aml_dma->regmap = devm_regmap_init_mmio(&pdev->dev, aml_dma->base, + &aml_regmap_config); + if (IS_ERR_OR_NULL(aml_dma->regmap)) + return PTR_ERR(aml_dma->regmap); + + aml_dma->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(aml_dma->clk)) + return PTR_ERR(aml_dma->clk); + + aml_dma->irq = platform_get_irq(pdev, 0); + + aml_dma->pdev = pdev; + aml_dma->dma_device.dev = &pdev->dev; + + dma_dev = &aml_dma->dma_device; + INIT_LIST_HEAD(&dma_dev->channels); + + /* Initialize channel parameters */ + for (i = 0; i < chan_nr; i++) { + struct aml_dma_chan *aml_chan = &aml_dma->aml_chans[i]; + + aml_chan->aml_dma = aml_dma; + aml_chan->vchan.desc_free = aml_dma_free_desc; + vchan_init(&aml_chan->vchan, &aml_dma->dma_device); + } + aml_dma->chan_used = 0; + + dma_set_max_seg_size(dma_dev->dev, SG_MAX_LEN); + dma_cap_set(DMA_SLAVE, dma_dev->cap_mask); + dma_dev->device_alloc_chan_resources = aml_dma_alloc_chan_resources; + dma_dev->device_free_chan_resources = aml_dma_free_chan_resources; + dma_dev->device_tx_status = aml_dma_tx_status; + dma_dev->device_prep_slave_sg = aml_dma_prep_slave_sg; + dma_dev->device_pause = aml_dma_chan_pause; + dma_dev->device_resume = aml_dma_chan_resume; + dma_dev->device_terminate_all = aml_dma_terminate_all; + dma_dev->device_issue_pending = aml_dma_issue_pending; + /* PIO 4 bytes and I2C 1 byte */ + dma_dev->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES) | BIT(DMA_SLAVE_BUSWIDTH_1_BYTE); + dma_dev->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); + dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; + + ret = dmaenginem_async_device_register(dma_dev); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to register dmaenginem\n"); + + ret = of_dma_controller_register(np, aml_of_dma_xlate, aml_dma); + if (ret) + goto err_dmaenginem_register; + + regmap_write(aml_dma->regmap, RCH_INT_MASK, 0xffffffff); + regmap_write(aml_dma->regmap, WCH_INT_MASK, 0xffffffff); + + ret = devm_request_irq(&pdev->dev, aml_dma->irq, aml_dma_interrupt_handler, + IRQF_SHARED, dev_name(&pdev->dev), aml_dma); + if (ret) { + dev_err(&pdev->dev, "failed to request_irq\n"); + goto err_of_dma_controller; + } + + platform_set_drvdata(pdev, aml_dma); + + return 0; + +err_of_dma_controller: + of_dma_controller_free(np); +err_dmaenginem_register: + dma_async_device_unregister(dma_dev); + + return ret; +} + +static void aml_dma_remove(struct platform_device *pdev) +{ + struct aml_dma_dev *aml_dma = platform_get_drvdata(pdev); + + of_dma_controller_free((&pdev->dev)->of_node); + dma_async_device_unregister(&aml_dma->dma_device); +} + +static const struct of_device_id aml_dma_ids[] = { + { .compatible = "amlogic,a9-dma", }, + {}, +}; +MODULE_DEVICE_TABLE(of, aml_dma_ids); + +static struct platform_driver aml_dma_driver = { + .probe = aml_dma_probe, + .remove = aml_dma_remove, + .driver = { + .name = "aml-dma", + .of_match_table = aml_dma_ids, + }, +}; + +module_platform_driver(aml_dma_driver); + +MODULE_DESCRIPTION("GENERAL DMA driver for Amlogic"); +MODULE_AUTHOR("Xianwei Zhao "); +MODULE_LICENSE("GPL"); -- 2.52.0 From sashiko-bot at kernel.org Thu May 21 01:21:29 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Thu, 21 May 2026 08:21:29 +0000 Subject: [PATCH v8 1/3] dt-bindings: dma: Add Amlogic A9 SoC DMA In-Reply-To: <20260521-amlogic-dma-v8-1-86cc2ce94142@amlogic.com> References: <20260521-amlogic-dma-v8-1-86cc2ce94142@amlogic.com> Message-ID: <20260521082129.9F3FE1F00A3B@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Low] The macros DMA_TX and DMA_RX are defined in a global header without any vendor or subsystem prefix. - [Low] The binding specifies `#dma-cells: const: 2` but fails to document the semantics of the two cells. -- commit 7a02c7e6145050221d77d7a4745ee4d4e651163b Author: Xianwei Zhao dt-bindings: dma: Add Amlogic A9 SoC DMA This commit adds device tree binding documentation for the Amlogic A9 SoC general-purpose DMA controller. It also introduces a new dt-bindings header file with property values for the DMA transfer directions. > diff --git a/Documentation/devicetree/bindings/dma/amlogic,a9-dma.yaml b/Documentation/devicetree/bindings/dma/amlogic,a9-dma.yaml > new file mode 100644 > index 0000000000000..efd7b2602c336 > --- /dev/null > +++ b/Documentation/devicetree/bindings/dma/amlogic,a9-dma.yaml [ ... ] > +properties: > + compatible: > + const: amlogic,a9-dma [ ... ] > + clock-names: > + const: sys > + > + '#dma-cells': > + const: 2 [Severity: Low] Should this include a description explaining what the two cells represent? Usually bindings with multiple cells document the semantics of each cell (e.g., first cell for the request line, second for transfer direction) so clients know how to construct their dmas specifier. [ ... ] > diff --git a/include/dt-bindings/dma/amlogic,a9-dma.h b/include/dt-bindings/dma/amlogic,a9-dma.h > new file mode 100644 > index 0000000000000..c59c2fd4b9561 > --- /dev/null > +++ b/include/dt-bindings/dma/amlogic,a9-dma.h > @@ -0,0 +1,8 @@ > +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */ > + > +#ifndef __DT_BINDINGS_DMA_AMLOGIC_DMA_H__ > +#define __DT_BINDINGS_DMA_AMLOGIC_DMA_H__ > + > +#define DMA_TX 0 > +#define DMA_RX 1 [Severity: Low] Would it be safer to add a vendor or device prefix to these macros? Since this header is globally accessible and can be included in both device tree sources and driver code, generic names like DMA_TX and DMA_RX might cause macro redefinition conflicts with other kernel components. Adding a prefix like AMLOGIC_A9_ would avoid this. > +#endif /* __DT_BINDINGS_DMA_AMLOGIC_DMA_H__ */ -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260521-amlogic-dma-v8-0-86cc2ce94142 at amlogic.com?part=1 From devnull+xianwei.zhao.amlogic.com at kernel.org Thu May 21 01:26:58 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Thu, 21 May 2026 08:26:58 +0000 Subject: [PATCH v3 1/2] dt-bindings: pwm: amlogic: Add new bindings for S6 S7 S7D In-Reply-To: <20260521-s6-s7-pwm-v3-0-57b073fbafef@amlogic.com> References: <20260521-s6-s7-pwm-v3-0-57b073fbafef@amlogic.com> Message-ID: <20260521-s6-s7-pwm-v3-1-57b073fbafef@amlogic.com> From: Junyi Zhao Amlogic S7/S7D/S6 different from the previous SoCs, a controller includes one pwm, at the same time, the controller has only one input clock source. Signed-off-by: Junyi Zhao Reviewed-by: Krzysztof Kozlowski Reviewed-by: Martin Blumenstingl Signed-off-by: Xianwei Zhao --- .../devicetree/bindings/pwm/pwm-amlogic.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml b/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml index c337d85da40f..93fa97f4011b 100644 --- a/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml +++ b/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml @@ -37,6 +37,7 @@ properties: - enum: - amlogic,meson8-pwm-v2 - amlogic,meson-s4-pwm + - amlogic,s7-pwm - items: - enum: - amlogic,a4-pwm @@ -45,6 +46,11 @@ properties: - amlogic,t7-pwm - amlogic,meson-a1-pwm - const: amlogic,meson-s4-pwm + - items: + - enum: + - amlogic,s6-pwm + - amlogic,s7d-pwm + - const: amlogic,s7-pwm - items: - enum: - amlogic,meson8b-pwm-v2 @@ -146,6 +152,19 @@ allOf: clock-names: false required: - clocks + - if: + properties: + compatible: + contains: + enum: + - amlogic,s7-pwm + then: + properties: + clocks: + maxItems: 1 + clock-names: false + required: + - clocks - if: properties: -- 2.52.0 From devnull+xianwei.zhao.amlogic.com at kernel.org Thu May 21 01:26:59 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Thu, 21 May 2026 08:26:59 +0000 Subject: [PATCH v3 2/2] pwm: meson: Add support for Amlogic S7 In-Reply-To: <20260521-s6-s7-pwm-v3-0-57b073fbafef@amlogic.com> References: <20260521-s6-s7-pwm-v3-0-57b073fbafef@amlogic.com> Message-ID: <20260521-s6-s7-pwm-v3-2-57b073fbafef@amlogic.com> From: Xianwei Zhao Add support for Amlogic S7 PWM. Amlogic S7 different from the previous SoCs, a controller includes one pwm, at the same time, the controller has only one input clock source. Signed-off-by: Xianwei Zhao --- drivers/pwm/pwm-meson.c | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index 8c6bf3d49753..66c41bf036de 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -113,6 +113,7 @@ struct meson_pwm_data { int (*channels_init)(struct pwm_chip *chip); bool has_constant; bool has_polarity; + u8 npwm; }; struct meson_pwm { @@ -503,6 +504,18 @@ static void meson_pwm_s4_put_clk(void *data) clk_put(clk); } +static int meson_pwm_init_channels_s7(struct pwm_chip *chip) +{ + struct device *dev = pwmchip_parent(chip); + struct meson_pwm *meson = to_meson_pwm(chip); + + meson->channels[0].clk = devm_clk_get(dev, NULL); + if (IS_ERR(meson->channels[0].clk)) + return dev_err_probe(dev, PTR_ERR(meson->channels[0].clk), + "Failed to get clk\n"); + return 0; +} + static int meson_pwm_init_channels_s4(struct pwm_chip *chip) { struct device *dev = pwmchip_parent(chip); @@ -530,6 +543,7 @@ static int meson_pwm_init_channels_s4(struct pwm_chip *chip) static const struct meson_pwm_data pwm_meson8b_data = { .parent_names = { "xtal", NULL, "fclk_div4", "fclk_div3" }, .channels_init = meson_pwm_init_channels_meson8b_legacy, + .npwm = MESON_NUM_PWMS, }; /* @@ -539,6 +553,7 @@ static const struct meson_pwm_data pwm_meson8b_data = { static const struct meson_pwm_data pwm_gxbb_ao_data = { .parent_names = { "xtal", "clk81", NULL, NULL }, .channels_init = meson_pwm_init_channels_meson8b_legacy, + .npwm = MESON_NUM_PWMS, }; static const struct meson_pwm_data pwm_axg_ee_data = { @@ -546,6 +561,7 @@ static const struct meson_pwm_data pwm_axg_ee_data = { .channels_init = meson_pwm_init_channels_meson8b_legacy, .has_constant = true, .has_polarity = true, + .npwm = MESON_NUM_PWMS, }; static const struct meson_pwm_data pwm_axg_ao_data = { @@ -553,6 +569,7 @@ static const struct meson_pwm_data pwm_axg_ao_data = { .channels_init = meson_pwm_init_channels_meson8b_legacy, .has_constant = true, .has_polarity = true, + .npwm = MESON_NUM_PWMS, }; static const struct meson_pwm_data pwm_g12a_ee_data = { @@ -560,6 +577,7 @@ static const struct meson_pwm_data pwm_g12a_ee_data = { .channels_init = meson_pwm_init_channels_meson8b_legacy, .has_constant = true, .has_polarity = true, + .npwm = MESON_NUM_PWMS, }; static const struct meson_pwm_data pwm_g12a_ao_ab_data = { @@ -567,6 +585,7 @@ static const struct meson_pwm_data pwm_g12a_ao_ab_data = { .channels_init = meson_pwm_init_channels_meson8b_legacy, .has_constant = true, .has_polarity = true, + .npwm = MESON_NUM_PWMS, }; static const struct meson_pwm_data pwm_g12a_ao_cd_data = { @@ -574,22 +593,33 @@ static const struct meson_pwm_data pwm_g12a_ao_cd_data = { .channels_init = meson_pwm_init_channels_meson8b_legacy, .has_constant = true, .has_polarity = true, + .npwm = MESON_NUM_PWMS, }; static const struct meson_pwm_data pwm_meson8_v2_data = { .channels_init = meson_pwm_init_channels_meson8b_v2, + .npwm = MESON_NUM_PWMS, }; static const struct meson_pwm_data pwm_meson_axg_v2_data = { .channels_init = meson_pwm_init_channels_meson8b_v2, .has_constant = true, .has_polarity = true, + .npwm = MESON_NUM_PWMS, }; static const struct meson_pwm_data pwm_s4_data = { .channels_init = meson_pwm_init_channels_s4, .has_constant = true, .has_polarity = true, + .npwm = MESON_NUM_PWMS, +}; + +static const struct meson_pwm_data pwm_s7_data = { + .channels_init = meson_pwm_init_channels_s7, + .has_constant = true, + .has_polarity = true, + .npwm = 1, }; static const struct of_device_id meson_pwm_matches[] = { @@ -642,7 +672,11 @@ static const struct of_device_id meson_pwm_matches[] = { .compatible = "amlogic,meson-s4-pwm", .data = &pwm_s4_data }, - {}, + { + .compatible = "amlogic,s7-pwm", + .data = &pwm_s7_data + }, + { } }; MODULE_DEVICE_TABLE(of, meson_pwm_matches); @@ -650,9 +684,10 @@ static int meson_pwm_probe(struct platform_device *pdev) { struct pwm_chip *chip; struct meson_pwm *meson; + const struct meson_pwm_data *pdata = of_device_get_match_data(&pdev->dev); int err; - chip = devm_pwmchip_alloc(&pdev->dev, MESON_NUM_PWMS, sizeof(*meson)); + chip = devm_pwmchip_alloc(&pdev->dev, pdata->npwm, sizeof(*meson)); if (IS_ERR(chip)) return PTR_ERR(chip); meson = to_meson_pwm(chip); @@ -664,7 +699,7 @@ static int meson_pwm_probe(struct platform_device *pdev) spin_lock_init(&meson->lock); chip->ops = &meson_pwm_ops; - meson->data = of_device_get_match_data(&pdev->dev); + meson->data = pdata; err = meson->data->channels_init(chip); if (err < 0) -- 2.52.0 From devnull+xianwei.zhao.amlogic.com at kernel.org Thu May 21 01:26:57 2026 From: devnull+xianwei.zhao.amlogic.com at kernel.org (Xianwei Zhao via B4 Relay) Date: Thu, 21 May 2026 08:26:57 +0000 Subject: [PATCH v3 0/2] Add PWM support Amlogic S7 S7D S6 Message-ID: <20260521-s6-s7-pwm-v3-0-57b073fbafef@amlogic.com> Add bindings and driver support Amlogic S7/S7D/S6 SoCs. Signed-off-by: Xianwei Zhao --- Changes in v3: - Use npwm instead of single_pwm in priv_data and format adjustments. - Link to v2: https://lore.kernel.org/r/20260402-s6-s7-pwm-v2-0-657dce040956 at amlogic.com Changes in v2: - Simpler s7 clock desc, and drop the example in bindings. - Make minor changes to probe based on Martin's suggestion. - Link to v1: https://lore.kernel.org/r/20260326-s6-s7-pwm-v1-0-67e2f72b98bc at amlogic.com --- Junyi Zhao (1): dt-bindings: pwm: amlogic: Add new bindings for S6 S7 S7D Xianwei Zhao (1): pwm: meson: Add support for Amlogic S7 .../devicetree/bindings/pwm/pwm-amlogic.yaml | 19 ++++++++++ drivers/pwm/pwm-meson.c | 41 ++++++++++++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) --- base-commit: b1385d0c7b7c633e55adcf4a7c1ef46c43a84a4a change-id: 20260325-s6-s7-pwm-281658b88736 Best regards, -- Xianwei Zhao From sashiko-bot at kernel.org Thu May 21 02:00:04 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Thu, 21 May 2026 09:00:04 +0000 Subject: [PATCH v8 2/3] dmaengine: amlogic: Add general DMA driver for A9 In-Reply-To: <20260521-amlogic-dma-v8-2-86cc2ce94142@amlogic.com> References: <20260521-amlogic-dma-v8-2-86cc2ce94142@amlogic.com> Message-ID: <20260521090005.377841F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 17 potential issue(s) to consider: - [Critical] The naive ring buffer allocator overflows on large scatterlists and silently overwrites active in-flight DMA descriptors. - [High] The DMA engine device is unregistered twice on driver removal and probe error paths. - [High] The DMA controller is exposed to clients before hardware and interrupts are fully initialized. - [High] Integer truncation in the calculation of the allocation length for `aml_dma_dev` leads to a heap buffer overflow. - [High] The driver allocates memory with `GFP_KERNEL` inside a DMA preparation callback, which can sleep in atomic context. - [High] The driver unconditionally dereferences `cur_desc` in the interrupt handler and `terminate_all`, leading to a kernel panic if the channel is idle. - [High] A single DMA transfer error permanently deadlocks the channel. - [High] Calling `aml_dma_prep_slave_sg` with an empty scatterlist results in a buffer underflow write. - [High] Mismatch between allocated links and written links for zero-length scatterlist elements. - [High] Missing `tasklet_kill()` during driver removal leads to Use-After-Free. - [High] For WCH (DEV_TO_MEM) channels, the hardware is never unpaused after `terminate_all()` or `pause()`. - [High] The 64-bit physical address of the DMA descriptor list is truncated to 32 bits, causing DMA faults. - [Medium] Incorrect and non-compliant descriptor residue calculation in `aml_dma_tx_status`. - [Medium] Signed integer overflow due to 32-bit shift in the interrupt handler. - [Medium] Concurrent modification of `aml_chan->status` without locking causes state corruption. - [Medium] The shared interrupt handler unconditionally returns `IRQ_HANDLED` even when it processes no events. - [Medium] Hardware descriptor fields use native endianness and unaligned 64-bit access. -- commit 6328a62df7a031573dae5a113f1f4a5b08fd7d50 Author: Xianwei Zhao Subject: dmaengine: amlogic: Add general DMA driver for A9 This patch adds a new DMA engine driver for the Amlogic A9 general-purpose DMA controller. It handles transfer operations using the virt-dma framework and registers the controller using the dmaengine API. > diff --git a/drivers/dma/amlogic-dma.c b/drivers/dma/amlogic-dma.c > new file mode 100644 > index 0000000000000..7dae5ba15c7e7 > --- /dev/null > +++ b/drivers/dma/amlogic-dma.c > @@ -0,0 +1,682 @@ [ ... ] > +struct aml_dma_sg_link { > +#define LINK_LEN GENMASK(26, 0) > +#define LINK_IRQ BIT(27) > +#define LINK_EOC BIT(28) > +#define LINK_LOOP BIT(29) > +#define LINK_ERR BIT(30) > +#define LINK_OWNER BIT(31) > + u32 ctl; > + u64 address; > + u32 revered; > +} __packed; [Severity: Medium] Will this __packed structure cause alignment faults on architectures sensitive to unaligned memory accesses? The 64-bit address field is forced to an unaligned 4-byte offset. Are the native types (u32, u64) safe here, or could they break DMA execution on big-endian kernels? [ ... ] > +static enum dma_status aml_dma_tx_status(struct dma_chan *chan, > + dma_cookie_t cookie, > + struct dma_tx_state *txstate) > +{ [ ... ] > + spin_lock_irqsave(&aml_chan->vchan.lock, flags); > + vd = vchan_find_desc(&aml_chan->vchan, cookie); > + if (vd) { > + list_for_each_entry(vd, &aml_chan->vchan.desc_issued, node) { > + aml_desc = to_aml_dma_desc(vd); > + residue += aml_desc->data_len; > + if (vd->tx.cookie == cookie) > + break; > + } [Severity: Medium] Does this incorrectly accumulate the data_len of independent descriptors? Furthermore, since list_for_each_entry() overwrites the loop cursor 'vd', could this traverse the entire desc_issued list without breaking if the target cookie resides in desc_submitted, thereby returning an incorrect total? [ ... ] > +static int find_dma_chan_link(struct aml_dma_chan *aml_chan, u32 num) > +{ > + int idx; > + unsigned long flags; > + > + spin_lock_irqsave(&aml_chan->vchan.lock, flags); > + if ((aml_chan->idx_next + num) >= DMA_MAX_LINK) > + idx = 0; > + else > + idx = aml_chan->idx_next; > + aml_chan->idx_next = idx + num; > + spin_unlock_irqrestore(&aml_chan->vchan.lock, flags); > + > + return idx; > +} [Severity: Critical] Is it possible for 'num' to be larger than DMA_MAX_LINK (256)? If a scatterlist needs more than 256 links, could idx_next become out of bounds and cause the descriptor preparation loop to write past the end of the sg_link array? Could this also silently overwrite active in-flight descriptors without checking if they are currently being processed? [ ... ] > +static struct dma_async_tx_descriptor *aml_dma_prep_slave_sg > + (struct dma_chan *chan, struct scatterlist *sgl, > + unsigned int sg_len, enum dma_transfer_direction direction, > + unsigned long flags, void *context) > +{ [ ... ] > + aml_desc = kzalloc(sizeof(*aml_desc), GFP_KERNEL); [Severity: High] Can this allocation sleep in atomic context? Dmaengine prep callbacks are expected to be callable from atomic contexts (e.g. inside interrupt handlers or while holding spinlocks). Should GFP_NOWAIT or GFP_ATOMIC be used instead? > + if (!aml_desc) > + return NULL; > + link_count = sg_nents_for_dma(sgl, sg_len, SG_MAX_LEN); > + aml_desc->idx = find_dma_chan_link(aml_chan, link_count); > + sg_link = aml_chan->sg_link + aml_desc->idx; > + for_each_sg(sgl, sg, sg_len, i) { > + avail = sg_dma_len(sg); > + paddr = sg->dma_address; > + while (avail > SG_MAX_LEN) { > + /* set dma address and len to sglink*/ > + sg_link->address = paddr; > + sg_link->ctl = FIELD_PREP(LINK_LEN, SG_MAX_LEN); > + paddr = paddr + SG_MAX_LEN; > + avail = avail - SG_MAX_LEN; > + sg_link++; > + } > + /* set dma address and len to sglink*/ > + sg_link->address = paddr; > + sg_link->ctl = FIELD_PREP(LINK_LEN, avail); > + > + aml_desc->data_len += sg_dma_len(sg); > + sg_link++; > + } [Severity: High] If a scatterlist element has a length of 0, sg_nents_for_dma() will not allocate a link slot for it. Will this loop unconditionally increment sg_link++ for that element anyway, causing the driver to consume more link slots than allocated and corrupt the ring buffer? > + > + /* the last sg set eoc flag */ > + sg_link--; > + sg_link->ctl |= LINK_EOC; [Severity: High] If aml_dma_prep_slave_sg() is called with sg_len == 0, the for_each_sg loop will not execute. Could this cause sg_link-- to point before the allocated descriptor index, leading to an out-of-bounds underflow write? [ ... ] > +static int aml_dma_chan_pause(struct dma_chan *chan) > +{ > + struct aml_dma_chan *aml_chan = to_aml_dma_chan(chan); > + struct aml_dma_dev *aml_dma = aml_chan->aml_dma; > + > + regmap_set_bits(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, CFG_PAUSE); > + aml_chan->pre_status = aml_chan->status; > + aml_chan->status = DMA_PAUSED; [Severity: Medium] Are these status updates subject to race conditions? aml_chan->status is modified here without acquiring aml_chan->vchan.lock, while it is also modified concurrently by the interrupt handler. [ ... ] > +static void aml_dma_start(struct aml_dma_chan *aml_chan) > +{ [ ... ] > + if (aml_chan->direction == DMA_MEM_TO_DEV) { > + regmap_write(aml_dma->regmap, aml_chan->reg_offs + RCH_ADDR, > + (aml_chan->sg_link_phys + aml_desc->idx * DMA_LINK_SIZE)); [Severity: High] Does passing a 64-bit dma_addr_t (sg_link_phys) to regmap_write() silently truncate the upper 32 bits of the address? If memory is allocated above 4GB, will this lead to incorrect memory accesses by the DMA controller? > + regmap_write(aml_dma->regmap, aml_chan->reg_offs + RCH_LEN, aml_desc->data_len); > + regmap_clear_bits(aml_dma->regmap, RCH_INT_MASK, BIT(chan_id)); > + /* for rch (tx) need set cfg 0 to trigger start */ > + regmap_write(aml_dma->regmap, aml_chan->reg_offs + RCH_CFG, 0); > + } else if (aml_chan->direction == DMA_DEV_TO_MEM) { > + regmap_write(aml_dma->regmap, aml_chan->reg_offs + WCH_ADDR, > + (aml_chan->sg_link_phys + aml_desc->idx * DMA_LINK_SIZE)); > + regmap_write(aml_dma->regmap, aml_chan->reg_offs + WCH_LEN, aml_desc->data_len); > + regmap_clear_bits(aml_dma->regmap, WCH_INT_MASK, BIT(chan_id)); > + } [Severity: High] For WCH (DEV_TO_MEM) channels, aml_dma_start() neglects to write to WCH_CFG. If aml_dma_terminate_all() or aml_dma_chan_pause() previously set the CFG_PAUSE bit, will WCH channels remain indefinitely paused? > +} [ ... ] > +static irqreturn_t aml_dma_interrupt_handler(int irq, void *dev_id) > +{ [ ... ] > + while (done) { > + i = ffs(done) - 1; > + regmap_write(aml_dma->regmap, CLEAR_RCH, BIT(i)); > + done &= ~BIT(i); > + aml_chan = aml_dma->aml_rch[i]; > + if (!aml_chan) { > + dev_err(aml_dma->dma_device.dev, "idx %d rch not initialized\n", i); > + continue; > + } > + aml_chan->status = (err & (1 << i)) ? DMA_ERROR : DMA_COMPLETE; [Severity: Medium] Could this shift result in a signed integer overflow? If 'i' reaches 31 (since MAX_CHAN_ID is 32), shifting the signed 32-bit literal '1' causes undefined behavior. Should this use 1U << i or BIT(i) instead? [Severity: High] If a hardware error occurs and aml_chan->status is set to DMA_ERROR, is there any mechanism to reset the status back to DMA_COMPLETE? Without a reset path, will aml_dma_start() simply return immediately for all future transfers, permanently deadlocking the channel? > + spin_lock(&aml_chan->vchan.lock); > + aml_desc = aml_chan->cur_desc; > + if (aml_chan->status == DMA_ERROR) { > + aml_desc->vd.tx_result.result = DMA_TRANS_READ_FAILED; > + regmap_read(aml_dma->regmap, aml_chan->reg_offs + RCH_RD_LEN, &cpl_data); > + aml_desc->vd.tx_result.residue = aml_desc->data_len - cpl_data; > + } [Severity: High] If a spurious interrupt fires, or if it fires concurrently with aml_dma_terminate_all() (which sets cur_desc to NULL), will aml_desc be NULL here? This appears to unconditionally dereference aml_desc, potentially causing a NULL pointer dereference. [ ... ] > + } > + > + return IRQ_HANDLED; > +} [Severity: Medium] Because this driver requests a shared interrupt line (IRQF_SHARED), shouldn't the handler return IRQ_NONE if it processed no events (e.g., done and end were both 0)? Returning IRQ_HANDLED unconditionally could break shared interrupt semantics and cause issues with the kernel's spurious interrupt detection. [ ... ] > +static int aml_dma_probe(struct platform_device *pdev) > +{ > + struct device_node *np = pdev->dev.of_node; > + struct dma_device *dma_dev; > + struct aml_dma_dev *aml_dma; > + int ret, i, len; > + u32 chan_nr; [ ... ] > + ret = of_property_read_u32(np, "dma-channels", &chan_nr); > + if (ret) > + return dev_err_probe(&pdev->dev, ret, "failed to read dma-channels\n"); > + > + len = sizeof(struct aml_dma_dev) + sizeof(struct aml_dma_chan) * chan_nr; > + aml_dma = devm_kzalloc(&pdev->dev, len, GFP_KERNEL); [Severity: High] Is the calculation of 'len' susceptible to an integer overflow? 'chan_nr' is read directly from the device tree and isn't bounds-checked before being used in the calculation of the signed integer 'len'. Could a large 'chan_nr' cause a heap buffer overflow later during initialization? [ ... ] > + ret = dmaenginem_async_device_register(dma_dev); > + if (ret) > + return dev_err_probe(&pdev->dev, ret, "failed to register dmaenginem\n"); > + > + ret = of_dma_controller_register(np, aml_of_dma_xlate, aml_dma); > + if (ret) > + goto err_dmaenginem_register; > + > + regmap_write(aml_dma->regmap, RCH_INT_MASK, 0xffffffff); > + regmap_write(aml_dma->regmap, WCH_INT_MASK, 0xffffffff); > + > + ret = devm_request_irq(&pdev->dev, aml_dma->irq, aml_dma_interrupt_handler, > + IRQF_SHARED, dev_name(&pdev->dev), aml_dma); [Severity: High] Does exposing the DMA controller to clients before the interrupt is requested leave a window where a client could queue a transfer and the hardware could generate an unhandled interrupt? Should the interrupt be initialized before dmaenginem_async_device_register() and of_dma_controller_register() are called? > + if (ret) { > + dev_err(&pdev->dev, "failed to request_irq\n"); > + goto err_of_dma_controller; > + } > + > + platform_set_drvdata(pdev, aml_dma); > + > + return 0; > + > +err_of_dma_controller: > + of_dma_controller_free(np); > +err_dmaenginem_register: > + dma_async_device_unregister(dma_dev); [Severity: High] Because dmaenginem_async_device_register() already registers a devres action to unregister the device upon failure, does calling dma_async_device_unregister() here manually result in a double-free or list corruption? > + > + return ret; > +} > + > +static void aml_dma_remove(struct platform_device *pdev) > +{ > + struct aml_dma_dev *aml_dma = platform_get_drvdata(pdev); > + > + of_dma_controller_free((&pdev->dev)->of_node); > + dma_async_device_unregister(&aml_dma->dma_device); [Severity: High] Similarly, does calling dma_async_device_unregister() here cause a double-free since dmaenginem_async_device_register() was used? [Severity: High] Is a call to tasklet_kill() needed here during removal? Since the virt-dma framework schedules tasklets for DMA completions, failing to kill them could lead to a use-after-free if a tasklet executes after the driver has been unloaded. > +} -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260521-amlogic-dma-v8-0-86cc2ce94142 at amlogic.com?part=2 From sashiko-bot at kernel.org Thu May 21 02:09:44 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Thu, 21 May 2026 09:09:44 +0000 Subject: [PATCH v4 2/3] media: meson: vdec: Add error handling for recycle thread creation In-Reply-To: <20260521073449.10057-3-linux.amoon@gmail.com> References: <20260521073449.10057-3-linux.amoon@gmail.com> Message-ID: <20260521090944.F35401F00A3D@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: New issues: - [High] The newly added error path in `vdec_start_streaming()` leaks `sess->priv` when `kthread_run()` fails. Pre-existing issues: - [Critical] Race condition between hardware power-on and `core->cur_sess` initialization leads to a NULL pointer dereference in the IRQ handler. - [High] Returning buffers for both source and destination queues upon single-queue failure orphans active queue buffers. - [High] Concurrent sessions can bypass the hardware exclusivity check, leading to simultaneous hardware programming. -- commit 6ac3ef9e0ba2b15ab6c09a7f5386537c4ce45927 Author: Anand Moon media: meson: vdec: Add error handling for recycle thread creation This commit adds error handling for kthread_run() in vdec_start_streaming() to prevent a kernel panic when thread creation fails. It unwinds resources and correctly reorders the error path to clear streamon flags before emptying M2M buffers. Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index 9244fb09eb36b..8615a935e86de 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c > @@ -337,29 +337,37 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) > > sess->sequence_cap = 0; > sess->sequence_out = 0; > - if (vdec_codec_needs_recycle(sess)) > + if (vdec_codec_needs_recycle(sess)) { > sess->recycle_thread = kthread_run(vdec_recycle_thread, sess, > "vdec_recycle"); > + if (IS_ERR(sess->recycle_thread)) { > + ret = PTR_ERR(sess->recycle_thread); > + sess->recycle_thread = NULL; > + goto err_cleanup; [Severity: High] Does this error path leak sess->priv? The vdec_poweron() function invoked earlier allocates dynamic memory for the codec context and assigns it to sess->priv. When kthread_run() fails, this new error path calls vdec_poweroff() which stops the hardware but doesn't free sess->priv. Because kthread_run() failing prevents sess->status from being updated to STATUS_INIT, any subsequent call to vdec_stop_streaming() will fail its status check and bypass the entire cleanup block, permanently leaking sess->priv and any associated DMA memory. > + } > + } > > sess->status = STATUS_INIT; > core->cur_sess = sess; [Severity: Critical] This is a pre-existing issue, but can an early hardware interrupt cause a NULL pointer dereference here? Because vdec_poweron() enables the decoding hardware and unmasks hardware interrupts before core->cur_sess is assigned, if a hardware interrupt triggers in between, the interrupt handler vdec_isr() will read a NULL core->cur_sess. It then executes sess->last_irq_jiffies = get_jiffies_64(), leading to a kernel panic. Adding the thread creation logic above widens this vulnerable window. [Severity: High] This is a pre-existing issue, but can concurrent sessions bypass the hardware exclusivity check and corrupt hardware state? The exclusivity check at the start of vdec_start_streaming(): if (core->cur_sess && core->cur_sess != sess) { is executed without holding the core-level lock (core->lock). Because the standard V4L2 queue lock (q->lock) is allocated per-session, two separate processes opening the device concurrently can enter vdec_start_streaming() at the same time, evaluate core->cur_sess == NULL as true, and concurrently invoke vdec_poweron(sess), triggering simultaneous writes to the same hardware registers. > schedule_work(&sess->esparser_queue_work); > return 0; > > +err_cleanup: > + vdec_poweroff(sess); > vififo_free: > dma_free_coherent(sess->core->dev, sess->vififo_size, > sess->vififo_vaddr, sess->vififo_paddr); > bufs_done: > - while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) > - v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); > - while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) > - v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); > - > if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) > sess->streamon_out = 0; > else > sess->streamon_cap = 0; > > + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) > + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); > + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) > + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); [Severity: High] This is a pre-existing issue, but will returning buffers for both queues upon a single-queue failure orphan active queue buffers? If vdec_start_streaming() fails (e.g., for the OUTPUT queue), the error path unconditionally removes all buffers from both the source (OUTPUT) and destination (CAPTURE) M2M queues and returns them with VB2_BUF_STATE_QUEUED. If the CAPTURE queue was successfully started in a previous call, returning its buffers puts them back into the vb2 queued list while the driver discards its references. Because the CAPTURE queue remains active, userspace calling DQBUF will hang indefinitely waiting for frames that the driver will never process. > + > return ret; > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260521073449.10057-1-linux.amoon at gmail.com?part=2 From heiko at sntech.de Thu May 21 02:14:07 2026 From: heiko at sntech.de (Heiko Stuebner) Date: Thu, 21 May 2026 11:14:07 +0200 Subject: [PATCH v7 00/23] drm: bridge: dw_hdmi: Misc enable/disable, CEC and EDID cleanup In-Reply-To: <20260518180206.2480119-1-jonas@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> Message-ID: <4998578.ktpJ11cQ8Q@phil> Am Montag, 18. Mai 2026, 20:01:36 Mitteleurop?ische Sommerzeit schrieb Jonas Karlman: > This is a revival of an old dw-hdmi series and is the first series part > of a new effort to upstream old LibreELEC HDMI 2.0 patches for Rockchip > RK33xx devices. > > This series ensure poweron/poweroff and CEC phys addr invalidation is > happening during normal DRM funcs, ensures EDID and CEC phys addr is > updated in detect() similar to how the bridge connector works with a > HDMI bridge attached, and also changes to debounce hotplug processing > to prevent a full disable/enable cycle during a HPD low voltage pulse. > > After this series HPD, EDID and CEC handling should work very similar > regardless is the dw-hdmi connector or the bridge connector is used. > It should also help ensure a smoother transition when dw-hdmi is fully > converted into a HDMI bridge in a future series. > > These changes have mainly been tested on Rockchip RK3328, RK3399 and > RK3568 devices using both the dw-hdmi connector and also using a basic > convert to use a bridge connector. The changes has also been tested on > Amlogic S905X, S905Y2 and A311D devices that uses the bridge connector. I now also gave this a spin on a rk3328 board and hdmi still workx :-) Tested-by: Heiko Stuebner #rk3328 From sashiko-bot at kernel.org Thu May 21 02:59:43 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Thu, 21 May 2026 09:59:43 +0000 Subject: [PATCH v4 3/3] media: meson: vdec: Cancel esparser work in error and stop paths In-Reply-To: <20260521073449.10057-4-linux.amoon@gmail.com> References: <20260521073449.10057-4-linux.amoon@gmail.com> Message-ID: <20260521095944.045141F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: New issues: - [Critical] AB-BA Deadlock in vdec_stop_streaming() and vdec_start_streaming(). - [High] Workqueue race condition and unexpected rescheduling in vdec_stop_streaming(). Pre-existing issues: - [Critical] Use-After-Free in vdec_close() due to incorrect work cancellation order. -- commit 68d67ae586f216ae4b5eab6d7931a8e345aebd15 Author: Anand Moon media: meson: vdec: Cancel esparser work in error and stop paths This commit attempts to fix a use-after-free issue by cancelling the esparser_queue_work before freeing the session context. It adds cancel_work_sync() calls in the error paths of vdec_start_streaming(), in vdec_stop_streaming(), and in vdec_close(). Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c > @@ -358,6 +358,8 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) > dma_free_coherent(sess->core->dev, sess->vififo_size, > sess->vififo_vaddr, sess->vififo_paddr); > bufs_done: > + cancel_work_sync(&sess->esparser_queue_work); > + [Severity: Critical] Does this cancel_work_sync() call introduce a deadlock? These are V4L2 queue callbacks, which are invoked by the V4L2 core with the queue lock held. Specifically, __video_do_ioctl acquires &sess->lock before calling the VIDIOC_STREAMOFF or VIDIOC_STREAMON ioctls. However, the worker thread esparser_queue_all_src() begins execution by attempting to acquire the exact same lock with mutex_lock(&sess->lock). If a user calls VIDIOC_STREAMOFF while the worker thread is executing, the ioctl thread will block indefinitely on cancel_work_sync() waiting for the worker to finish, while the worker is permanently blocked waiting for sess->lock held by the ioctl thread. > if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) > sess->streamon_out = 0; [ ... ] > @@ -415,6 +417,7 @@ static void vdec_stop_streaming(struct vb2_queue *q) > if (vdec_codec_needs_recycle(sess)) > kthread_stop(sess->recycle_thread); > > + cancel_work_sync(&sess->esparser_queue_work); > vdec_poweroff(sess); [Severity: High] Could this placement of cancel_work_sync() leave the background work pending after the stream is fully stopped? The call to vdec_poweroff(sess) waits for the hardware to become inactive. During this time, the hardware is still running and can complete a buffer, triggering a codec interrupt. The interrupt handler calls amvdec_dst_buf_done(), which explicitly calls schedule_work(&sess->esparser_queue_work). Since the work is scheduled after cancel_work_sync() has already returned, the work could be unexpectedly left pending. > vdec_free_canvas(sess); > dma_free_coherent(sess->core->dev, sess->vififo_size, [ ... ] > @@ -937,6 +940,7 @@ static int vdec_close(struct file *file) > v4l2_m2m_ctx_release(sess->m2m_ctx); > v4l2_m2m_release(sess->m2m_dev); > v4l2_ctrl_handler_free(&sess->ctrl_handler); > + cancel_work_sync(&sess->esparser_queue_work); [Severity: Critical] This isn't a bug introduced by this patch, but the patch's attempt to fix it is incomplete. Could placing cancel_work_sync() after v4l2_m2m_ctx_release() lead to a use-after-free? The function v4l2_m2m_ctx_release() explicitly frees sess->m2m_ctx. If the background work is rescheduled before this final cancellation (for example, due to the late hardware interrupt during vdec_stop_streaming() described above), the worker thread esparser_queue_all_src() can execute. The worker thread iterates over buffers using the freed context, which dereferences sess->m2m_ctx before cancel_work_sync() can stop it. Should the work be cancelled before releasing the context? > v4l2_fh_del(&sess->fh, file); > v4l2_fh_exit(&sess->fh); -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260521073449.10057-1-linux.amoon at gmail.com?part=3 From dmitry.baryshkov at oss.qualcomm.com Thu May 21 05:05:11 2026 From: dmitry.baryshkov at oss.qualcomm.com (Dmitry Baryshkov) Date: Thu, 21 May 2026 15:05:11 +0300 Subject: [PATCH RESEND v3 1/6] drm/connector: report IRQ_HPD events to drm_connector_oob_hotplug_event() In-Reply-To: <20260521-funny-astonishing-mackerel-cc5a01@penduick> References: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> <20260513-hpd-irq-events-v3-1-086857017f16@oss.qualcomm.com> <20260521-funny-astonishing-mackerel-cc5a01@penduick> Message-ID: On Thu, May 21, 2026 at 09:47:29AM +0200, Maxime Ripard wrote: > On Wed, May 13, 2026 at 09:23:21PM +0300, Dmitry Baryshkov wrote: > > The DisplayPort standard defines a special kind of events called IRQ. > > These events are used to notify DP Source about the events on the Sink > > side. It is extremely important for DP MST handling, where the MST > > events are reported through this IRQ. > > > > In case of the USB-C DP AltMode there is no actual HPD pulse, but the > > events are ported through the bits in the AltMode VDOs. > > > > Extend the drm_connector_oob_hotplug_event() interface and report IRQ > > events to the DisplayPort Sink drivers. > > > > Signed-off-by: Dmitry Baryshkov > > --- > > drivers/gpu/drm/drm_connector.c | 5 ++++- > > drivers/usb/typec/altmodes/displayport.c | 15 +++++++++++---- > > include/drm/drm_connector.h | 19 ++++++++++++++++++- > > 3 files changed, 33 insertions(+), 6 deletions(-) > > > > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c > > index 47dc53c4a738..edee9daccd51 100644 > > --- a/drivers/gpu/drm/drm_connector.c > > +++ b/drivers/gpu/drm/drm_connector.c > > @@ -3510,6 +3510,8 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) > > * drm_connector_oob_hotplug_event - Report out-of-band hotplug event to connector > > * @connector_fwnode: fwnode_handle to report the event on > > * @status: hot plug detect logical state > > + * @extra_status: additional information provided by the sink without changing > > + * the HPD state (or in addition to such a change). > > * > > * On some hardware a hotplug event notification may come from outside the display > > * driver / device. An example of this is some USB Type-C setups where the hardware > > @@ -3520,7 +3522,8 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) > > * a drm_connector reference through calling drm_connector_find_by_fwnode(). > > */ > > void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, > > - enum drm_connector_status status) > > + enum drm_connector_status status, > > + enum drm_connector_status_extra extra_status) > > { > > struct drm_connector *connector; > > > > diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c > > index 35d9c3086990..7182a8e2e710 100644 > > --- a/drivers/usb/typec/altmodes/displayport.c > > +++ b/drivers/usb/typec/altmodes/displayport.c > > @@ -189,7 +189,9 @@ static int dp_altmode_status_update(struct dp_altmode *dp) > > } else { > > drm_connector_oob_hotplug_event(dp->connector_fwnode, > > hpd ? connector_status_connected : > > - connector_status_disconnected); > > + connector_status_disconnected, > > + (hpd && irq_hpd) ? DRM_CONNECTOR_DP_IRQ_HPD : > > + DRM_CONNECTOR_NO_EXTRA_STATUS); > > Since the extra status itself, and what the options mean, are DP specific, do we really want to > extend drm_connector_oob_hotplug_event()? I think I'd prefer to have a DP specific variant, with its > own set of parameters. I can try arguing that drm_connector_oob_hotplug_event() is DP-specific, there are no other users for it, only the DP AltMode driver. Anyway, do you just mean new API here or new API and a new connector callback? -- With best wishes Dmitry From neil.armstrong at linaro.org Thu May 21 06:19:34 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Thu, 21 May 2026 15:19:34 +0200 Subject: [GIT PULL] amlogic ARM64 DT updates for v7.2 take 1 Message-ID: <16fc7412-c470-4f3f-b6c8-55c35698a424@linaro.org> Hi, Here's the Amlogic ARM64 DT changes for v7.2, contains improvements for the Khadas VIM4 and VIM1s SBCs, plus some additions for the Phicomm N1 and a couple of low priority fixes. This PR is largely the same as `amlogic-arm64-dt-for-v7.1`, but I sent the fixes separately as `amlogic-fixes-v7.1-rc` as discussed with Arnd, so this tag `amlogic-arm64-dt-for-v7.2-v1` is based on top of `amlogic-fixes-v7.1-rc`. Thanks, Neil The following changes since commit 174a0ef3b33434f475c87e66f37980e39b73805a: arm64: dts: meson-gxl-p230: fix ethernet PHY interrupt number (2026-04-21 15:46:29 +0200) are available in the Git repository at: https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git amlogic-arm64-dt-for-v7.2-v1 for you to fetch changes up to 43e6ece01ba673c3bd8dd1638bcd93827d254b3d: arm64: dts: amlogic: t7: Add i2c pinctrl node (2026-05-18 16:23:29 +0200) ---------------------------------------------------------------- Amlogic ARM64 DT for v7.2 take 1: - Khadas VIM4 (T7 SoC) features: - Memory layout fixup - GIC register range - Model name fixup - PWM, eMMC, SD card and SDIO support - PWM LED - I2C pinctrl node - Khadas VIM1s Features - Bluetooth - PWM LED - Power Key - Function Key via SARADC - RTC - Remote control keymap - Bluetooth node for Phicomm N1 ---------------------------------------------------------------- Jian Hu (1): arm64: dts: amlogic: t7: Add clock controller nodes Jun Yan (1): arm64: dts: amlogic: meson-gxl-s905d-phicomm-n1: add bluetooth node Nick Xie (9): arm64: dts: amlogic: meson-s4: add UART_A node arm64: dts: amlogic: meson-s4-s905y4-khadas-vim1s: enable bluetooth arm64: dts: amlogic: meson-s4-s905y4-khadas-vim1s: add PWM LED support arm64: dts: amlogic: meson-s4-s905y4-khadas-vim1s: add POWER key support arm64: dts: amlogic: meson-s4: add internal SARADC controller arm64: dts: amlogic: meson-s4-s905y4-khadas-vim1s: add Function key support arm64: dts: amlogic: meson-s4: add VRTC node arm64: dts: amlogic: meson-s4-s905y4-khadas-vim1s: enable HYM8563 RTC arm64: dts: amlogic: meson-s4-s905y4-khadas-vim1s: use rc-khadas keymap Ronald Claveau (12): arm64: dts: amlogic: t7: Add eMMC, SD card and SDIO pinctrl nodes arm64: dts: amlogic: t7: Add PWM pinctrl nodes arm64: dts: amlogic: t7: khadas-vim4: Add power regulators arm64: dts: amlogic: t7: khadas-vim4: Remove invalid property arm64: dts: amlogic: t7: Add MMC controller nodes arm64: dts: amlogic: t7: Add PWM controller nodes arm64: dts: amlogic: t7: khadas-vim4: Add SDIO power sequence and WiFi clock arm64: dts: amlogic: t7: khadas-vim4: Add MMC nodes arm64: dts: amlogic: t7: Fix missing required reset property arm64: dts: amlogic: t7: khadas-vim4: reorder root node arm64: dts: amlogic: t7: khadas-vim4: add PWM-driven status LED arm64: dts: amlogic: t7: Add i2c pinctrl node .../dts/amlogic/amlogic-t7-a311d2-khadas-vim4.dts | 215 +++++++++ arch/arm64/boot/dts/amlogic/amlogic-t7.dtsi | 482 +++++++++++++++++++++ .../dts/amlogic/meson-gxl-s905d-phicomm-n1.dts | 15 + .../dts/amlogic/meson-s4-s905y4-khadas-vim1s.dts | 81 ++++ arch/arm64/boot/dts/amlogic/meson-s4.dtsi | 45 ++ 5 files changed, 838 insertions(+) From vsetti at baylibre.com Wed May 20 09:05:18 2026 From: vsetti at baylibre.com (Valerio Setti) Date: Wed, 20 May 2026 18:05:18 +0200 Subject: [PATCH 1/4] ASoC: meson: gx: add gx-formatter and gx-interface In-Reply-To: <20260515153651.965F8C2BCB7@smtp.kernel.org> References: <20260515-reshape-aiu-as-axg-v1-1-53b457784ff3@baylibre.com> <20260515153651.965F8C2BCB7@smtp.kernel.org> Message-ID: > >> +int gx_stream_start(struct gx_stream *ts) >> +{ >> + struct gx_formatter *formatter; >> + int ret = 0; >> + >> + mutex_lock(&ts->lock); >> + >> + /* Start all the formatters attached to the stream */ >> + list_for_each_entry(formatter, &ts->formatter_list, list) { >> + ret = gx_formatter_enable(formatter); >> + if (ret) { >> + pr_err("failed to start tdm stream\n"); >> + goto out; > > Do we need to disable the formatters that were already successfully enabled > in previous iterations of this loop before returning? > > The ALSA core does not automatically invoke the STOP trigger when a START > trigger fails. Returning an error here without rolling back the earlier > formatters could leave the hardware clocks and registers permanently enabled, > resulting in leaked resources. > I don't mind adding this check, but I have a question for reviewers for sake of knowledge. I just checked and it seemes to me that actually when "soc_pcm_trigger()" fails to SNDRV_PCM_TRIGGER_START it rolls back to SNDRV_PCM_TRIGGER_STOP. Sure I'm missing something that Sashiko found, but I cannot figure out what. Any help on this? -- Valerio From vsetti at baylibre.com Thu May 21 07:59:12 2026 From: vsetti at baylibre.com (Valerio Setti) Date: Thu, 21 May 2026 16:59:12 +0200 Subject: [PATCH 2/4] ASoC: meson: aiu-encoder-i2s: use gx_iface and gx_stream structures In-Reply-To: <20260515161047.C0701C2BCF7@smtp.kernel.org> References: <20260515-reshape-aiu-as-axg-v1-2-53b457784ff3@baylibre.com> <20260515161047.C0701C2BCF7@smtp.kernel.org> Message-ID: >> static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream, >> struct snd_soc_dai *dai) >> { >> struct aiu *aiu = snd_soc_component_get_drvdata(dai->component); >> + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); >> + struct gx_iface *iface = ts->iface; >> + >> + if (!snd_soc_dai_active(dai)) >> + iface->rate = 0; > > The ASoC core invokes the shutdown callback before decrementing the stream's > active count. > > Because of this, does snd_soc_dai_active(dai) always evaluate to at least 1 > during the shutdown of the final stream? > > If so, iface->rate will never be reset to 0, which would cause subsequent > streams with different sample rates to permanently fail with -EINVAL until > the driver is reloaded. > I might be missing something also here, but I don't think this is correct. It seems to me that when 'aiu_encoder_i2s_shutdown' is called the number of active streams have already been decreased so everything works as expected. In support of my hypothesis I have tried this patch series together with the upcoming one adding also capture stream and I didn't experience problems, so I think that 'iface->date' is correctly reset when expected. >> +static int aiu_encoder_i2s_trigger(struct snd_pcm_substream *substream, >> + int cmd, >> + struct snd_soc_dai *dai) >> +{ >> + struct gx_stream *ts = snd_soc_dai_get_dma_data(dai, substream); >> + int ret; >> + >> + switch (cmd) { >> + case SNDRV_PCM_TRIGGER_START: >> + case SNDRV_PCM_TRIGGER_RESUME: >> + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: >> + ret = gx_stream_start(ts); >> + break; >> + case SNDRV_PCM_TRIGGER_SUSPEND: >> + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: >> + case SNDRV_PCM_TRIGGER_STOP: >> + gx_stream_stop(ts); >> + ret = 0; >> + break; > > The PCM trigger callback typically executes in an atomic context with the > PCM stream spinlock held and hardware interrupts disabled, unless the DAI > is explicitly marked with nonatomic = 1. > > Looking at gx_stream_start() and gx_stream_stop(), they both unconditionally > acquire a sleeping lock via mutex_lock(&ts->lock). > > Does acquiring a mutex in this atomic context trigger a scheduling while > atomic kernel panic? > This is one of the parts that has been copied from AXG. I would say that the problem highlighted here would only appear in case two formatters are started/stopped on same stream concurrently. If that's the case then this is not a problem for GX because we're going to have 1 single formatter per stream. -- Valerio From linux.amoon at gmail.com Thu May 21 10:42:59 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Thu, 21 May 2026 23:12:59 +0530 Subject: [PATCH v4 1/3] media: meson: vdec: Fix memory leak in error path of vdec_open In-Reply-To: <20260521080928.26A3E1F000E9@smtp.kernel.org> References: <20260521073449.10057-2-linux.amoon@gmail.com> <20260521080928.26A3E1F000E9@smtp.kernel.org> Message-ID: Hi Sashiko, Thanks for your review comment, On Thu, 21 May 2026 at 13:39, wrote: > > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: > - [High] Incorrect teardown order in `vdec_close()` causes use-after-destroy of the V4L2 control handler's mutex. > -- > > commit 82e916771a779b02500f97d58e344bb93ca8e9ca > Author: Anand Moon > > media: meson: vdec: Fix memory leak in error path of vdec_open > > This commit introduces a proper err_m2m_ctx_release label in the error path > of vdec_open() to ensure the m2m context is released before releasing the m2m > device. It also attempts to free the V4L2 control handler memory in > vdec_close() to fix a resource leak. > > Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t > > > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > > --- a/drivers/staging/media/meson/vdec/vdec.c > > +++ b/drivers/staging/media/meson/vdec/vdec.c > > [ ... ] > > > @@ -926,6 +928,7 @@ static int vdec_close(struct file *file) > > > > v4l2_m2m_ctx_release(sess->m2m_ctx); > > v4l2_m2m_release(sess->m2m_dev); > > + v4l2_ctrl_handler_free(&sess->ctrl_handler); > > [Severity: High] > Could this specific ordering cause a use-after-destroy of the V4L2 control > handler's mutex? > > If an application has subscribed to a V4L2 control event, v4l2_fh_exit() > unconditionally calls v4l2_event_unsubscribe_all(). This unsubscribe logic > eventually invokes v4l2_ctrl_del_event(), which attempts to lock the > control handler's mutex. > > Since v4l2_ctrl_handler_free() destroys the mutex: > > v4l2_ctrl_handler_free() > mutex_destroy(&hdl->_lock); > > Calling it before v4l2_fh_exit() could lead to operating on a destroyed mutex > and potentially cause a kernel panic if mutex debugging is enabled. > > Should v4l2_ctrl_handler_free() be called after v4l2_fh_del() and > v4l2_fh_exit() instead? > > > v4l2_fh_del(&sess->fh, file); > > v4l2_fh_exit(&sess->fh); > > I will try to study and update in the next version. > > -- > Sashiko AI review ? https://sashiko.dev/#/patchset/20260521073449.10057-1-linux.amoon at gmail.com?part=1 Thanks -Anand From jonas at kwiboo.se Thu May 21 13:13:15 2026 From: jonas at kwiboo.se (Jonas Karlman) Date: Thu, 21 May 2026 22:13:15 +0200 Subject: [PATCH v7 19/23] drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event In-Reply-To: References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-20-jonas@kwiboo.se> Message-ID: <310e7a75-62e2-463c-b471-fcaaa3acfef5@kwiboo.se> Hi Neil, On 5/20/2026 11:58 AM, Neil Armstrong wrote: > Hi, > > On 5/18/26 20:01, Jonas Karlman wrote: >> 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. > > The logic looks ok, but I'm puzzled by the 1.1 sec debounce, which after > plugging in a monitor will only send an irq event after 1.1s which is very long. > > Since the spec says 100ms and the real worls values are more like 200-300ms, > I would first reduce this to 500ms. You are correct, this value was picked based on existing values used by other drivers: exynos/exynos_hdmi.c:#define HOTPLUG_DEBOUNCE_MS 1100 bridge/ti-tfp410.c:#define HOTPLUG_DEBOUNCE_MS 1100 amd/display/amdgpu_dm/amdgpu_dm.h:#define AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS 5000 rockchip/dw_hdmi_qp-rockchip.c:#define HOTPLUG_DEBOUNCE_MS 150 150 ms was too short for my test monitor (Raspberry Pi Monitor), it does a HPD low voltage level pulse when powering on of around 200+ ms. [82.641903] dw_hdmi_hardirq: EVENT=plugout [82.841939] dw_hdmi_hardirq: EVENT=plugin And on my LG OLED 4K TV there was typically a pulse of around 700-900 ms. So the 1.1 seconds used by other drivers seemed like a good candidate :-) > But as I understand the code right now, on the first HPD front the irq work > is programmed to run after the debounce time, but if it's a pulse the irq would > also trigger on the second HPD front and then delay again the work after the > debounce time. Your analysis is correct, any HPD event would keep debouncing adding 1.1 timout from each HPD irq, both plugout and plugin. The intention was to allow for up to 1.1 seconds to pass between a plugout and a plugin, before we treated a plugout as a full disconnect event, and not to delay a possible connected event by 1.1 seconds. > My understanding of a debounce was that we "ignore" the pulse by only generating > a single irq event when the pulse is finished. Correct, as long as the pulse was less than 1.1 seconds. > The current code does that, we will only have a single irq event and the HPD > will return as connected state, good. But this delays the irq event 1.1s _after_ > the end of the pulse, which I would expect the event to be send at tht debounce > time after the start of the pulse. Good catch and I agree, we should delay/timeout the disconnected state longer than plugin and react on plugin almost immediately. > Like, program the work at the beginning of the pulse, if somehow the pulse ends before > the debounce time, send the irq event immediately, otherwise let the debounce > work run after the debounce time which will trigger a disconnect event. > > But the delay is too high, 1.1s could be a manual unplug/plug or bad connector > with false contact on the hpd pin. > > I would rather reduce this to something more realistic like 500ms or less and > try to better handle the pulse somehow. But I don't have any idea if the scheme > I described is doable. We can configure different delays for plugout and plugin, I am currently testing something like following: #define HOTPLUG_CONNECTED_DEBOUNCE_MS 150 #define HOTPLUG_DISCONNECTED_DEBOUNCE_MS 500 delay = status == connector_status_connected ? HOTPLUG_CONNECTED_DEBOUNCE_MS : HOTPLUG_DISCONNECTED_DEBOUNCE_MS; mod_delayed_work(system_percpu_wq, &hdmi->hpd_work, msecs_to_jiffies(delay)); That would mean: - at plugout we (re-)start the timer to trigger in 500ms - at plugin we (re-)start the timer to trigger in 150ms - whenever the timer triggers the hpd_work is started - the hpw_work trigger normal detect() handling to determin if connector status (or EDID) has changed Example during power on of a Raspberry Pi Monitor: [82.641903] dw_hdmi_hardirq: EVENT=plugout [82.648276] dw_hdmi_schedule_hpd_work(status=2) [82.841939] dw_hdmi_hardirq: EVENT=plugin [82.848211] dw_hdmi_schedule_hpd_work(status=1) [83.008573] dw_hdmi_hpd_work(): START [83.020358] dw_hdmi_bridge_detect() [83.034958] dw_hdmi_bridge_edid_read() [83.187102] [drm:update_display_info.part.0] [CONNECTOR:55:HDMI-A-1] CEA VCDB 0x4a [83.194471] [drm:update_display_info.part.0] [CONNECTOR:55:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz [83.201755] [drm:update_display_info.part.0] [CONNECTOR:55:HDMI-A-1] ELD monitor RPI MON156 [83.208968] [drm:update_display_info.part.0] [CONNECTOR:55:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 [83.216588] [drm:update_display_info.part.0] [CONNECTOR:55:HDMI-A-1] ELD size 36, SAD count 1 [83.224445] [drm:check_connector_changed] [CONNECTOR:55:HDMI-A-1] Same epoch counter 2 [83.231553] dw_hdmi_hpd_work(): END Around 205ms between HPD=0 and HPD=1 and around 160ms from plugin until delayed hpd_work starts. And at full plugout the debounce time is around 497 ms: [95.125105] dw_hdmi_hardirq: EVENT=plugout [95.147586] dw_hdmi_schedule_hpd_work(status=2) [95.645277] dw_hdmi_hpd_work(): START [95.667741] dw_hdmi_bridge_detect() [95.691523] [drm:drm_edid_connector_update] [CONNECTOR:55:HDMI-A-1] EDID changed, epoch counter 3 [95.709861] [drm:check_connector_changed] [CONNECTOR:55:HDMI-A-1] status updated from connected to disconnected [95.722674] [drm:check_connector_changed] [CONNECTOR:55:HDMI-A-1] Changed epoch counter 2 => 4 [95.735173] [drm:drm_sysfs_connector_hotplug_event] [CONNECTOR:55:HDMI-A-1] generating connector hotplug event [95.746326] [drm:drm_fb_helper_hotplug_event] [95.754871] [drm:drm_client_modeset_probe] [95.762429] [drm:drm_helper_probe_single_connector_modes] [CONNECTOR:55:HDMI-A-1] [95.769758] dw_hdmi_bridge_detect() [95.776597] [drm:drm_helper_probe_single_connector_modes] [CONNECTOR:55:HDMI-A-1] disconnected [95.784389] [drm:drm_client_modeset_probe] No connectors reported connected with modes [95.791865] [drm:drm_client_modeset_probe] [CONNECTOR:55:HDMI-A-1] enabled? no [95.799426] [drm:drm_client_firmware_config.isra.0] Not using firmware configuration [95.807140] [drm:drm_client_modeset_probe] picking CRTCs for 1920x1080 config [95.818422] dw_hdmi_bridge_atomic_disable() [95.826412] [drm:vop2_plane_atomic_disable] Smart0-win0 disable [95.868341] [drm:drm_client_hotplug] fbdev: ret=0 [95.878206] dw_hdmi_hpd_work(): END With a much quicker reaction on plugin event maybe the 1.1 seconds could stay to avoid having to do a full teardown, disable() + enable() cycle, at slightly longer HPD pulses. Or maybe it is time to re-spin an old cec-adapter debounce series [1] that can allow for some additional time until the CEC phys addr is fully invalidated at a longer HPD low voltage level pulse. [1] https://lore.kernel.org/linux-media/HE1PR06MB40115700084D1D875673D60EAC9D0 at HE1PR06MB4011.eurprd06.prod.outlook.com/ Regards, Jonas > > Neil > >> >> Signed-off-by: Jonas Karlman >> --- >> v7: Change to free irq before mute and clear using IH regs, also include >> clear of STAT0_RX_SENSE >> 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 | 80 +++++++++++++++++++++-- >> 1 file changed, 75 insertions(+), 5 deletions(-) >> >> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >> index 8afc9d240121..270db58a0e7c 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); >> @@ -3532,6 +3593,18 @@ 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); >> + >> + /* Free, mute and clear phy interrupts */ >> + devm_free_irq(hdmi->dev, irq, hdmi); >> + hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); >> + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, >> + HDMI_IH_PHY_STAT0); >> + >> + /* Cancel any pending hot plug work */ >> + cancel_delayed_work_sync(&hdmi->hpd_work); >> + >> drm_bridge_remove(&hdmi->bridge); >> >> if (hdmi->audio && !IS_ERR(hdmi->audio)) >> @@ -3539,9 +3612,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 > From andersson at kernel.org Thu May 21 16:12:52 2026 From: andersson at kernel.org (Bjorn Andersson) Date: Thu, 21 May 2026 18:12:52 -0500 Subject: [PATCH RESEND v3 2/6] drm/bridge: pass down IRQ_HPD to the drivers In-Reply-To: <20260513-hpd-irq-events-v3-2-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> <20260513-hpd-irq-events-v3-2-086857017f16@oss.qualcomm.com> Message-ID: On Wed, May 13, 2026 at 09:23:22PM +0300, Dmitry Baryshkov wrote: > Pass down the notifications about the IRQ_HPD events down to the > individual drivers, letting them handle those as required. > I think patch 2 through 6 relies on patch 1's commit message to establish the motivation for each change, but they are scattered across a variety of drivers/files. It would be preferable to have the commit message of each patch stand on its own. Regards, Bjorn > Signed-off-by: Dmitry Baryshkov > --- > drivers/gpu/drm/bridge/chrontel-ch7033.c | 3 ++- > drivers/gpu/drm/bridge/lontium-lt8912b.c | 3 ++- > drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 3 ++- > drivers/gpu/drm/bridge/ti-tfp410.c | 4 ++-- > drivers/gpu/drm/display/drm_bridge_connector.c | 22 +++++++++++++--------- > drivers/gpu/drm/drm_bridge.c | 5 +++-- > drivers/gpu/drm/drm_connector.c | 2 +- > drivers/gpu/drm/i915/display/intel_dp.c | 3 ++- > drivers/gpu/drm/meson/meson_encoder_hdmi.c | 3 ++- > drivers/gpu/drm/msm/dp/dp_display.c | 3 ++- > drivers/gpu/drm/msm/dp/dp_drm.h | 3 ++- > drivers/gpu/drm/omapdrm/dss/hdmi4.c | 3 ++- > include/drm/drm_bridge.h | 9 ++++++--- > include/drm/drm_connector.h | 3 ++- > 14 files changed, 43 insertions(+), 26 deletions(-) > > diff --git a/drivers/gpu/drm/bridge/chrontel-ch7033.c b/drivers/gpu/drm/bridge/chrontel-ch7033.c > index 54d49d4882c8..04e6b4c00a28 100644 > --- a/drivers/gpu/drm/bridge/chrontel-ch7033.c > +++ b/drivers/gpu/drm/bridge/chrontel-ch7033.c > @@ -259,7 +259,8 @@ static const struct drm_connector_helper_funcs ch7033_connector_helper_funcs = { > .best_encoder = ch7033_connector_best_encoder, > }; > > -static void ch7033_hpd_event(void *arg, enum drm_connector_status status) > +static void ch7033_hpd_event(void *arg, enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > struct ch7033_priv *priv = arg; > > diff --git a/drivers/gpu/drm/bridge/lontium-lt8912b.c b/drivers/gpu/drm/bridge/lontium-lt8912b.c > index 8a0b48efca58..b404f0cbf60d 100644 > --- a/drivers/gpu/drm/bridge/lontium-lt8912b.c > +++ b/drivers/gpu/drm/bridge/lontium-lt8912b.c > @@ -504,7 +504,8 @@ static int lt8912_attach_dsi(struct lt8912 *lt) > return 0; > } > > -static void lt8912_bridge_hpd_cb(void *data, enum drm_connector_status status) > +static void lt8912_bridge_hpd_cb(void *data, enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > struct lt8912 *lt = data; > > diff --git a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c > index 11aab07d88df..ca41ebe9f26f 100644 > --- a/drivers/gpu/drm/bridge/lontium-lt9611uxc.c > +++ b/drivers/gpu/drm/bridge/lontium-lt9611uxc.c > @@ -430,7 +430,8 @@ static const struct drm_edid *lt9611uxc_bridge_edid_read(struct drm_bridge *brid > > static void lt9611uxc_bridge_hpd_notify(struct drm_bridge *bridge, > struct drm_connector *connector, > - enum drm_connector_status status) > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > const struct drm_edid *drm_edid; > > diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c > index 3b6b0e92cf89..199916662895 100644 > --- a/drivers/gpu/drm/bridge/ti-tfp410.c > +++ b/drivers/gpu/drm/bridge/ti-tfp410.c > @@ -39,7 +39,6 @@ drm_bridge_to_tfp410(struct drm_bridge *bridge) > { > return container_of(bridge, struct tfp410, bridge); > } > - > static inline struct tfp410 * > drm_connector_to_tfp410(struct drm_connector *connector) > { > @@ -110,7 +109,8 @@ static void tfp410_hpd_work_func(struct work_struct *work) > drm_helper_hpd_irq_event(dvi->bridge.dev); > } > > -static void tfp410_hpd_callback(void *arg, enum drm_connector_status status) > +static void tfp410_hpd_callback(void *arg, enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > struct tfp410 *dvi = arg; > > diff --git a/drivers/gpu/drm/display/drm_bridge_connector.c b/drivers/gpu/drm/display/drm_bridge_connector.c > index 39cc18f78eda..5fdb1a231cec 100644 > --- a/drivers/gpu/drm/display/drm_bridge_connector.c > +++ b/drivers/gpu/drm/display/drm_bridge_connector.c > @@ -141,7 +141,8 @@ struct drm_bridge_connector { > */ > > static void drm_bridge_connector_hpd_notify(struct drm_connector *connector, > - enum drm_connector_status status) > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > struct drm_bridge_connector *bridge_connector = > to_drm_bridge_connector(connector); > @@ -149,12 +150,13 @@ static void drm_bridge_connector_hpd_notify(struct drm_connector *connector, > /* Notify all bridges in the pipeline of hotplug events. */ > drm_for_each_bridge_in_chain_scoped(bridge_connector->encoder, bridge) { > if (bridge->funcs->hpd_notify) > - bridge->funcs->hpd_notify(bridge, connector, status); > + bridge->funcs->hpd_notify(bridge, connector, status, extra_status); > } > } > > static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bridge_connector, > - enum drm_connector_status status) > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > struct drm_connector *connector = &drm_bridge_connector->base; > struct drm_device *dev = connector->dev; > @@ -163,24 +165,26 @@ static void drm_bridge_connector_handle_hpd(struct drm_bridge_connector *drm_bri > connector->status = status; > mutex_unlock(&dev->mode_config.mutex); > > - drm_bridge_connector_hpd_notify(connector, status); > + drm_bridge_connector_hpd_notify(connector, status, extra_status); > > drm_kms_helper_connector_hotplug_event(connector); > } > > static void drm_bridge_connector_hpd_cb(void *cb_data, > - enum drm_connector_status status) > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > - drm_bridge_connector_handle_hpd(cb_data, status); > + drm_bridge_connector_handle_hpd(cb_data, status, extra_status); > } > > static void drm_bridge_connector_oob_hotplug_event(struct drm_connector *connector, > - enum drm_connector_status status) > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > struct drm_bridge_connector *bridge_connector = > to_drm_bridge_connector(connector); > > - drm_bridge_connector_handle_hpd(bridge_connector, status); > + drm_bridge_connector_handle_hpd(bridge_connector, status, extra_status); > } > > static void drm_bridge_connector_enable_hpd(struct drm_connector *connector) > @@ -223,7 +227,7 @@ drm_bridge_connector_detect(struct drm_connector *connector, bool force) > if (hdmi) > drm_atomic_helper_connector_hdmi_hotplug(connector, status); > > - drm_bridge_connector_hpd_notify(connector, status); > + drm_bridge_connector_hpd_notify(connector, status, DRM_CONNECTOR_NO_EXTRA_STATUS); > } else { > switch (connector->connector_type) { > case DRM_MODE_CONNECTOR_DPI: > diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c > index d6f512b73389..c8c3301cd936 100644 > --- a/drivers/gpu/drm/drm_bridge.c > +++ b/drivers/gpu/drm/drm_bridge.c > @@ -1444,7 +1444,8 @@ EXPORT_SYMBOL_GPL(drm_bridge_edid_read); > */ > void drm_bridge_hpd_enable(struct drm_bridge *bridge, > void (*cb)(void *data, > - enum drm_connector_status status), > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status), > void *data) > { > if (!(bridge->ops & DRM_BRIDGE_OP_HPD)) > @@ -1509,7 +1510,7 @@ void drm_bridge_hpd_notify(struct drm_bridge *bridge, > { > mutex_lock(&bridge->hpd_mutex); > if (bridge->hpd_cb) > - bridge->hpd_cb(bridge->hpd_data, status); > + bridge->hpd_cb(bridge->hpd_data, status, DRM_CONNECTOR_NO_EXTRA_STATUS); > mutex_unlock(&bridge->hpd_mutex); > } > EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify); > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c > index edee9daccd51..415eb834808c 100644 > --- a/drivers/gpu/drm/drm_connector.c > +++ b/drivers/gpu/drm/drm_connector.c > @@ -3532,7 +3532,7 @@ void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, > return; > > if (connector->funcs->oob_hotplug_event) > - connector->funcs->oob_hotplug_event(connector, status); > + connector->funcs->oob_hotplug_event(connector, status, extra_status); > > drm_connector_put(connector); > } > diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c > index 4955bd8b11d7..98bbcab2067b 100644 > --- a/drivers/gpu/drm/i915/display/intel_dp.c > +++ b/drivers/gpu/drm/i915/display/intel_dp.c > @@ -6779,7 +6779,8 @@ static int intel_dp_connector_atomic_check(struct drm_connector *_connector, > } > > static void intel_dp_oob_hotplug_event(struct drm_connector *_connector, > - enum drm_connector_status hpd_state) > + enum drm_connector_status hpd_state, > + enum drm_connector_status_extra extra_status) > { > struct intel_connector *connector = to_intel_connector(_connector); > struct intel_display *display = to_intel_display(connector); > diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > index 1abb0572bb5f..691b9996c8a4 100644 > --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > @@ -323,7 +323,8 @@ static int meson_encoder_hdmi_atomic_check(struct drm_bridge *bridge, > > static void meson_encoder_hdmi_hpd_notify(struct drm_bridge *bridge, > struct drm_connector *connector, > - enum drm_connector_status status) > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > struct meson_encoder_hdmi *encoder_hdmi = bridge_to_meson_encoder_hdmi(bridge); > > diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c > index d2124d625485..7a0623fdbd8e 100644 > --- a/drivers/gpu/drm/msm/dp/dp_display.c > +++ b/drivers/gpu/drm/msm/dp/dp_display.c > @@ -1785,7 +1785,8 @@ void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge) > > void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, > struct drm_connector *connector, > - enum drm_connector_status status) > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge); > struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; > diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h > index 9eb3431dd93a..74da3ef6b625 100644 > --- a/drivers/gpu/drm/msm/dp/dp_drm.h > +++ b/drivers/gpu/drm/msm/dp/dp_drm.h > @@ -41,6 +41,7 @@ void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge); > void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge); > void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, > struct drm_connector *connector, > - enum drm_connector_status status); > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status); > > #endif /* _DP_DRM_H_ */ > diff --git a/drivers/gpu/drm/omapdrm/dss/hdmi4.c b/drivers/gpu/drm/omapdrm/dss/hdmi4.c > index 29b2dfb90b5f..a7288791b2a5 100644 > --- a/drivers/gpu/drm/omapdrm/dss/hdmi4.c > +++ b/drivers/gpu/drm/omapdrm/dss/hdmi4.c > @@ -429,7 +429,8 @@ static void hdmi4_bridge_disable(struct drm_bridge *bridge, > > static void hdmi4_bridge_hpd_notify(struct drm_bridge *bridge, > struct drm_connector *connector, > - enum drm_connector_status status) > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status) > { > struct omap_hdmi *hdmi = drm_bridge_to_hdmi(bridge); > > diff --git a/include/drm/drm_bridge.h b/include/drm/drm_bridge.h > index a8d67bd9ee50..3e4672fbd7a8 100644 > --- a/include/drm/drm_bridge.h > +++ b/include/drm/drm_bridge.h > @@ -615,7 +615,8 @@ struct drm_bridge_funcs { > */ > void (*hpd_notify)(struct drm_bridge *bridge, > struct drm_connector *connector, > - enum drm_connector_status status); > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status); > > /** > * @hpd_enable: > @@ -1260,7 +1261,8 @@ struct drm_bridge { > * @hpd_cb: Hot plug detection callback, registered with > * drm_bridge_hpd_enable(). > */ > - void (*hpd_cb)(void *data, enum drm_connector_status status); > + void (*hpd_cb)(void *data, enum drm_connector_status status, > + enum drm_connector_status_extra extra_status); > /** > * @hpd_data: Private data passed to the Hot plug detection callback > * @hpd_cb. > @@ -1550,7 +1552,8 @@ const struct drm_edid *drm_bridge_edid_read(struct drm_bridge *bridge, > struct drm_connector *connector); > void drm_bridge_hpd_enable(struct drm_bridge *bridge, > void (*cb)(void *data, > - enum drm_connector_status status), > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status), > void *data); > void drm_bridge_hpd_disable(struct drm_bridge *bridge); > void drm_bridge_hpd_notify(struct drm_bridge *bridge, > diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h > index e05197e970d3..5ac5a64f83d9 100644 > --- a/include/drm/drm_connector.h > +++ b/include/drm/drm_connector.h > @@ -1720,7 +1720,8 @@ struct drm_connector_funcs { > * has been received from a source outside the display driver / device. > */ > void (*oob_hotplug_event)(struct drm_connector *connector, > - enum drm_connector_status status); > + enum drm_connector_status status, > + enum drm_connector_status_extra extra_status); > > /** > * @debugfs_init: > > -- > 2.47.3 > From broonie at kernel.org Thu May 21 16:15:19 2026 From: broonie at kernel.org (Mark Brown) Date: Fri, 22 May 2026 00:15:19 +0100 Subject: [PATCH 4/4] ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data In-Reply-To: <20260515-reshape-aiu-as-axg-v1-4-53b457784ff3@baylibre.com> References: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> <20260515-reshape-aiu-as-axg-v1-4-53b457784ff3@baylibre.com> Message-ID: <758a4ef9-1a3e-475a-ae1e-83523330d006@sirena.org.uk> On Fri, May 15, 2026 at 05:10:40PM +0200, Valerio Setti wrote: > Create a new DAPM widget for "I2S formatter" and place it on the path > between FIFO and output DAI interface. Remove I2S output formatting code > from aiu-encoder-i2s since it's now implemented from aiu-formatter-i2s. This series, it looks like this specific patch, is breaking pcm-test on my libretech Le Potato board, the clocking looks to be seriously messed up. I'm getting: # selftests: alsa: pcm-test # TAP version 13 # # Card 0/LIBRETECHCC - LIBRETECH-CC (LIBRETECH-CC) # # LIBRETECHCC.0 - fe.dai-link-0 (*) # 1..7 # # default.time1.LIBRETECHCC.0.0.PLAYBACK - 8kHz mono large periods # ok 1 # SKIP default.time1.LIBRETECHCC.0.0.PLAYBACK # # snd_pcm_hw_params_set_channels 1: Invalid argument # # default.time2.LIBRETECHCC.0.0.PLAYBACK - 8kHz stereo large periods # # default.time2.LIBRETECHCC.0.0.PLAYBACK hw_params.RW_INTERLEAVED.S16_LE.8000.2.8000.32000 sw_params.32000 # not ok 2 default.time2.LIBRETECHCC.0.0.PLAYBACK # # time mismatch: expected 2000ms got 4425 # # default.time3.LIBRETECHCC.0.0.PLAYBACK - 44.1kHz stereo large periods # # default.time3.LIBRETECHCC.0.0.PLAYBACK hw_params.RW_INTERLEAVED.S16_LE.44100.2.22528.192000 sw_params.180224 # not ok 3 default.time3.LIBRETECHCC.0.0.PLAYBACK # # time mismatch: expected 2000ms got 4852 # # default.time4.LIBRETECHCC.0.0.PLAYBACK - 48kHz stereo small periods # # default.time4.LIBRETECHCC.0.0.PLAYBACK hw_params.RW_INTERLEAVED.S16_LE.48000.2.512.4096 sw_params.4096 # not ok 4 default.time4.LIBRETECHCC.0.0.PLAYBACK # # expected 48000, wrote 4096 # # default.time5.LIBRETECHCC.0.0.PLAYBACK - 48kHz stereo large periods # # default.time5.LIBRETECHCC.0.0.PLAYBACK hw_params.RW_INTERLEAVED.S16_LE.48000.2.24000.192000 sw_params.192000 # not ok 5 default.time5.LIBRETECHCC.0.0.PLAYBACK # # time mismatch: expected 2000ms got 4489 # # default.time6.LIBRETECHCC.0.0.PLAYBACK - 48kHz 6 channel large periods # # default.time6.LIBRETECHCC.0.0.PLAYBACK hw_params.RW_INTERLEAVED.S16_LE.48000.2.48000.262144 sw_params.240000 # not ok 6 default.time6.LIBRETECHCC.0.0.PLAYBACK # # time mismatch: expected 2000ms got 6135 # # default.time7.LIBRETECHCC.0.0.PLAYBACK - 96kHz stereo large periods # # default.time7.LIBRETECHCC.0.0.PLAYBACK hw_params.RW_INTERLEAVED.S16_LE.96000.2.48000.192000 sw_params.192000 # not ok 7 default.time7.LIBRETECHCC.0.0.PLAYBACK # # time mismatch: expected 2000ms got 2287 # # 1 skipped test(s) detected. Consider enabling relevant config options to improve coverage. # # Totals: pass:0 fail:6 xfail:0 xpass:0 skip:1 error:0 Full log: https://lava.sirena.org.uk/scheduler/job/2786342#L1934 The prior patches seem to test fine, it's this one that seems to introduce the issue. -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 488 bytes Desc: not available URL: From andersson at kernel.org Thu May 21 16:17:15 2026 From: andersson at kernel.org (Bjorn Andersson) Date: Thu, 21 May 2026 18:17:15 -0500 Subject: [PATCH RESEND v3 5/6] soc: qcom: pmic-glink-altmode: pass down HPD_IRQ events In-Reply-To: <20260513-hpd-irq-events-v3-5-086857017f16@oss.qualcomm.com> References: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> <20260513-hpd-irq-events-v3-5-086857017f16@oss.qualcomm.com> Message-ID: On Wed, May 13, 2026 at 09:23:25PM +0300, Dmitry Baryshkov wrote: > Pass IRQ_HPD events to the HPD bridge, letting those to be delivered to > the DisplayPort driver. > Acked-by: Bjorn Andersson Regards, Bjorn > Signed-off-by: Dmitry Baryshkov > --- > drivers/soc/qcom/pmic_glink_altmode.c | 6 +++++- > 1 file changed, 5 insertions(+), 1 deletion(-) > > diff --git a/drivers/soc/qcom/pmic_glink_altmode.c b/drivers/soc/qcom/pmic_glink_altmode.c > index 619bad2c27ee..946eb20b8f83 100644 > --- a/drivers/soc/qcom/pmic_glink_altmode.c > +++ b/drivers/soc/qcom/pmic_glink_altmode.c > @@ -373,7 +373,11 @@ static void pmic_glink_altmode_worker(struct work_struct *work) > else > conn_status = connector_status_disconnected; > > - drm_aux_hpd_bridge_notify(&alt_port->bridge->dev, conn_status); > + drm_aux_hpd_bridge_notify_extra(&alt_port->bridge->dev, > + conn_status, > + alt_port->hpd_irq ? > + DRM_CONNECTOR_DP_IRQ_HPD : > + DRM_CONNECTOR_NO_EXTRA_STATUS); > } else if (alt_port->mux_ctrl == MUX_CTRL_STATE_TUNNELING) { > if (alt_port->svid == USB_TYPEC_TBT_SID) > pmic_glink_altmode_enable_tbt(altmode, alt_port); > > -- > 2.47.3 > From jian.hu at amlogic.com Thu May 21 23:20:20 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Fri, 22 May 2026 14:20:20 +0800 Subject: [PATCH 02/10] dt-bindings: clock: Add Amlogic A9 PLL clock controller In-Reply-To: <20260515-subtle-sepia-tuatara-cfee3d@quoll> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-2-41cb4071b7c9@amlogic.com> <20260515-subtle-sepia-tuatara-cfee3d@quoll> Message-ID: <40e83bed-e7a0-4c66-806c-c2988c5d0f33@amlogic.com> Hi Krzysztof, Thanks for your review. On 5/15/2026 4:09 PM, Krzysztof Kozlowski wrote: > [ EXTERNAL EMAIL ] > > On Mon, May 11, 2026 at 08:47:24PM +0800, Jian Hu wrote: >> Add the PLL clock controller dt-bindings for the Amlogic A9 SoC family. >> >> Signed-off-by: Jian Hu >> --- >> .../bindings/clock/amlogic,a9-pll-clkc.yaml | 110 +++++++++++++++++++++ >> include/dt-bindings/clock/amlogic,a9-pll-clkc.h | 55 +++++++++++ >> 2 files changed, 165 insertions(+) >> >> diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml >> new file mode 100644 >> index 000000000000..4ee6013ba1a1 >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml >> @@ -0,0 +1,110 @@ >> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) >> +# Copyright (C) 2026 Amlogic, Inc. All rights reserved >> +%YAML 1.2 >> +--- >> +$id: http://devicetree.org/schemas/clock/amlogic,a9-pll-clkc.yaml# >> +$schema: http://devicetree.org/meta-schemas/core.yaml# >> + >> +title: Amlogic A9 Series PLL Clock Controller >> + >> +maintainers: >> + - Neil Armstrong >> + - Jerome Brunet >> + - Jian Hu >> + - Xianwei Zhao >> + >> +properties: >> + compatible: >> + enum: >> + - amlogic,a9-gp0-pll >> + - amlogic,a9-hifi0-pll >> + - amlogic,a9-hifi1-pll >> + - amlogic,a9-mclk0-pll >> + - amlogic,a9-mclk1-pll >> + >> + reg: >> + maxItems: 1 >> + >> + '#clock-cells': >> + const: 1 >> + >> + clocks: >> + items: >> + - description: pll input oscillator gate >> + - description: fixed input clock source for mclk_sel_0 >> + - description: u3p2pll input clock source for mclk_sel_0 (optional) > Second clock is also optional. Drop "(optional)" comment, just > confusing. GP0 has only one parent clock, while MCLK has three. The second and third parent entries of GP0 are vacant, so they need to be marked optional. I will add the optional property for the second clock in the next revision. >> + minItems: 1 >> + >> + clock-names: >> + items: >> + - const: in0 >> + - const: in1 >> + - const: in2 > Pretty pointless names, drop property. Ok, I will drop them. ? ?clock-names: -? ? items: -? ? ? - const: in0 -? ? ? - const: in1 -? ? ? - const: in2 ? ? ?minItems: 1 >> + minItems: 1 >> + >> +required: >> + - compatible >> + - '#clock-cells' >> + - reg >> + - clocks >> + - clock-names >> + >> +allOf: >> + - if: >> + properties: >> + compatible: >> + contains: >> + enum: >> + - amlogic,a9-mclk0-pll >> + - amlogic,a9-mclk1-pll >> + >> + then: >> + properties: >> + clocks: >> + maxItems: 3 > No, minItems instead. maxItems is already 3, so what is the point of > redefining it? Ok, I will use minItems instead. >> + >> + clock-names: >> + maxItems: 3 >> + >> + - if: >> + properties: >> + compatible: >> + contains: >> + enum: >> + - amlogic,a9-gp0-pll >> + - amlogic,a9-hifi0-pll >> + - amlogic,a9-hifi1-pll >> + >> + then: >> + properties: >> + clocks: >> + maxItems: 1 >> + >> + clock-names: >> + maxItems: 1 >> + >> +additionalProperties: false >> + >> +examples: >> + - | >> + apb4 { > soc Ok, I will rename it to soc. >> + #address-cells = <2>; >> + #size-cells = <2>; >> + >> + clock-controller at 8200 { >> + compatible = "amlogic,a9-gp0-pll"; >> + reg = <0x0 0x8200 0x0 0x20>; >> + #clock-cells = <1>; >> + clocks = <&scmi_clk 0>; >> + clock-names = "in0"; >> + }; >> + >> + clock-controller at 8330 { >> + compatible = "amlogic,a9-mclk0-pll"; >> + reg = <0x0 0x8330 0x0 0x14>; >> + #clock-cells = <1>; >> + clocks = <&scmi_clk 4>, >> + <&scmi_clk 8>; >> + clock-names = "in0", "in1"; > One example is enough, you have exactly the same properties. Ok, I will drop the second clock node. > > Best regards, > Krzysztof > Best regards, Jian From jian.hu at amlogic.com Fri May 22 00:49:28 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Fri, 22 May 2026 15:49:28 +0800 Subject: [PATCH 03/10] dt-bindings: clock: Add Amlogic A9 peripherals clock controller In-Reply-To: <20260515-augmented-cyber-puffin-4db20f@quoll> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-3-41cb4071b7c9@amlogic.com> <20260515-augmented-cyber-puffin-4db20f@quoll> Message-ID: <4779aae7-f47b-4cb0-b2cb-1e021fb0cd80@amlogic.com> On 5/15/2026 4:10 PM, Krzysztof Kozlowski wrote: > [ EXTERNAL EMAIL ] > > On Mon, May 11, 2026 at 08:47:25PM +0800, Jian Hu wrote: >> Add the peripherals clock controller dt-bindings for the Amlogic A9 >> SoC family. >> >> Signed-off-by: Jian Hu >> --- >> .../clock/amlogic,a9-peripherals-clkc.yaml | 150 +++++++++ >> .../clock/amlogic,a9-peripherals-clkc.h | 352 +++++++++++++++++++++ >> 2 files changed, 502 insertions(+) >> >> diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml >> new file mode 100644 >> index 000000000000..97e2c44d8630 >> --- /dev/null >> +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-peripherals-clkc.yaml >> @@ -0,0 +1,150 @@ >> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) >> +# Copyright (C) 2026 Amlogic, Inc. All rights reserved >> +%YAML 1.2 >> +--- >> +$id: http://devicetree.org/schemas/clock/amlogic,a9-peripherals-clkc.yaml# >> +$schema: http://devicetree.org/meta-schemas/core.yaml# >> + >> +title: Amlogic A9 Series Peripherals Clock Controller >> + >> +maintainers: >> + - Neil Armstrong >> + - Jerome Brunet >> + - Jian Hu >> + - Xianwei Zhao >> + >> +properties: >> + compatible: >> + const: amlogic,a9-peripherals-clkc >> + >> + reg: >> + maxItems: 1 >> + >> + '#clock-cells': >> + const: 1 >> + >> + clocks: >> + minItems: 20 > I don't think so. How they could be optional in silicon? How does > exactly work from silicon point of view? These are internal clocks from unimplemented analog blocks, and these clocks will be added in the future. Marking them as optional is indeed incorrect. Only the last external clock is actually optional. I will fix it in the next version. >> + items: >> + - description: input oscillator >> + - description: input fclk div 2 >> + - description: input fclk div 3 >> + - description: input fclk div 4 >> + - description: input fclk div 5 >> + - description: input fclk div 7 >> + - description: input fclk div 2p5 >> + - description: input sys clk >> + - description: input gp1 pll >> + - description: input gp2 pll >> + - description: input sys pll div 16 >> + - description: input cpu clk div 16 >> + - description: input a78 clk div 16 >> + - description: input dsu clk div 16 >> + - description: input rtc clk >> + - description: input gp0 pll >> + - description: input hifi0 pll >> + - description: input hifi1 pll >> + - description: input mclk0 pll >> + - description: input mclk1 pll >> + - description: input video1 pll (optional) >> + - description: input video2 pll (optional) >> + - description: input hdmi out2 clk (optional) >> + - description: input hdmi pixel clk (optional) >> + - description: input pixel0 pll (optional) >> + - description: input pixel1 pll (optional) >> + - description: input usb2 drd clk (optional) >> + - description: external input rmii oscillator (optional) >> + >> + clock-names: >> + minItems: 20 >> + items: >> + - const: xtal >> + - const: fdiv2 >> + - const: fdiv3 >> + - const: fdiv4 >> + - const: fdiv5 >> + - const: fdiv7 >> + - const: fdiv2p5 >> + - const: sys >> + - const: gp1 >> + - const: gp2 >> + - const: sysplldiv16 >> + - const: cpudiv16 >> + - const: a78div16 >> + - const: dsudiv16 >> + - const: rtc >> + - const: gp0 >> + - const: hifi0 >> + - const: hifi1 >> + - const: mclk0 >> + - const: mclk1 >> + - const: vid1 >> + - const: vid2 >> + - const: hdmiout2 >> + - const: hdmipix >> + - const: pix0 >> + - const: pix1 >> + - const: u2drd >> + - const: ext_rmii >> + >> +required: >> + - compatible >> + - reg >> + - '#clock-cells' >> + - clocks >> + - clock-names >> + >> +additionalProperties: false >> + >> +examples: >> + - | >> + apb4 { > Same comments as other patches. Do not come with your own style, but > adjust to mainline. Do you see this anywhere? > > git grep apb4 -- Documentation/devicetree/bindings/clock/ > > So why coming with something COMPLETELY different? > > Best regards, > Krzysztof Thanks for pointing this out. You are correct that there is no precedent for "apb4" in the mainline clock bindings. I should not have invented a new naming scheme here. I will rename this to use the standard "soc" naming that is consistent with all other similar bindings in the kernel tree. Furthermore, I will search the kernel to see if it exists when naming. This will be fixed in the next revision. From jian.hu at amlogic.com Fri May 22 01:14:00 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Fri, 22 May 2026 16:14:00 +0800 Subject: [PATCH 04/10] dt-bindings: clock: Add Amlogic A9 AO clock controller In-Reply-To: <20260515-resourceful-diligent-hound-b666e5@quoll> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-4-41cb4071b7c9@amlogic.com> <20260515-resourceful-diligent-hound-b666e5@quoll> Message-ID: <9c161508-5ba1-4988-b046-aa9ab668a080@amlogic.com> On 5/15/2026 4:10 PM, Krzysztof Kozlowski wrote: > [ EXTERNAL EMAIL ] > > On Mon, May 11, 2026 at 08:47:26PM +0800, Jian Hu wrote: >> Add the Always-On clock controller dt-bindings for the Amlogic A9 >> SoC family. >> >> Signed-off-by: Jian Hu >> --- >> .../bindings/clock/amlogic,a9-aoclkc.yaml | 76 ++++++++++++++++++++++ >> include/dt-bindings/clock/amlogic,a9-aoclkc.h | 76 ++++++++++++++++++++++ >> 2 files changed, 152 insertions(+) > All comments apply. > > Best regards, > Krzysztof > Ok, I will rename aobus to soc. Best regards, Jian From p.zabel at pengutronix.de Fri May 22 01:35:48 2026 From: p.zabel at pengutronix.de (Philipp Zabel) Date: Fri, 22 May 2026 10:35:48 +0200 Subject: [PATCH] ARM: meson: keep reset control around Message-ID: <20260522083548.2360352-1-p.zabel@pengutronix.de> Do not put the reset control, retain exclusive control over it, since After turning on a CPU, the corresponding reset line must stay deasserted. This also avoids calling reset_control_put() before workqueues are operational. Fixes: 78ebbff6d1a0 ("reset: handle removing supplier before consumers") Signed-off-by: Philipp Zabel --- This should fix the same issue as the one reported at [1] and fixed by [2] for rockchip. [1] https://lore.kernel.org/all/20260417154809.1984386-1-steven.price at arm.com/ [2] https://lore.kernel.org/all/20260521210915.2331176-1-heiko at sntech.de/ --- arch/arm/mach-meson/platsmp.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/arch/arm/mach-meson/platsmp.c b/arch/arm/mach-meson/platsmp.c index 32ac60b89fdc..5f18895104ed 100644 --- a/arch/arm/mach-meson/platsmp.c +++ b/arch/arm/mach-meson/platsmp.c @@ -34,6 +34,7 @@ static void __iomem *sram_base; static void __iomem *scu_base; static struct regmap *pmu; +static struct reset_control *cpu_rstc[4]; static struct reset_control *meson_smp_get_core_reset(int cpu) { @@ -62,6 +63,7 @@ static void __init meson_smp_prepare_cpus(const char *scu_compatible, const char *sram_compatible) { static struct device_node *node; + int cpu; /* SMP SRAM */ node = of_find_compatible_node(NULL, NULL, sram_compatible); @@ -99,6 +101,9 @@ static void __init meson_smp_prepare_cpus(const char *scu_compatible, } scu_enable(scu_base); + + for (cpu = 0; cpu < ARRAY_SIZE(cpu_rstc); cpu++) + cpu_rstc[cpu] = meson_smp_get_core_reset(cpu); } static void __init meson8b_smp_prepare_cpus(unsigned int max_cpus) @@ -155,10 +160,9 @@ static int meson_smp_finalize_secondary_boot(unsigned int cpu) static int meson8_smp_boot_secondary(unsigned int cpu, struct task_struct *idle) { - struct reset_control *rstc; + struct reset_control *rstc = cpu_rstc[cpu]; int ret; - rstc = meson_smp_get_core_reset(cpu); if (IS_ERR(rstc)) { pr_err("Couldn't get the reset controller for CPU%d\n", cpu); return PTR_ERR(rstc); @@ -203,19 +207,16 @@ static int meson8_smp_boot_secondary(unsigned int cpu, goto out; out: - reset_control_put(rstc); - return 0; } static int meson8b_smp_boot_secondary(unsigned int cpu, struct task_struct *idle) { - struct reset_control *rstc; + struct reset_control *rstc = cpu_rstc[cpu]; int ret; u32 val; - rstc = meson_smp_get_core_reset(cpu); if (IS_ERR(rstc)) { pr_err("Couldn't get the reset controller for CPU%d\n", cpu); return PTR_ERR(rstc); @@ -286,8 +287,6 @@ static int meson8b_smp_boot_secondary(unsigned int cpu, goto out; out: - reset_control_put(rstc); - return 0; } -- 2.47.3 From sashiko-bot at kernel.org Fri May 22 01:54:19 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Fri, 22 May 2026 08:54:19 +0000 Subject: [PATCH] ARM: meson: keep reset control around In-Reply-To: <20260522083548.2360352-1-p.zabel@pengutronix.de> References: <20260522083548.2360352-1-p.zabel@pengutronix.de> Message-ID: <20260522085419.D4E391F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Unguarded access to the fixed-size `cpu_rstc` array using the `cpu` index. - [High] Early returns in `meson_smp_prepare_cpus` leave `cpu_rstc` uninitialized, bypassing error checks during secondary boot. -- commit 3a78a84452dfa8eb8509282b8a25dfa2693e6e1a Author: Philipp Zabel ARM: meson: keep reset control around This commit retains exclusive control over the CPU reset line rather than releasing it. This is intended to keep the reset line deasserted after turning on a CPU and avoids calling reset_control_put before workqueues are operational. > diff --git a/arch/arm/mach-meson/platsmp.c b/arch/arm/mach-meson/platsmp.c > index 32ac60b89fdcc..5f18895104ed8 100644 > --- a/arch/arm/mach-meson/platsmp.c > +++ b/arch/arm/mach-meson/platsmp.c > @@ -34,6 +34,7 @@ > static void __iomem *sram_base; > static void __iomem *scu_base; > static struct regmap *pmu; > +static struct reset_control *cpu_rstc[4]; > > static struct reset_control *meson_smp_get_core_reset(int cpu) > { [ ... ] > @@ -99,6 +101,9 @@ static void __init meson_smp_prepare_cpus(const char *scu_compatible, > } > > scu_enable(scu_base); > + > + for (cpu = 0; cpu < ARRAY_SIZE(cpu_rstc); cpu++) > + cpu_rstc[cpu] = meson_smp_get_core_reset(cpu); > } [Severity: High] Does this leave the cpu_rstc array uninitialized if an earlier step fails? If a setup step like mapping the SCU fails earlier in this function, it logs an error and returns early without executing this loop. The cpu_rstc array will remain filled with NULL pointers. When smp_boot_secondary subsequently executes, it reads the uninitialized NULL pointer from the array. Since IS_ERR(NULL) evaluates to false, the error check is entirely bypassed. The subsequent reset control calls will act as silent no-ops, which could result in attempting to power on an unreset CPU instead of gracefully failing. [ ... ] > @@ -155,10 +160,9 @@ static int meson_smp_finalize_secondary_boot(unsigned int cpu) > static int meson8_smp_boot_secondary(unsigned int cpu, > struct task_struct *idle) > { > - struct reset_control *rstc; > + struct reset_control *rstc = cpu_rstc[cpu]; [Severity: High] Does this introduce a potential out-of-bounds read on the cpu_rstc array? The cpu_rstc array is statically allocated with a size of 4, but the cpu index is used directly here without bounds checking. If the system's device tree specifies more than 4 logical CPU nodes, nr_cpu_ids will exceed 4, and the boot code will pass an index that reads past the array bounds. The invalid pointer retrieved would then be dereferenced when asserting the reset control. > int ret; > > - rstc = meson_smp_get_core_reset(cpu); > if (IS_ERR(rstc)) { > pr_err("Couldn't get the reset controller for CPU%d\n", cpu); > return PTR_ERR(rstc); [ ... ] -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260522083548.2360352-1-p.zabel at pengutronix.de?part=1 From krzk at kernel.org Fri May 22 02:16:41 2026 From: krzk at kernel.org (Krzysztof Kozlowski) Date: Fri, 22 May 2026 11:16:41 +0200 Subject: [PATCH 02/10] dt-bindings: clock: Add Amlogic A9 PLL clock controller In-Reply-To: <40e83bed-e7a0-4c66-806c-c2988c5d0f33@amlogic.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-2-41cb4071b7c9@amlogic.com> <20260515-subtle-sepia-tuatara-cfee3d@quoll> <40e83bed-e7a0-4c66-806c-c2988c5d0f33@amlogic.com> Message-ID: <7c458070-a56a-4d49-89fc-efeb388beffc@kernel.org> On 22/05/2026 08:20, Jian Hu wrote: > Hi Krzysztof, > > Thanks for your review. > > On 5/15/2026 4:09 PM, Krzysztof Kozlowski wrote: >> [ EXTERNAL EMAIL ] >> >> On Mon, May 11, 2026 at 08:47:24PM +0800, Jian Hu wrote: >>> Add the PLL clock controller dt-bindings for the Amlogic A9 SoC family. >>> >>> Signed-off-by: Jian Hu >>> --- >>> .../bindings/clock/amlogic,a9-pll-clkc.yaml | 110 +++++++++++++++++++++ >>> include/dt-bindings/clock/amlogic,a9-pll-clkc.h | 55 +++++++++++ >>> 2 files changed, 165 insertions(+) >>> >>> diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml >>> new file mode 100644 >>> index 000000000000..4ee6013ba1a1 >>> --- /dev/null >>> +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml >>> @@ -0,0 +1,110 @@ >>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) >>> +# Copyright (C) 2026 Amlogic, Inc. All rights reserved >>> +%YAML 1.2 >>> +--- >>> +$id: http://devicetree.org/schemas/clock/amlogic,a9-pll-clkc.yaml# >>> +$schema: http://devicetree.org/meta-schemas/core.yaml# >>> + >>> +title: Amlogic A9 Series PLL Clock Controller >>> + >>> +maintainers: >>> + - Neil Armstrong >>> + - Jerome Brunet >>> + - Jian Hu >>> + - Xianwei Zhao >>> + >>> +properties: >>> + compatible: >>> + enum: >>> + - amlogic,a9-gp0-pll >>> + - amlogic,a9-hifi0-pll >>> + - amlogic,a9-hifi1-pll >>> + - amlogic,a9-mclk0-pll >>> + - amlogic,a9-mclk1-pll >>> + >>> + reg: >>> + maxItems: 1 >>> + >>> + '#clock-cells': >>> + const: 1 >>> + >>> + clocks: >>> + items: >>> + - description: pll input oscillator gate >>> + - description: fixed input clock source for mclk_sel_0 >>> + - description: u3p2pll input clock source for mclk_sel_0 (optional) >> Second clock is also optional. Drop "(optional)" comment, just >> confusing. > > > GP0 has only one parent clock, while MCLK has three. > > The second and third parent entries of GP0 are vacant, > > so they need to be marked optional. > > I will add the optional property for the second clock in the next revision. How? Read the previous feedback... Best regards, Krzysztof From broonie at kernel.org Thu May 21 04:36:07 2026 From: broonie at kernel.org (Mark Brown) Date: Thu, 21 May 2026 12:36:07 +0100 Subject: [PATCH 0/4] ASoC: meson: aiu: align I2S design to the AXG one In-Reply-To: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> References: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> Message-ID: <177936336749.58022.15311643425508742115.b4-ty@b4> On Fri, 15 May 2026 17:10:36 +0200, Valerio Setti wrote: > ASoC: meson: aiu: align I2S design to the AXG one > > This is the first follow-up patch series based on RFC [1]. The goal here > is simply to reshape Amlogic GX's AIU implementation for I2S to follow > the same design as in AXG's TDM. Keeping the same design allows for > unifying the two platform implementations in the future. > > [...] Applied to https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-7.2 Thanks! [1/4] ASoC: meson: gx: add gx-formatter and gx-interface https://git.kernel.org/broonie/sound/c/4efe33e7a2f0 [2/4] ASoC: meson: aiu-encoder-i2s: use gx_iface and gx_stream structures https://git.kernel.org/broonie/sound/c/3383866c1b77 [3/4] ASoC: meson: aiu: introduce I2S output formatter https://git.kernel.org/broonie/sound/c/df6057a25c52 [4/4] ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data https://git.kernel.org/broonie/sound/c/ca3543cf247b All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted. You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed. If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced. Please add any relevant lists and maintainers to the CCs when replying to this mail. Thanks, Mark From jian.hu at amlogic.com Fri May 22 04:44:45 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Fri, 22 May 2026 19:44:45 +0800 Subject: [PATCH 02/10] dt-bindings: clock: Add Amlogic A9 PLL clock controller In-Reply-To: <7c458070-a56a-4d49-89fc-efeb388beffc@kernel.org> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-2-41cb4071b7c9@amlogic.com> <20260515-subtle-sepia-tuatara-cfee3d@quoll> <40e83bed-e7a0-4c66-806c-c2988c5d0f33@amlogic.com> <7c458070-a56a-4d49-89fc-efeb388beffc@kernel.org> Message-ID: <86d0e5f0-f1be-4fa9-aad9-c498e2740e95@amlogic.com> On 5/22/2026 5:16 PM, Krzysztof Kozlowski wrote: > [ EXTERNAL EMAIL ] > > On 22/05/2026 08:20, Jian Hu wrote: >> Hi Krzysztof, >> >> Thanks for your review. >> >> On 5/15/2026 4:09 PM, Krzysztof Kozlowski wrote: >>> [ EXTERNAL EMAIL ] >>> >>> On Mon, May 11, 2026 at 08:47:24PM +0800, Jian Hu wrote: >>>> Add the PLL clock controller dt-bindings for the Amlogic A9 SoC family. >>>> >>>> Signed-off-by: Jian Hu >>>> --- >>>> .../bindings/clock/amlogic,a9-pll-clkc.yaml | 110 +++++++++++++++++++++ >>>> include/dt-bindings/clock/amlogic,a9-pll-clkc.h | 55 +++++++++++ >>>> 2 files changed, 165 insertions(+) >>>> >>>> diff --git a/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml b/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml >>>> new file mode 100644 >>>> index 000000000000..4ee6013ba1a1 >>>> --- /dev/null >>>> +++ b/Documentation/devicetree/bindings/clock/amlogic,a9-pll-clkc.yaml >>>> @@ -0,0 +1,110 @@ >>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) >>>> +# Copyright (C) 2026 Amlogic, Inc. All rights reserved >>>> +%YAML 1.2 >>>> +--- >>>> +$id: http://devicetree.org/schemas/clock/amlogic,a9-pll-clkc.yaml# >>>> +$schema: http://devicetree.org/meta-schemas/core.yaml# >>>> + >>>> +title: Amlogic A9 Series PLL Clock Controller >>>> + >>>> +maintainers: >>>> + - Neil Armstrong >>>> + - Jerome Brunet >>>> + - Jian Hu >>>> + - Xianwei Zhao >>>> + >>>> +properties: >>>> + compatible: >>>> + enum: >>>> + - amlogic,a9-gp0-pll >>>> + - amlogic,a9-hifi0-pll >>>> + - amlogic,a9-hifi1-pll >>>> + - amlogic,a9-mclk0-pll >>>> + - amlogic,a9-mclk1-pll >>>> + >>>> + reg: >>>> + maxItems: 1 >>>> + >>>> + '#clock-cells': >>>> + const: 1 >>>> + >>>> + clocks: >>>> + items: >>>> + - description: pll input oscillator gate >>>> + - description: fixed input clock source for mclk_sel_0 >>>> + - description: u3p2pll input clock source for mclk_sel_0 (optional) >>> Second clock is also optional. Drop "(optional)" comment, just >>> confusing. >> >> GP0 has only one parent clock, while MCLK has three. >> >> The second and third parent entries of GP0 are vacant, >> >> so they need to be marked optional. >> >> I will add the optional property for the second clock in the next revision. > How? Read the previous feedback... > > Best regards, > Krzysztof My apologies, I misunderstood your previous comment. I will drop "(optional)" from the clock descriptions. It will be updated as: ? clocks: ? ? items: ? ? ? - description: pll input oscillator gate ? ? ? - description: fixed input clock source for mclk_sel_0 ? ? ? - description: u3p2pll input clock source for mclk_sel_0 Best regards, Jian From neil.armstrong at linaro.org Fri May 22 05:35:38 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Fri, 22 May 2026 14:35:38 +0200 Subject: [PATCH v7 19/23] drm: bridge: dw_hdmi: Use delayed_work to debounce hotplug event In-Reply-To: <310e7a75-62e2-463c-b471-fcaaa3acfef5@kwiboo.se> References: <20260518180206.2480119-1-jonas@kwiboo.se> <20260518180206.2480119-20-jonas@kwiboo.se> <310e7a75-62e2-463c-b471-fcaaa3acfef5@kwiboo.se> Message-ID: <4df67011-e3f1-4b0b-afee-5fe1c7583277@linaro.org> On 5/21/26 22:13, Jonas Karlman wrote: > Hi Neil, > > On 5/20/2026 11:58 AM, Neil Armstrong wrote: >> Hi, >> >> On 5/18/26 20:01, Jonas Karlman wrote: >>> 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. >> >> The logic looks ok, but I'm puzzled by the 1.1 sec debounce, which after >> plugging in a monitor will only send an irq event after 1.1s which is very long. >> >> Since the spec says 100ms and the real worls values are more like 200-300ms, >> I would first reduce this to 500ms. > > You are correct, this value was picked based on existing values used by > other drivers: > > exynos/exynos_hdmi.c:#define HOTPLUG_DEBOUNCE_MS 1100 > bridge/ti-tfp410.c:#define HOTPLUG_DEBOUNCE_MS 1100 > amd/display/amdgpu_dm/amdgpu_dm.h:#define AMDGPU_DM_MAX_HDMI_HPD_DEBOUNCE_MS 5000 > rockchip/dw_hdmi_qp-rockchip.c:#define HOTPLUG_DEBOUNCE_MS 150 > > 150 ms was too short for my test monitor (Raspberry Pi Monitor), it > does a HPD low voltage level pulse when powering on of around 200+ ms. > > [82.641903] dw_hdmi_hardirq: EVENT=plugout > [82.841939] dw_hdmi_hardirq: EVENT=plugin > > And on my LG OLED 4K TV there was typically a pulse of around 700-900 ms. > So the 1.1 seconds used by other drivers seemed like a good candidate :-) > >> But as I understand the code right now, on the first HPD front the irq work >> is programmed to run after the debounce time, but if it's a pulse the irq would >> also trigger on the second HPD front and then delay again the work after the >> debounce time. > > Your analysis is correct, any HPD event would keep debouncing adding 1.1 > timout from each HPD irq, both plugout and plugin. > > The intention was to allow for up to 1.1 seconds to pass between a > plugout and a plugin, before we treated a plugout as a full disconnect > event, and not to delay a possible connected event by 1.1 seconds. > >> My understanding of a debounce was that we "ignore" the pulse by only generating >> a single irq event when the pulse is finished. > > Correct, as long as the pulse was less than 1.1 seconds. > >> The current code does that, we will only have a single irq event and the HPD >> will return as connected state, good. But this delays the irq event 1.1s _after_ >> the end of the pulse, which I would expect the event to be send at tht debounce >> time after the start of the pulse. > > Good catch and I agree, we should delay/timeout the disconnected state > longer than plugin and react on plugin almost immediately. > >> Like, program the work at the beginning of the pulse, if somehow the pulse ends before >> the debounce time, send the irq event immediately, otherwise let the debounce >> work run after the debounce time which will trigger a disconnect event. >> >> But the delay is too high, 1.1s could be a manual unplug/plug or bad connector >> with false contact on the hpd pin. >> >> I would rather reduce this to something more realistic like 500ms or less and >> try to better handle the pulse somehow. But I don't have any idea if the scheme >> I described is doable. > > We can configure different delays for plugout and plugin, I am currently > testing something like following: > > #define HOTPLUG_CONNECTED_DEBOUNCE_MS 150 > #define HOTPLUG_DISCONNECTED_DEBOUNCE_MS 500 > > delay = status == connector_status_connected ? > HOTPLUG_CONNECTED_DEBOUNCE_MS : > HOTPLUG_DISCONNECTED_DEBOUNCE_MS; > mod_delayed_work(system_percpu_wq, &hdmi->hpd_work, > msecs_to_jiffies(delay)); > > That would mean: > - at plugout we (re-)start the timer to trigger in 500ms > - at plugin we (re-)start the timer to trigger in 150ms > - whenever the timer triggers the hpd_work is started > - the hpw_work trigger normal detect() handling to determin if > connector status (or EDID) has changed > > Example during power on of a Raspberry Pi Monitor: > > [82.641903] dw_hdmi_hardirq: EVENT=plugout > [82.648276] dw_hdmi_schedule_hpd_work(status=2) > [82.841939] dw_hdmi_hardirq: EVENT=plugin > [82.848211] dw_hdmi_schedule_hpd_work(status=1) > [83.008573] dw_hdmi_hpd_work(): START > [83.020358] dw_hdmi_bridge_detect() > [83.034958] dw_hdmi_bridge_edid_read() > [83.187102] [drm:update_display_info.part.0] [CONNECTOR:55:HDMI-A-1] CEA VCDB 0x4a > [83.194471] [drm:update_display_info.part.0] [CONNECTOR:55:HDMI-A-1] HDMI: DVI dual 0, max TMDS clock 0 kHz > [83.201755] [drm:update_display_info.part.0] [CONNECTOR:55:HDMI-A-1] ELD monitor RPI MON156 > [83.208968] [drm:update_display_info.part.0] [CONNECTOR:55:HDMI-A-1] HDMI: latency present 0 0, video latency 0 0, audio latency 0 0 > [83.216588] [drm:update_display_info.part.0] [CONNECTOR:55:HDMI-A-1] ELD size 36, SAD count 1 > [83.224445] [drm:check_connector_changed] [CONNECTOR:55:HDMI-A-1] Same epoch counter 2 > [83.231553] dw_hdmi_hpd_work(): END > > Around 205ms between HPD=0 and HPD=1 and around 160ms from plugin until > delayed hpd_work starts. > > And at full plugout the debounce time is around 497 ms: > > [95.125105] dw_hdmi_hardirq: EVENT=plugout > [95.147586] dw_hdmi_schedule_hpd_work(status=2) > [95.645277] dw_hdmi_hpd_work(): START > [95.667741] dw_hdmi_bridge_detect() > [95.691523] [drm:drm_edid_connector_update] [CONNECTOR:55:HDMI-A-1] EDID changed, epoch counter 3 > [95.709861] [drm:check_connector_changed] [CONNECTOR:55:HDMI-A-1] status updated from connected to disconnected > [95.722674] [drm:check_connector_changed] [CONNECTOR:55:HDMI-A-1] Changed epoch counter 2 => 4 > [95.735173] [drm:drm_sysfs_connector_hotplug_event] [CONNECTOR:55:HDMI-A-1] generating connector hotplug event > [95.746326] [drm:drm_fb_helper_hotplug_event] > [95.754871] [drm:drm_client_modeset_probe] > [95.762429] [drm:drm_helper_probe_single_connector_modes] [CONNECTOR:55:HDMI-A-1] > [95.769758] dw_hdmi_bridge_detect() > [95.776597] [drm:drm_helper_probe_single_connector_modes] [CONNECTOR:55:HDMI-A-1] disconnected > [95.784389] [drm:drm_client_modeset_probe] No connectors reported connected with modes > [95.791865] [drm:drm_client_modeset_probe] [CONNECTOR:55:HDMI-A-1] enabled? no > [95.799426] [drm:drm_client_firmware_config.isra.0] Not using firmware configuration > [95.807140] [drm:drm_client_modeset_probe] picking CRTCs for 1920x1080 config > [95.818422] dw_hdmi_bridge_atomic_disable() > [95.826412] [drm:vop2_plane_atomic_disable] Smart0-win0 disable > [95.868341] [drm:drm_client_hotplug] fbdev: ret=0 > [95.878206] dw_hdmi_hpd_work(): END > > With a much quicker reaction on plugin event maybe the 1.1 seconds could > stay to avoid having to do a full teardown, disable() + enable() cycle, > at slightly longer HPD pulses. Yes those traces a good > > Or maybe it is time to re-spin an old cec-adapter debounce series [1] > that can allow for some additional time until the CEC phys addr is fully > invalidated at a longer HPD low voltage level pulse. Either one are good, the cec-adapt one looks simpler but still effective. Thanks, Neil > > [1] https://lore.kernel.org/linux-media/HE1PR06MB40115700084D1D875673D60EAC9D0 at HE1PR06MB4011.eurprd06.prod.outlook.com/ > > Regards, > Jonas > >> >> Neil >> >>> >>> Signed-off-by: Jonas Karlman >>> --- >>> v7: Change to free irq before mute and clear using IH regs, also include >>> clear of STAT0_RX_SENSE >>> 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 | 80 +++++++++++++++++++++-- >>> 1 file changed, 75 insertions(+), 5 deletions(-) >>> >>> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c >>> index 8afc9d240121..270db58a0e7c 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); >>> @@ -3532,6 +3593,18 @@ 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); >>> + >>> + /* Free, mute and clear phy interrupts */ >>> + devm_free_irq(hdmi->dev, irq, hdmi); >>> + hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0); >>> + hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE, >>> + HDMI_IH_PHY_STAT0); >>> + >>> + /* Cancel any pending hot plug work */ >>> + cancel_delayed_work_sync(&hdmi->hpd_work); >>> + >>> drm_bridge_remove(&hdmi->bridge); >>> >>> if (hdmi->audio && !IS_ERR(hdmi->audio)) >>> @@ -3539,9 +3612,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 >> > From vsetti at baylibre.com Fri May 22 09:24:16 2026 From: vsetti at baylibre.com (Valerio Setti) Date: Fri, 22 May 2026 18:24:16 +0200 Subject: [PATCH 4/4] ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data In-Reply-To: <758a4ef9-1a3e-475a-ae1e-83523330d006@sirena.org.uk> References: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> <20260515-reshape-aiu-as-axg-v1-4-53b457784ff3@baylibre.com> <758a4ef9-1a3e-475a-ae1e-83523330d006@sirena.org.uk> Message-ID: <891be10c-99f1-45b7-bd31-ec5080cfc780@baylibre.com> On 5/22/26 01:15, Mark Brown wrote: > On Fri, May 15, 2026 at 05:10:40PM +0200, Valerio Setti wrote: >> Create a new DAPM widget for "I2S formatter" and place it on the path >> between FIFO and output DAI interface. Remove I2S output formatting code >> from aiu-encoder-i2s since it's now implemented from aiu-formatter-i2s. > > This series, it looks like this specific patch, is breaking pcm-test on > my libretech Le Potato board, the clocking looks to be seriously messed > up. I'm getting: > > [...] > > Full log: > > https://lava.sirena.org.uk/scheduler/job/2786342#L1934 > > The prior patches seem to test fine, it's this one that seems to > introduce the issue. Thanks a lot for the heads up and please apologize for the problem. I wasn't aware of these testing tools so I based my testing on playing with userspace alsa tools on the physical board that I have. I will take a look at it ASAP and send a properly tested v2. -- Valerio From jerrysteve1101 at gmail.com Sun May 24 08:49:53 2026 From: jerrysteve1101 at gmail.com (Jun Yan) Date: Sun, 24 May 2026 23:49:53 +0800 Subject: [PATCH] dt-bindings: gpio: meson-axg: Fix whitespace issue Message-ID: <20260524154954.385778-1-jerrysteve1101@gmail.com> Clean up whitespace misalignment in meson-axg-gpio.h Signed-off-by: Jun Yan --- include/dt-bindings/gpio/meson-axg-gpio.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/dt-bindings/gpio/meson-axg-gpio.h b/include/dt-bindings/gpio/meson-axg-gpio.h index 25bb1fffa97a..a0d42bcd9bd3 100644 --- a/include/dt-bindings/gpio/meson-axg-gpio.h +++ b/include/dt-bindings/gpio/meson-axg-gpio.h @@ -23,7 +23,7 @@ #define GPIOAO_11 11 #define GPIOAO_12 12 #define GPIOAO_13 13 -#define GPIO_TEST_N 14 +#define GPIO_TEST_N 14 /* Second GPIO chip */ #define GPIOZ_0 0 @@ -52,7 +52,7 @@ #define BOOT_12 23 #define BOOT_13 24 #define BOOT_14 25 -#define GPIOA_0 26 +#define GPIOA_0 26 #define GPIOA_1 27 #define GPIOA_2 28 #define GPIOA_3 29 -- 2.54.0 From mhun512 at gmail.com Sun May 24 09:01:39 2026 From: mhun512 at gmail.com (Myeonghun Pak) Date: Mon, 25 May 2026 01:01:39 +0900 Subject: [PATCH] drm/meson: clean up KMS polling on register failure Message-ID: <20260524160657.17802-1-mhun512@gmail.com> meson_drv_bind_master() starts the KMS polling helper before registering the DRM device. If drm_dev_register() fails, probe unwinds the IRQ and DRM device without stopping the polling helper. Call drm_kms_helper_poll_fini() on that failure path before freeing the IRQ. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: bbbe775ec5b5 ("drm: Add support for Amlogic Meson Graphic Controller") Cc: stable at vger.kernel.org Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak --- drivers/gpu/drm/meson/meson_drv.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c index 49ff9f1f16..e49de5df73 100644 --- a/drivers/gpu/drm/meson/meson_drv.c +++ b/drivers/gpu/drm/meson/meson_drv.c @@ -352,12 +352,14 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) ret = drm_dev_register(drm, 0); if (ret) - goto uninstall_irq; + goto uninstall_poll; drm_client_setup(drm, NULL); return 0; +uninstall_poll: + drm_kms_helper_poll_fini(drm); uninstall_irq: free_irq(priv->vsync_irq, drm); exit_afbcd: -- 2.47.1 From sashiko-bot at kernel.org Sun May 24 09:25:33 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sun, 24 May 2026 16:25:33 +0000 Subject: [PATCH] drm/meson: clean up KMS polling on register failure In-Reply-To: <20260524160657.17802-1-mhun512@gmail.com> References: <20260524160657.17802-1-mhun512@gmail.com> Message-ID: <20260524162533.A98601F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: Pre-existing issues: - [High] Use-after-free of the `drm` pointer in the `free_drm` error path of `meson_drv_bind_master()`. - [High] Memory leak of `devm_*` allocated resources during component bind failures or unbind operations. -- commit 0a1b882dff3689e80c5546f7c0bf38c6930a1652 Author: Myeonghun Pak drm/meson: clean up KMS polling on register failure This commit fixes an issue in meson_drv_bind_master() where the KMS polling helper is left running if drm_dev_register() fails. It introduces a new uninstall_poll error path to call drm_kms_helper_poll_fini() before freeing the IRQ. > diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c > index 49ff9f1f16d32..e49de5df73867 100644 > --- a/drivers/gpu/drm/meson/meson_drv.c > +++ b/drivers/gpu/drm/meson/meson_drv.c > @@ -352,12 +352,14 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) > > ret = drm_dev_register(drm, 0); > if (ret) > - goto uninstall_irq; > + goto uninstall_poll; > > drm_client_setup(drm, NULL); > > return 0; > > +uninstall_poll: > + drm_kms_helper_poll_fini(drm); > uninstall_irq: > free_irq(priv->vsync_irq, drm); > exit_afbcd: [Severity: High] This isn't a bug introduced by this patch, but does the error unwinding path have a use-after-free? Looking further down in this function at the free_drm label: free_drm: drm_dev_put(drm); meson_encoder_dsi_remove(priv); meson_encoder_hdmi_remove(priv); meson_encoder_cvbs_remove(priv); if (has_components) component_unbind_all(dev, drm); Since drm_dev_put() drops the final reference and frees the drm structure, won't component_unbind_all() trigger a use-after-free when unbind callbacks dereference the freed pointer? [Severity: High] This is also a pre-existing issue, but are we leaking devm_* resources during component bind failures or unbind operations? Because devm allocations are scoped to the device probe lifecycle, they are not implicitly released during bind failure paths or unbind calls. Could this result in permanent memory leaks across retries if component binding fails and is later retried? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260524160657.17802-1-mhun512 at gmail.com?part=1 From mhun512 at gmail.com Sun May 24 09:35:31 2026 From: mhun512 at gmail.com (Myeonghun Pak) Date: Mon, 25 May 2026 01:35:31 +0900 Subject: [PATCH] drm/meson: clean up KMS polling on register failure In-Reply-To: <20260524162533.A98601F000E9@smtp.kernel.org> References: <20260524160657.17802-1-mhun512@gmail.com> <20260524162533.A98601F000E9@smtp.kernel.org> Message-ID: Thanks for the review. Both points look pre-existing and unrelated to this patch. This change only adds the missing drm_kms_helper_poll_fini() on the drm_dev_register() failure path after drm_kms_helper_poll_init(). It does not change the drm_dev_put()/component_unbind_all() ordering or the component bind devm resource lifetime. I would prefer to keep this patch scoped to the KMS polling unwind fix. 2026? 5? 25? (?) ?? 1:25, ?? ??: > > Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: > > Pre-existing issues: > - [High] Use-after-free of the `drm` pointer in the `free_drm` error path of `meson_drv_bind_master()`. > - [High] Memory leak of `devm_*` allocated resources during component bind failures or unbind operations. > -- > > commit 0a1b882dff3689e80c5546f7c0bf38c6930a1652 > Author: Myeonghun Pak > drm/meson: clean up KMS polling on register failure > > This commit fixes an issue in meson_drv_bind_master() where the KMS > polling helper is left running if drm_dev_register() fails. It introduces > a new uninstall_poll error path to call drm_kms_helper_poll_fini() before > freeing the IRQ. > > > diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c > > index 49ff9f1f16d32..e49de5df73867 100644 > > --- a/drivers/gpu/drm/meson/meson_drv.c > > +++ b/drivers/gpu/drm/meson/meson_drv.c > > @@ -352,12 +352,14 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) > > > > ret = drm_dev_register(drm, 0); > > if (ret) > > - goto uninstall_irq; > > + goto uninstall_poll; > > > > drm_client_setup(drm, NULL); > > > > return 0; > > > > +uninstall_poll: > > + drm_kms_helper_poll_fini(drm); > > uninstall_irq: > > free_irq(priv->vsync_irq, drm); > > exit_afbcd: > > [Severity: High] > This isn't a bug introduced by this patch, but does the error unwinding path > have a use-after-free? > > Looking further down in this function at the free_drm label: > > free_drm: > drm_dev_put(drm); > > meson_encoder_dsi_remove(priv); > meson_encoder_hdmi_remove(priv); > meson_encoder_cvbs_remove(priv); > > if (has_components) > component_unbind_all(dev, drm); > > Since drm_dev_put() drops the final reference and frees the drm structure, > won't component_unbind_all() trigger a use-after-free when unbind callbacks > dereference the freed pointer? > > [Severity: High] > This is also a pre-existing issue, but are we leaking devm_* resources > during component bind failures or unbind operations? > > Because devm allocations are scoped to the device probe lifecycle, they > are not implicitly released during bind failure paths or unbind calls. > > Could this result in permanent memory leaks across retries if component > binding fails and is later retried? > > -- > Sashiko AI review ? https://sashiko.dev/#/patchset/20260524160657.17802-1-mhun512 at gmail.com?part=1 From linusw at kernel.org Mon May 25 01:34:11 2026 From: linusw at kernel.org (Linus Walleij) Date: Mon, 25 May 2026 10:34:11 +0200 Subject: [PATCH] pinctrl: meson: amlogic-a4: fix gpio output glitch In-Reply-To: <20260518-fix-set-value-glitch-v1-1-d350732dc934@amlogic.com> References: <20260518-fix-set-value-glitch-v1-1-d350732dc934@amlogic.com> Message-ID: On Mon, May 18, 2026 at 10:26?AM Xianwei Zhao via B4 Relay wrote: > From: Xianwei Zhao > > When the system transitions from bootloader to kernel, the GPIO is > expected to keep driving high. > > However, the Linux kernel first configures the pin direction and then > sets the output value. This may cause a brief low-level glitch on the > GPIO line, which can be problematic for regulator control. > > By configuring the output value before switching the pin direction to > output, the glitch can be avoided. > > This commit fixes the issue by swapping the configuration order. > > Fixes: 6e9be3abb78c ("pinctrl: Add driver support for Amlogic SoCs") > Signed-off-by: Xianwei Zhao Is this a regression? I.e. does it cause problems on a supported system with mainline? Linus (the big penguin) is unhappy with too many non-critical fixes so I wanna check this before this goes into fixes. Yours, Linus Walleij From linux.amoon at gmail.com Mon May 25 02:51:48 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Mon, 25 May 2026 15:21:48 +0530 Subject: [PATCH v5 0/6] media: meson: Fix memory leak in error path in vdec Message-ID: <20260525095216.12078-1-linux.amoon@gmail.com> V5: Changes Following chamges try to fix the memory leak reported by Sashiko New issues: - [High] The newly added error path in `vdec_start_streaming()` leaks `sess->priv` when `kthread_run()` fails. Pre-existing issues: - [Critical] Race condition between hardware power-on and `core->cur_sess` initialization leads to a NULL pointer dereference in the IRQ handler. - [High] Returning buffers for both source and destination queues upon single-queue failure orphans active queue buffers. - [High] Concurrent sessions can bypass the hardware exclusivity check, leading to simultaneous hardware programming. -- Reported-by: Sashiko https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ V4: Changes: Following chamges try to fix the memory leak reported by Sashiko Pre-existing issues: - [Critical] The `sess->esparser_queue_work` work item is not canceled before freeing the session context, leading to a potential Use-After-Free vulnerability. - [High] The patch attempts to fix a memory leak reported by kmemleak, but misdiagnoses the root cause and leaves the primary memory leak (the V4L2 control handler) unresolved. - [High] The driver does not verify if `kthread_run()` returns an `ERR_PTR`, leading to a kernel panic when `kthread_stop()` is called. Reported-by: Sashiko https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t Thanks -Anand Anand Moon (6): media: meson: vdec: Fix memory leak in error path of vdec_open media: meson: vdec: Protect session exclusivity check with lock media: meson: vdec: Set cur_sess before hardware vdec_poweron() media: meson: vdec: Handle kthread error and free codec private data media: meson: vdec: Isolate error path buffer flush to the active queue media: meson: vdec: Cancel esparser work in error and stop paths drivers/staging/media/meson/vdec/vdec.c | 54 ++++++++++++++++++++----- 1 file changed, 44 insertions(+), 10 deletions(-) base-commit: e7ae89a0c97ce2b68b0983cd01eda67cf373517d -- 2.50.1 From linux.amoon at gmail.com Mon May 25 02:51:49 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Mon, 25 May 2026 15:21:49 +0530 Subject: [PATCH v5 1/6] media: meson: vdec: Fix memory leak in error path of vdec_open In-Reply-To: <20260525095216.12078-1-linux.amoon@gmail.com> References: <20260525095216.12078-1-linux.amoon@gmail.com> Message-ID: <20260525095216.12078-2-linux.amoon@gmail.com> The vdec_open() function previously jumped directly to err_m2m_release when vdec_init_ctrls() failed, skipping release of the m2m context. This caused a resource leak. Fix it by introducing a proper err_m2m_ctx_release label that calls v4l2_m2m_ctx_release(sess->m2m_ctx) before releasing the m2m device. Also free the v4l2 control handler memory allocated by vdec_init_ctrls() in vdec_close(). This was identified via kmemleak: unreferenced object 0xffff0000205d6878 (size 8): comm "v4l_id", pid 5289, jiffies 4294938580 hex dump (first 8 bytes): 40 d2 49 18 00 00 ff ff @.I..... backtrace (crc d3204599): kmemleak_alloc+0xc8/0xf0 __kvmalloc_node_noprof+0x60c/0x850 v4l2_ctrl_handler_init_class+0x1b4/0x2e8 [videodev] vdec_open+0x1f4/0x788 [meson_vdec] v4l2_open+0x144/0x460 [videodev] chrdev_open+0x1ac/0x500 do_dentry_open+0x3f0/0xfe8 vfs_open+0x68/0x320 do_open+0x2d8/0x9a8 path_openat+0x1d0/0x4f0 do_filp_open+0x190/0x380 do_sys_openat2+0xf8/0x1b0 __arm64_sys_openat+0x13c/0x1e8 invoke_syscall+0xdc/0x268 el0_svc_common.constprop.0+0x178/0x258 do_el0_svc+0x4c/0x70 Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- v5: update the error path for v4l2_ctrl_handler_free() as per the review ccmment to fix the use after free bug. [4] https://patchwork.kernel.org/project/linux-amlogic/patch/20260521073449.10057-2-linux.amoon at gmail.com/ v4: update the commit message to add v4l2_ctrl_handler_free() in vdec_close() to adderss the issue: This isn't a bug introduced by this patch, but does vdec_close() properly free the v4l2 control handler memory allocated by vdec_init_ctrls() here? v3: https://lore.kernel.org/all/20260520044046.7553-1-linux.amoon at gmail.com/ update the commit messagee. v2: https://lore.kernel.org/all/20260321065408.209723-1-linux.amoon at gmail.com/ updated the commit message, applied the suggestion from sashiko below. [3] https://sashiko.dev/#/patchset/20260321065408.209723-1-linux.amoon%40gmail.com v1: https://lore.kernel.org/all/20260304100557.126488-1-linux.amoon at gmail.com/ tried to address the issue reported by Nicolas improve the commit message. --- drivers/staging/media/meson/vdec/vdec.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 4b77ec1af5a7..18a22b79e835 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -889,7 +889,7 @@ static int vdec_open(struct file *file) ret = vdec_init_ctrls(sess); if (ret) - goto err_m2m_release; + goto err_m2m_ctx_release; sess->pixfmt_cap = formats[0].pixfmts_cap[0]; sess->fmt_out = &formats[0]; @@ -913,6 +913,8 @@ static int vdec_open(struct file *file) return 0; +err_m2m_ctx_release: + v4l2_m2m_ctx_release(sess->m2m_ctx); err_m2m_release: v4l2_m2m_release(sess->m2m_dev); err_free_sess: @@ -928,6 +930,7 @@ static int vdec_close(struct file *file) v4l2_m2m_release(sess->m2m_dev); v4l2_fh_del(&sess->fh, file); v4l2_fh_exit(&sess->fh); + v4l2_ctrl_handler_free(&sess->ctrl_handler); mutex_destroy(&sess->lock); mutex_destroy(&sess->bufs_recycle_lock); -- 2.50.1 From linux.amoon at gmail.com Mon May 25 02:51:50 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Mon, 25 May 2026 15:21:50 +0530 Subject: [PATCH v5 2/6] media: meson: vdec: Protect session exclusivity check with lock In-Reply-To: <20260525095216.12078-1-linux.amoon@gmail.com> References: <20260525095216.12078-1-linux.amoon@gmail.com> Message-ID: <20260525095216.12078-3-linux.amoon@gmail.com> Add the check for an active hardware session is performed without holding the core->lock mutex. In multi-threaded environments, two concurrent STREAMON ioctls on different file descriptors can simultaneously find core->cur_sess to be NULL, bypass the check, and concurrently call vdec_poweron(), corrupting hardware state. Fix this by wrapping the session exclusivity check inside core->lock. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- v5: New patch. [High] Concurrent sessions can bypass the hardware exclusivity check, leading to simultaneous hardware programming. --- drivers/staging/media/meson/vdec/vdec.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 18a22b79e835..e72f54af026e 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -286,10 +286,13 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) struct vb2_v4l2_buffer *buf; int ret; + mutex_lock(&core->lock); if (core->cur_sess && core->cur_sess != sess) { + mutex_unlock(&core->lock); ret = -EBUSY; goto bufs_done; } + mutex_unlock(&core->lock); if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) sess->streamon_out = 1; -- 2.50.1 From linux.amoon at gmail.com Mon May 25 02:51:51 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Mon, 25 May 2026 15:21:51 +0530 Subject: [PATCH v5 3/6] media: meson: vdec: Set cur_sess before hardware vdec_poweron() In-Reply-To: <20260525095216.12078-1-linux.amoon@gmail.com> References: <20260525095216.12078-1-linux.amoon@gmail.com> Message-ID: <20260525095216.12078-4-linux.amoon@gmail.com> vdec_poweron() initializes hardware and unmasks device interrupts. If an interrupt fires before core->cur_sess is set, vdec_isr() dereferences a NULL pointer when updating sess->last_irq_jiffies, leading to a kernel panic. Fix this by assigning core->cur_sess and updating sess->status under core->lock before calling vdec_poweron(). This ensures the interrupt handler always sees a valid session pointer. On the error path, clear core->cur_sess and reset sess->status to STATUS_STOPPED to avoid stale references. Following change also strengthens the hardware exclusivity check by holding core->lock during session assignment, preventing concurrent sessions from racing through cur_sess == NULL and corrupting hardware state. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- v5: [Critical] Race condition between hardware power-on and `core->cur_sess` initialization leads to a NULL pointer dereference in the IRQ handler. --- drivers/staging/media/meson/vdec/vdec.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index e72f54af026e..52ace4de967c 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -334,6 +334,11 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) atomic_set(&sess->esparser_queued_bufs, 0); v4l2_ctrl_s_ctrl(sess->ctrl_min_buf_capture, 1); + mutex_lock(&core->lock); + core->cur_sess = sess; + sess->status = STATUS_INIT; + mutex_unlock(&core->lock); + ret = vdec_poweron(sess); if (ret) goto vififo_free; @@ -344,12 +349,14 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) sess->recycle_thread = kthread_run(vdec_recycle_thread, sess, "vdec_recycle"); - sess->status = STATUS_INIT; - core->cur_sess = sess; schedule_work(&sess->esparser_queue_work); return 0; vififo_free: + mutex_lock(&core->lock); + core->cur_sess = NULL; + sess->status = STATUS_STOPPED; + mutex_unlock(&core->lock); dma_free_coherent(sess->core->dev, sess->vififo_size, sess->vififo_vaddr, sess->vififo_paddr); bufs_done: -- 2.50.1 From linux.amoon at gmail.com Mon May 25 02:51:52 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Mon, 25 May 2026 15:21:52 +0530 Subject: [PATCH v5 4/6] media: meson: vdec: Handle kthread error and free codec private data In-Reply-To: <20260525095216.12078-1-linux.amoon@gmail.com> References: <20260525095216.12078-1-linux.amoon@gmail.com> Message-ID: <20260525095216.12078-5-linux.amoon@gmail.com> vdec_start_streaming() launches a recycle thread when required by the codec. If kthread_run() fails, the previous error path only powered off the hardware, leaving sess->priv and codec state allocated. This caused a permanent leak of the codec context and associated DMA buffers. Fix this by adding an err_cleanup path: if thread creation fails, call codec_ops->stop() to release the codec context and clear sess->priv, then power off the hardware. Also reset core->cur_sess and sess->status to avoid stale references. This change closes the memory leak on kthread_run() failure and ensures proper cleanup of codec resources. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- v5: The vdec_poweron() function invoked earlier allocates dynamic memory for the codec context and assigns it to sess->priv. When kthread_run() fails, this new error path calls vdec_poweroff() which stops the hardware but doesn't free sess->priv. --- drivers/staging/media/meson/vdec/vdec.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 52ace4de967c..b31bf08af88e 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -345,13 +345,25 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) sess->sequence_cap = 0; sess->sequence_out = 0; - if (vdec_codec_needs_recycle(sess)) + if (vdec_codec_needs_recycle(sess)) { sess->recycle_thread = kthread_run(vdec_recycle_thread, sess, "vdec_recycle"); + if (IS_ERR(sess->recycle_thread)) { + ret = PTR_ERR(sess->recycle_thread); + sess->recycle_thread = NULL; + goto err_cleanup; + } + } schedule_work(&sess->esparser_queue_work); return 0; +err_cleanup: + if (codec_ops && codec_ops->stop && sess->priv) { + codec_ops->stop(sess); + sess->priv = NULL; + } + vdec_poweroff(sess); vififo_free: mutex_lock(&core->lock); core->cur_sess = NULL; -- 2.50.1 From linux.amoon at gmail.com Mon May 25 02:51:53 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Mon, 25 May 2026 15:21:53 +0530 Subject: [PATCH v5 5/6] media: meson: vdec: Isolate error path buffer flush to the active queue In-Reply-To: <20260525095216.12078-1-linux.amoon@gmail.com> References: <20260525095216.12078-1-linux.amoon@gmail.com> Message-ID: <20260525095216.12078-6-linux.amoon@gmail.com> When vdec_start_streaming() fails, the error path clears buffers from both the source and destination queues unconditionally. If one queue was already streaming successfully from a prior invocation, flushing its buffers behind its back leaves videobuf2 deadlocked waiting for completions. Fix this by only sweeping buffers from the specific queue type container that failed to initialize. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- v5: This is a pre-existing issue, but will returning buffers for both queues upon a single-queue failure orphan active queue buffers? If the CAPTURE queue was successfully started in a previous call, returning its buffers puts them back into the vb2 queued list while the driver discards its references. Because the CAPTURE queue remains active, userspace calling DQBUF will hang indefinitely waiting for frames that the driver will never process. --- drivers/staging/media/meson/vdec/vdec.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index b31bf08af88e..925537bd4d0b 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -372,15 +372,15 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) dma_free_coherent(sess->core->dev, sess->vififo_size, sess->vififo_vaddr, sess->vififo_paddr); bufs_done: - while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) - v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); - while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) - v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); - - if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { sess->streamon_out = 0; - else + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); + } else { sess->streamon_cap = 0; + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); + } return ret; } -- 2.50.1 From linux.amoon at gmail.com Mon May 25 02:51:54 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Mon, 25 May 2026 15:21:54 +0530 Subject: [PATCH v5 6/6] media: meson: vdec: Cancel esparser work in error and stop paths In-Reply-To: <20260525095216.12078-1-linux.amoon@gmail.com> References: <20260525095216.12078-1-linux.amoon@gmail.com> Message-ID: <20260525095216.12078-7-linux.amoon@gmail.com> The esparser workqueue may remain pending when streaming is stopped or the device is closed, leading to use-after-free if it runs after session teardown. vdec_start_streaming(), vdec_stop_streaming(), and vdec_close() did not cancel this work, leaving a race between session cleanup and work execution. Fix this by calling cancel_work_sync(&sess->esparser_queue_work) in all cleanup paths. Unlocking and relocking sess->lock around the cancel ensures the work handler cannot run concurrently with teardown. This prevents dangling work items from accessing freed session memory and eliminates a potential kernel crash. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- v5: Tried to fix the order of cancel_work_sync() which could lead to a use-after-free. update the commit message. --- drivers/staging/media/meson/vdec/vdec.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 925537bd4d0b..296b387f3667 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -372,6 +372,10 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) dma_free_coherent(sess->core->dev, sess->vififo_size, sess->vififo_vaddr, sess->vififo_paddr); bufs_done: + mutex_unlock(&sess->lock); + cancel_work_sync(&sess->esparser_queue_work); + mutex_lock(&sess->lock); + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { sess->streamon_out = 0; while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) @@ -430,6 +434,9 @@ static void vdec_stop_streaming(struct vb2_queue *q) kthread_stop(sess->recycle_thread); vdec_poweroff(sess); + mutex_unlock(&sess->lock); + cancel_work_sync(&sess->esparser_queue_work); + mutex_lock(&sess->lock); vdec_free_canvas(sess); dma_free_coherent(sess->core->dev, sess->vififo_size, sess->vififo_vaddr, sess->vififo_paddr); @@ -948,6 +955,8 @@ static int vdec_close(struct file *file) { struct amvdec_session *sess = file_to_amvdec_session(file); + cancel_work_sync(&sess->esparser_queue_work); + v4l2_m2m_ctx_release(sess->m2m_ctx); v4l2_m2m_release(sess->m2m_dev); v4l2_fh_del(&sess->fh, file); -- 2.50.1 From sashiko-bot at kernel.org Mon May 25 03:27:46 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 25 May 2026 10:27:46 +0000 Subject: [PATCH v5 1/6] media: meson: vdec: Fix memory leak in error path of vdec_open In-Reply-To: <20260525095216.12078-2-linux.amoon@gmail.com> References: <20260525095216.12078-2-linux.amoon@gmail.com> Message-ID: <20260525102746.B58861F00A3D@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: Pre-existing issues: - [Critical] Allocating and destroying a V4L2 m2m_dev on every session open/close causes a Use-After-Free within the V4L2 M2M core. - [High] The hardware interrupt handler vdec_isr() dereferences core->cur_sess without checking if it is NULL. -- commit bde68706fa632effa876c30cef33b3528cd917da Author: Anand Moon media: meson: vdec: Fix memory leak in error path of vdec_open This commit introduces an err_m2m_ctx_release label in vdec_open() to prevent a resource leak when vdec_init_ctrls() fails. It also frees the v4l2 control handler memory in vdec_close(). Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index 4b77ec1af5a76..18a22b79e835e 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c [ ... ] > @@ -913,6 +913,8 @@ static int vdec_open(struct file *file) > > return 0; > > +err_m2m_ctx_release: > + v4l2_m2m_ctx_release(sess->m2m_ctx); > err_m2m_release: > v4l2_m2m_release(sess->m2m_dev); [Severity: Critical] This is a pre-existing issue, but does allocating and destroying the m2m_dev on every session open and close cause a use-after-free? Since the v4l2 m2m core schedules jobs via a work struct, if a job is scheduled just before the file descriptor is closed: vdec_close() v4l2_m2m_release(sess->m2m_dev); kfree(m2m_dev); Could the system worker later execute v4l2_m2m_device_run_work() on the freed m2m_dev, as pending background work isn't cancelled during release? Should m2m_dev be allocated globally once in probe() instead? [ ... ] [Severity: High] This is a pre-existing issue, but looking at vdec_isr(), sess is initialized from core->cur_sess and immediately dereferenced: vdec_isr() sess = core->cur_sess; sess->last_irq_jiffies = get_jiffies_64(); If a spurious interrupt fires while the device is idle or immediately after streaming stops, could core->cur_sess be NULL here? Should there be a check before dereferencing sess? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260525095216.12078-1-linux.amoon at gmail.com?part=1 From sashiko-bot at kernel.org Mon May 25 03:43:45 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 25 May 2026 10:43:45 +0000 Subject: [PATCH v5 2/6] media: meson: vdec: Protect session exclusivity check with lock In-Reply-To: <20260525095216.12078-3-linux.amoon@gmail.com> References: <20260525095216.12078-3-linux.amoon@gmail.com> Message-ID: <20260525104345.C8D501F00A3C@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: New issues: - [High] The patch fails to resolve the concurrent STREAMON race condition, leaving a Time-Of-Check to Time-Of-Use (TOCTOU) vulnerability. Pre-existing issues: - [High] Unprotected modification of `core->cur_sess` in `vdec_stop_streaming` leads to a data race. - [High] Missing NULL check for `core->cur_sess` in interrupt handlers risks a kernel panic from spurious interrupts. -- commit 7f55f473f3e7d75722e346666034bf541752f2d3 Author: Anand Moon Subject: media: meson: vdec: Protect session exclusivity check with lock This commit attempts to fix a race condition during concurrent STREAMON calls. It wraps the session exclusivity check in vdec_start_streaming() with a mutex to prevent multiple threads from bypassing the active session check and concurrently triggering hardware initialization. Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index 18a22b79e835e..e72f54af026e3 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c > @@ -286,10 +286,13 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) > struct vb2_v4l2_buffer *buf; > int ret; > > + mutex_lock(&core->lock); > if (core->cur_sess && core->cur_sess != sess) { > + mutex_unlock(&core->lock); > ret = -EBUSY; > goto bufs_done; > } > + mutex_unlock(&core->lock); [Severity: High] Does this completely resolve the concurrent STREAMON race condition? While the lock protects the check itself, it is released immediately afterward. This leaves a window where two threads could sequentially acquire the lock, observe core->cur_sess as NULL, and then concurrently proceed with hardware initialization in vdec_poweron(). A later patch in the series wraps the eventual core->cur_sess = sess assignment in the lock, but does that still leave an unprotected gap between this check and the assignment? [Severity: High] This is a pre-existing issue, but does core->cur_sess need lock protection in vdec_stop_streaming() as well? Since V4L2 queue locks are used, STREAMON and STREAMOFF on different file descriptors can execute concurrently. If vdec_stop_streaming() sets core->cur_sess to NULL without acquiring core->lock, could this create a data race on the global pointer? [Severity: High] This isn't a bug introduced by this patch, but is it possible for core->cur_sess to be NULL when an interrupt fires? Since core->cur_sess is set to NULL when no decoding session is active, could a spurious hardware interrupt arriving during an idle period cause a NULL pointer dereference in vdec_isr() or vdec_threaded_isr() if they blindly dereference core->cur_sess without checking? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260525095216.12078-1-linux.amoon at gmail.com?part=2 From xianwei.zhao at amlogic.com Mon May 25 04:16:20 2026 From: xianwei.zhao at amlogic.com (Xianwei Zhao) Date: Mon, 25 May 2026 19:16:20 +0800 Subject: [PATCH] pinctrl: meson: amlogic-a4: fix gpio output glitch In-Reply-To: References: <20260518-fix-set-value-glitch-v1-1-d350732dc934@amlogic.com> Message-ID: <0ea637d9-6d79-4c8f-8c45-68cfdd84c262@amlogic.com> Hi Linus, Thanks for your review. On 2026/5/25 16:34, Linus Walleij wrote: > On Mon, May 18, 2026 at 10:26?AM Xianwei Zhao via B4 Relay > wrote: > >> From: Xianwei Zhao >> >> When the system transitions from bootloader to kernel, the GPIO is >> expected to keep driving high. >> >> However, the Linux kernel first configures the pin direction and then >> sets the output value. This may cause a brief low-level glitch on the >> GPIO line, which can be problematic for regulator control. >> >> By configuring the output value before switching the pin direction to >> output, the glitch can be avoided. >> >> This commit fixes the issue by swapping the configuration order. >> >> Fixes: 6e9be3abb78c ("pinctrl: Add driver support for Amlogic SoCs") >> Signed-off-by: Xianwei Zhao > Is this a regression? I.e. does it cause problems on a supported > system with mainline? > > Linus (the big penguin) is unhappy with too many non-critical fixes > so I wanna check this before this goes into fixes. > The issue only occurs when the critical power supply uses GPIO control. Otherwise, it is not significant. > Yours, > Linus Walleij From sashiko-bot at kernel.org Mon May 25 04:20:35 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 25 May 2026 11:20:35 +0000 Subject: [PATCH v5 3/6] media: meson: vdec: Set cur_sess before hardware vdec_poweron() In-Reply-To: <20260525095216.12078-4-linux.amoon@gmail.com> References: <20260525095216.12078-4-linux.amoon@gmail.com> Message-ID: <20260525112035.B1B281F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: New issues: - [Critical] Setting `core->cur_sess` before enabling hardware clocks creates a window where an interrupt can trigger hardware access while clocks are gated, causing a system crash. Pre-existing issues: - [High] The patch attempts to fix a hardware exclusivity race by protecting the assignment of `core->cur_sess` with a lock, but leaves a critical Time-Of-Check to Time-Of-Use (TOCTOU) gap. - [High] The interrupt handlers lack a NULL pointer check for `core->cur_sess`, leaving the driver susceptible to panics from spurious or delayed interrupts. - [High] The global pointers `core->cur_sess` and `sess->status` are cleared in `vdec_stop_streaming()` without acquiring `core->lock`, introducing a data race. -- commit aa641955fed13a943e3d3bbb36f369ce0d7fb41c Author: Anand Moon Subject: media: meson: vdec: Set cur_sess before hardware vdec_poweron() This patch moves the assignment of core->cur_sess and sess->status before vdec_poweron() in vdec_start_streaming() to prevent a NULL pointer dereference in the ISR. It also updates the error path and protects the assignments with core->lock. Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index e72f54af026e3..52ace4de967cb 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c > @@ -334,6 +334,11 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) > atomic_set(&sess->esparser_queued_bufs, 0); > v4l2_ctrl_s_ctrl(sess->ctrl_min_buf_capture, 1); > > + mutex_lock(&core->lock); > + core->cur_sess = sess; > + sess->status = STATUS_INIT; > + mutex_unlock(&core->lock); > + > ret = vdec_poweron(sess); [Severity: Critical] By setting core->cur_sess before calling vdec_poweron(), could this create a window where a spurious or shared interrupt triggers a hardware access while the hardware clocks are still gated? If the interrupt fires here, the ISR might invoke the active codec's ISR and perform MMIO writes. Accessing registers of a clock-gated device could result in a synchronous external abort (bus error or SError) and crash the kernel. [Severity: High] This isn't a bug introduced by this patch, but there is a Time-Of-Check to Time-Of-Use (TOCTOU) race condition regarding core->cur_sess. Earlier in vdec_start_streaming(), core->lock is held to check if core->cur_sess is available, but the lock is immediately released: mutex_lock(&core->lock); if (core->cur_sess && core->cur_sess != sess) { mutex_unlock(&core->lock); ret = -EBUSY; goto bufs_done; } mutex_unlock(&core->lock); Multiple concurrent sessions could simultaneously observe core->cur_sess == NULL, drop the lock, perform memory allocations, and then sequentially re-acquire the lock here to overwrite core->cur_sess. Both sessions would then execute vdec_poweron(), bypassing the exclusivity check. Should the initial check and the assignment be performed atomically within the same critical section? [Severity: High] This is a pre-existing issue, but does this fix address the root cause of the NULL pointer dereference? The commit message notes that vdec_isr() can dereference a NULL pointer if an interrupt fires early. Because the IRQ is registered during probe, it remains active for the device's lifetime. If a spurious interrupt fires when no session is active, or if a delayed interrupt triggers immediately after vdec_stop_streaming() clears core->cur_sess, could the kernel still panic? Should vdec_isr() and vdec_threaded_isr() be updated to include a proper NULL check for core->cur_sess? > if (ret) > goto vififo_free; > @@ -344,12 +349,14 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) > sess->recycle_thread = kthread_run(vdec_recycle_thread, sess, > "vdec_recycle"); > > - sess->status = STATUS_INIT; > - core->cur_sess = sess; > schedule_work(&sess->esparser_queue_work); > return 0; > > vififo_free: > + mutex_lock(&core->lock); > + core->cur_sess = NULL; > + sess->status = STATUS_STOPPED; > + mutex_unlock(&core->lock); [Severity: High] This is a pre-existing issue, but while core->lock is now used here in vdec_start_streaming(), is it also required in vdec_stop_streaming()? If vdec_stop_streaming() clears core->cur_sess and sess->status locklessly, it could run concurrently with another session's vdec_start_streaming(). This could break the synchronization model and allow for data races where the starting session reads an inconsistent hardware state. > dma_free_coherent(sess->core->dev, sess->vififo_size, > sess->vififo_vaddr, sess->vififo_paddr); > bufs_done: -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260525095216.12078-1-linux.amoon at gmail.com?part=3 From sashiko-bot at kernel.org Mon May 25 05:15:44 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 25 May 2026 12:15:44 +0000 Subject: [PATCH v5 4/6] media: meson: vdec: Handle kthread error and free codec private data In-Reply-To: <20260525095216.12078-5-linux.amoon@gmail.com> References: <20260525095216.12078-5-linux.amoon@gmail.com> Message-ID: <20260525121544.D094E1F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: New issues: - [High] Calling `codec_ops->stop()` before `vdec_poweroff()` causes a hardware DMA use-after-free vulnerability. - [Medium] The error path fails to free the dynamically allocated codec context (`sess->priv`), leading to a permanent memory leak. Pre-existing issues: - [High] The error path fails to free the hardware canvases allocated during `vdec_poweron()`. -- commit 8dc81d30970c8a0de3ed5a1ebe498c0479036766 Author: Anand Moon media: meson: vdec: Handle kthread error and free codec private data This commit adds an error handling path in vdec_start_streaming() when kthread_run() fails. It introduces an err_cleanup label to invoke the codec's stop callback and power off the hardware, aiming to fix memory leaks. Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index 52ace4de967cb..b31bf08af88e6 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c [ ... ] > @@ -345,13 +345,25 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) [ ... ] > schedule_work(&sess->esparser_queue_work); > return 0; > > +err_cleanup: > + if (codec_ops && codec_ops->stop && sess->priv) { > + codec_ops->stop(sess); [Severity: High] Does calling codec_ops->stop() before vdec_poweroff() risk a hardware DMA use after free? The codec_ops->stop() callbacks unmap and free DMA memory used by the firmware, but vdec_poweroff() hasn't been called yet to halt the hardware. The hardware could still be powered on with its DMA pointers pointing to the freed memory. Could this lead to IOMMU faults or memory corruption if the active hardware issues any memory transactions before it is powered off? > + sess->priv = NULL; [Severity: Medium] Does zeroing out sess->priv without freeing it cause a memory leak? The codec_ops->stop() implementations typically only release internal DMA buffers, but do not free the sess->priv structure itself. Normally, this memory is freed via kfree() in vdec_stop_streaming(). Does setting it to NULL here permanently orphan the structure? > + } > + vdec_poweroff(sess); [Severity: High] This is a pre-existing issue, but does this error path also need to free the hardware canvases? During vdec_poweron(), codecs allocate hardware canvas IDs. The normal shutdown path calls vdec_free_canvas() to release them. Since the hardware has a limited global pool of canvas IDs, does omitting this call risk rendering the video decoder subsystem unusable by leaking the canvas IDs? > vififo_free: > mutex_lock(&core->lock); > core->cur_sess = NULL; -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260525095216.12078-1-linux.amoon at gmail.com?part=4 From sashiko-bot at kernel.org Mon May 25 05:51:18 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 25 May 2026 12:51:18 +0000 Subject: [PATCH v5 5/6] media: meson: vdec: Isolate error path buffer flush to the active queue In-Reply-To: <20260525095216.12078-6-linux.amoon@gmail.com> References: <20260525095216.12078-6-linux.amoon@gmail.com> Message-ID: <20260525125118.887021F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] The error path in `vdec_start_streaming()` bypasses the teardown block, causing permanent memory leaks of hardware canvas IDs and codec private data. -- commit c72dab3c816fea617e47aef85e4ce526fc910b8a Author: Anand Moon Subject: media: meson: vdec: Isolate error path buffer flush to the active queue When vdec_start_streaming() fails, this commit limits clearing buffers to the specific queue type that failed to initialize. This prevents videobuf2 deadlocks when the other queue is already streaming successfully. Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index b31bf08af88e6..925537bd4d0b3 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c > @@ -372,15 +372,15 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) > dma_free_coherent(sess->core->dev, sess->vififo_size, > sess->vififo_vaddr, sess->vififo_paddr); > bufs_done: [Severity: High] This isn't a bug introduced by this patch, but does the error path leading up to this label cause permanent memory leaks of hardware canvas IDs and codec private data? When initialization fails late in vdec_start_streaming(), for example if kthread_run() fails, the error path handles cleanup: err_cleanup: if (codec_ops && codec_ops->stop && sess->priv) { codec_ops->stop(sess); sess->priv = NULL; } vdec_poweroff(sess); vififo_free: mutex_lock(&core->lock); core->cur_sess = NULL; sess->status = STATUS_STOPPED; mutex_unlock(&core->lock); By setting sess->status = STATUS_STOPPED, vdec_stop_streaming() is prevented from executing its teardown block on device close. As a result, vdec_free_canvas() is skipped, permanently leaking the limited hardware canvas IDs allocated during codec_ops->start(). Additionally, setting sess->priv = NULL without freeing it permanently leaks the codec context memory, because codec_ops->stop() implementations only free internal DMA buffers and rely on vdec_stop_streaming() to free sess->priv. > - while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) > - v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); > - while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) > - v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); > - > - if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) > + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { > sess->streamon_out = 0; > - else > + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) > + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); > + } else { > sess->streamon_cap = 0; > + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) > + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); > + } > > return ret; > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260525095216.12078-1-linux.amoon at gmail.com?part=5 From sashiko-bot at kernel.org Mon May 25 06:42:55 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Mon, 25 May 2026 13:42:55 +0000 Subject: [PATCH v5 6/6] media: meson: vdec: Cancel esparser work in error and stop paths In-Reply-To: <20260525095216.12078-7-linux.amoon@gmail.com> References: <20260525095216.12078-7-linux.amoon@gmail.com> Message-ID: <20260525134255.AB5411F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Critical] Dropping the V4L2 queue lock inside `vdec_stop_streaming()` breaks videobuf2 synchronization, allowing concurrent ioctls to cause use-after-free and double-frees. - [High] Unbalanced mutex unlock/lock in `vdec_stop_streaming()` when called during file release causes kernel panic and lock state corruption. - [Low] The `cancel_work_sync()` call added to `vdec_close()` is racy and ineffective because it is called before hardware interrupts are disabled. -- commit 6c14d2bfd15b9a61e01b7119c0cddce74a6adee0 Author: Anand Moon media: meson: vdec: Cancel esparser work in error and stop paths This commit adds cancel_work_sync() calls to the stop and close paths to prevent the esparser workqueue from running after session teardown. It drops and reacquires the session lock around the cancellation in the stop_streaming callback. Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index 925537bd4d0b3..296b387f3667b 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c [ ... ] > @@ -430,6 +434,9 @@ static void vdec_stop_streaming(struct vb2_queue *q) > kthread_stop(sess->recycle_thread); > > vdec_poweroff(sess); > + mutex_unlock(&sess->lock); > + cancel_work_sync(&sess->esparser_queue_work); > + mutex_lock(&sess->lock); [Severity: Critical] Does dropping the queue lock here break videobuf2 synchronization? When the v4l2 core processes a streamoff ioctl, it enters vb2_core_streamoff() holding this queue lock and eventually calls q->ops->stop_streaming(). If we unlock it here, could a concurrent streamoff ioctl on the same queue acquire the lock and re-enter vb2_core_streamoff()? Since q->start_streaming_called is only cleared after the stop_streaming callback returns, the second thread might also invoke vdec_stop_streaming() concurrently. Because sess->status hasn't been updated to STATUS_STOPPED yet, both threads could execute this teardown block, potentially leading to double-frees of sess->vififo_vaddr and sess->priv, or concurrent kthread_stop() calls. [Severity: High] Is it guaranteed that sess->lock is held when vdec_stop_streaming() is called? When a file descriptor is closed without explicitly stopping the stream first, the call chain looks like this: vdec_close() v4l2_m2m_ctx_release() vb2_queue_release() __vb2_queue_cancel() q->ops->stop_streaming() In this path, the v4l2 core does not hold the queue lock. Calling mutex_unlock() on an unheld mutex will cause lock state corruption. Furthermore, this code will then acquire the mutex and return, leaving it locked when vdec_close() subsequently calls mutex_destroy() on it. [ ... ] > @@ -948,6 +955,8 @@ static int vdec_close(struct file *file) > { > struct amvdec_session *sess = file_to_amvdec_session(file); > > + cancel_work_sync(&sess->esparser_queue_work); > + [Severity: Low] Can the workqueue be re-armed immediately after this cancellation? At this point in vdec_close(), the hardware hasn't been powered off yet. If a hardware interrupt fires right after cancel_work_sync() returns, could the interrupt service routine call schedule_work() and re-queue it? While this might not lead to a use-after-free because it gets cancelled again in vdec_stop_streaming() before any memory is freed, it seems to leave the workqueue active longer than intended. > v4l2_m2m_ctx_release(sess->m2m_ctx); > v4l2_m2m_release(sess->m2m_dev); > v4l2_fh_del(&sess->fh, file); -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260525095216.12078-1-linux.amoon at gmail.com?part=6 From linux.amoon at gmail.com Mon May 25 09:15:57 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Mon, 25 May 2026 21:45:57 +0530 Subject: [PATCH v5 1/6] media: meson: vdec: Fix memory leak in error path of vdec_open In-Reply-To: <20260525102746.B58861F00A3D@smtp.kernel.org> References: <20260525095216.12078-2-linux.amoon@gmail.com> <20260525102746.B58861F00A3D@smtp.kernel.org> Message-ID: Hi Sashiko, Thanks for your review comments On Mon, 25 May 2026 at 15:57, wrote: > > Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: > > Pre-existing issues: > - [Critical] Allocating and destroying a V4L2 m2m_dev on every session open/close causes a Use-After-Free within the V4L2 M2M core. > - [High] The hardware interrupt handler vdec_isr() dereferences core->cur_sess without checking if it is NULL. > -- > > commit bde68706fa632effa876c30cef33b3528cd917da > Author: Anand Moon > > media: meson: vdec: Fix memory leak in error path of vdec_open > > This commit introduces an err_m2m_ctx_release label in vdec_open() to > prevent a resource leak when vdec_init_ctrls() fails. It also frees the > v4l2 control handler memory in vdec_close(). > > Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t > > > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > > index 4b77ec1af5a76..18a22b79e835e 100644 > > --- a/drivers/staging/media/meson/vdec/vdec.c > > +++ b/drivers/staging/media/meson/vdec/vdec.c > [ ... ] > > @@ -913,6 +913,8 @@ static int vdec_open(struct file *file) > > > > return 0; > > > > +err_m2m_ctx_release: > > + v4l2_m2m_ctx_release(sess->m2m_ctx); > > err_m2m_release: > > v4l2_m2m_release(sess->m2m_dev); > > [Severity: Critical] > This is a pre-existing issue, but does allocating and destroying the m2m_dev > on every session open and close cause a use-after-free? > > Since the v4l2 m2m core schedules jobs via a work struct, if a job is > scheduled just before the file descriptor is closed: > > vdec_close() > v4l2_m2m_release(sess->m2m_dev); > kfree(m2m_dev); > > Could the system worker later execute v4l2_m2m_device_run_work() on the > freed m2m_dev, as pending background work isn't cancelled during release? > > Should m2m_dev be allocated globally once in probe() instead? > > [ ... ] > > [Severity: High] > This is a pre-existing issue, but looking at vdec_isr(), sess is initialized > from core->cur_sess and immediately dereferenced: > > vdec_isr() > sess = core->cur_sess; > sess->last_irq_jiffies = get_jiffies_64(); > > If a spurious interrupt fires while the device is idle or immediately after > streaming stops, could core->cur_sess be NULL here? > > Should there be a check before dereferencing sess? Apologies, let me check. Every time I try to fix a bug, I seem to create a new one. > Thanks -Anand > -- > Sashiko AI review ? https://sashiko.dev/#/patchset/20260525095216.12078-1-linux.amoon at gmail.com?part=1 From neil.armstrong at linaro.org Tue May 26 00:14:41 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Tue, 26 May 2026 09:14:41 +0200 Subject: [PATCH] dt-bindings: gpio: meson-axg: Fix whitespace issue In-Reply-To: <20260524154954.385778-1-jerrysteve1101@gmail.com> References: <20260524154954.385778-1-jerrysteve1101@gmail.com> Message-ID: <45081b91-7b49-44f0-998c-af9927deadf0@linaro.org> On 5/24/26 17:49, Jun Yan wrote: > Clean up whitespace misalignment in meson-axg-gpio.h > > Signed-off-by: Jun Yan > --- > include/dt-bindings/gpio/meson-axg-gpio.h | 4 ++-- > 1 file changed, 2 insertions(+), 2 deletions(-) > > diff --git a/include/dt-bindings/gpio/meson-axg-gpio.h b/include/dt-bindings/gpio/meson-axg-gpio.h > index 25bb1fffa97a..a0d42bcd9bd3 100644 > --- a/include/dt-bindings/gpio/meson-axg-gpio.h > +++ b/include/dt-bindings/gpio/meson-axg-gpio.h > @@ -23,7 +23,7 @@ > #define GPIOAO_11 11 > #define GPIOAO_12 12 > #define GPIOAO_13 13 > -#define GPIO_TEST_N 14 > +#define GPIO_TEST_N 14 > > /* Second GPIO chip */ > #define GPIOZ_0 0 > @@ -52,7 +52,7 @@ > #define BOOT_12 23 > #define BOOT_13 24 > #define BOOT_14 25 > -#define GPIOA_0 26 > +#define GPIOA_0 26 > #define GPIOA_1 27 > #define GPIOA_2 28 > #define GPIOA_3 29 Reviewed-by: Neil Armstrong Thanks, Neil From neil.armstrong at linaro.org Tue May 26 00:16:31 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Tue, 26 May 2026 09:16:31 +0200 Subject: [PATCH] drm/meson: clean up KMS polling on register failure In-Reply-To: <20260524160657.17802-1-mhun512@gmail.com> References: <20260524160657.17802-1-mhun512@gmail.com> Message-ID: <147bb128-a77c-49e4-897d-ba6ee1b1449d@linaro.org> On 5/24/26 18:01, Myeonghun Pak wrote: > meson_drv_bind_master() starts the KMS polling helper before registering > the DRM device. If drm_dev_register() fails, probe unwinds the IRQ and > DRM device without stopping the polling helper. > > Call drm_kms_helper_poll_fini() on that failure path before freeing the > IRQ. > > This issue was identified during our ongoing static-analysis research while > reviewing kernel code. > > Fixes: bbbe775ec5b5 ("drm: Add support for Amlogic Meson Graphic Controller") > Cc: stable at vger.kernel.org > Co-developed-by: Ijae Kim > Signed-off-by: Ijae Kim > Signed-off-by: Myeonghun Pak > --- > drivers/gpu/drm/meson/meson_drv.c | 4 +++- > 1 file changed, 3 insertions(+), 1 deletion(-) > > diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c > index 49ff9f1f16..e49de5df73 100644 > --- a/drivers/gpu/drm/meson/meson_drv.c > +++ b/drivers/gpu/drm/meson/meson_drv.c > @@ -352,12 +352,14 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) > > ret = drm_dev_register(drm, 0); > if (ret) > - goto uninstall_irq; > + goto uninstall_poll; > > drm_client_setup(drm, NULL); > > return 0; > > +uninstall_poll: > + drm_kms_helper_poll_fini(drm); > uninstall_irq: > free_irq(priv->vsync_irq, drm); > exit_afbcd: Reviewed-by: Neil Armstrong Thanks, Neil From jbrunet at baylibre.com Tue May 26 00:33:14 2026 From: jbrunet at baylibre.com (Jerome Brunet) Date: Tue, 26 May 2026 09:33:14 +0200 Subject: [PATCH 00/10] Add support for A9 family clock controller In-Reply-To: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> (Jian Hu via's message of "Mon, 11 May 2026 20:47:22 +0800") References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> Message-ID: <1jldd662x1.fsf@starbuckisacylon.baylibre.com> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > There are 4 clock controllers in A9 SoC: > - SCMI clock controller: these clocks are managed by the > Trusted Firmware-A(TF-A) and handled through SCMI. > - PLL clock controller. > - peripheral clock controller. > - AO clock controller. > > There are reserved register regions placed between individual PLLs, so a > separate driver is implemented for each PLL, similar to T7. > > Compared to previous SoCs PLLs, the A9 PLL controller introduces 4 new features: > 1.PLL l_detect signal supports active-high configuration. > Previous A7 and T7 l_detect signals are active-low. > 2.PLL reset signal supports active-low configuration. > Previous reset signals are active-high. > 3.Support POWER_OF_TWO for the PLL pre-divider N; > the N pre-divider follows the same calculation rule as OD. > 4.The PLL input path includes an inherent divide-by-2 divider. > > Implement the first three features in clk-pll.c (verified on A9 and T7), > with no impact to PLL logic on existing SoCs. Add a fixed divide-by-2 to > A9 PLL driver for the fourth feature. > > A9 PLL is composed as follows: > > PLL > +---------------------------------+ > | | > | +--+ | > in/2 >>---[ /2^N ]-->| | +-----+ | > | | |------| DCO |----->> out > | +--------->| | +--v--+ | > | | +--+ | | > | | | | > | +--[ *(M + (F/Fmax) ]<--+ | > | | > +---------------------------------+ > > out = in / 2 * (m + frac / frac_max) / 2^n > > Signed-off-by: Jian Hu > --- > Jian Hu (10): > dt-bindings: clock: Add Amlogic A9 SCMI clock controller > dt-bindings: clock: Add Amlogic A9 PLL clock controller > dt-bindings: clock: Add Amlogic A9 peripherals clock controller > dt-bindings: clock: Add Amlogic A9 AO clock controller > clk: amlogic: PLL l_detect signal supports active-high configuration > clk: amlogic: PLL reset signal supports active-low configuration > clk: amlogic: Support POWER_OF_TWO for PLL pre-divider > clk: amlogic: Add A9 PLL clock controller driver > clk: amlogic: Add A9 peripherals clock controller driver > clk: amlogic: Add A9 AO clock controller driver > > .../bindings/clock/amlogic,a9-aoclkc.yaml | 76 + > .../clock/amlogic,a9-peripherals-clkc.yaml | 150 ++ > .../bindings/clock/amlogic,a9-pll-clkc.yaml | 110 + > drivers/clk/meson/Kconfig | 28 + > drivers/clk/meson/Makefile | 2 + > drivers/clk/meson/a9-aoclk.c | 494 +++++ > drivers/clk/meson/a9-peripherals.c | 2317 ++++++++++++++++++++ > drivers/clk/meson/a9-pll.c | 831 +++++++ > drivers/clk/meson/clk-pll.c | 79 +- > drivers/clk/meson/clk-pll.h | 6 + > include/dt-bindings/clock/amlogic,a9-aoclkc.h | 76 + > .../clock/amlogic,a9-peripherals-clkc.h | 352 +++ > include/dt-bindings/clock/amlogic,a9-pll-clkc.h | 55 + > include/dt-bindings/clock/amlogic,a9-scmi-clkc.h | 51 + > 14 files changed, 4609 insertions(+), 18 deletions(-) For the next version, please split things up. There is no hard dependency between the different controllers. This will ease the review. The PLL controllers are bringing a new contraints in. The global/static nature of the controllers is something that has been bothering me for a while but there was no real reason to address it so far. Please give me some time to think about. Feel free to re-post the other controllers in the meantime. > --- > base-commit: ca89c88bcf69daca829044c638a8163d5ce47af0 > change-id: 20260511-b4-a9_clk-67652c1ae56e > > Best regards, -- Jerome From zhentao.guo at amlogic.com Tue May 26 00:33:35 2026 From: zhentao.guo at amlogic.com (Zhentao Guo) Date: Tue, 26 May 2026 15:33:35 +0800 Subject: [PATCH RFC v5 0/6] Add Amlogic stateless H.264 video decoder for S4 Message-ID: <20260526-b4-s4-vdec-upstream-v5-0-c6edebf5ea89@amlogic.com> Introduce initial driver support for Amlogic's new video acceleration hardware architecture, designed for video stream decoding. Compared to the current Amlogic video decoder hardware architecture, this new implementation eliminates the Esparser hardware component, enabling direct vb2 buffer input. The driver is designed to support the V4L2 M2M stateless decoder API. The initial phase includes support for H.264 decoding on Amlogic S805X2 platform. The driver needs to work alongside with a signed firmware. The loading process of the signed fw is as follow. Stage1: Decypt and decompose the full firmware package when the driver is probed. +---------------------+ +---------------------+ | Decoder Driver | | TEE Shared Memory | | (Kernel Space) | | | | +---------------+ | | +---------------+ | | | video_ucode | | | | firmware | | | | .bin | | Copy payload to SHM | | payload | | | | (from fs) | | ---------------------> | | (Secure RAM) | | | +---------------+ | | +---------------+ | +---------------------+ +----------+----------+ | | PTA Invocation v +-------------------------------+ | BL32 | | +-------------------------+ | | | Decrypt Firmware | | | +-----------+-------------+ | | | | | v | | +-------------------------+ | | | Decompose the full | | | | firmware pacakge | | | +-----------+-------------+ | | | | | v | | +-------------------------+ | | | Store decomposed .bin | | | | in Secure Memory | | | +-------------------------+ | +-------------------------------+ Stage2: When a decode job is scheduled, load decrypted fw via secure monitor. +---------------------+ | V4L2 M2M Framework | | +---------------+ | | | device_run | | | +------+--------+ | +---------+-----------+ | v +---------------------+ +---------------------+ | Decoder Driver | | Secure Monitor | | (Kernel Space) | | (bl32) | | +---------------+ | SMC Call | +---------------+ | | | Select Codec | | ---------------> | | Select & Load | | | | Specific FW | | | | firmware.bin | | | +---------------+ | | | to AMRISC | | +---------------------+ | +-------+-------+ | +----------+----------+ | v +---------------------+ | AMRISC Core | | +---------------+ | | | Running fw on | | | | AMRISC | | | +---------------+ | +---------------------+ The driver is capable of: - Supporting stateless H.264 decoding up to a resolution 1920x1088(on the S805X2 platform). - Supporting I/P/B frame handling. - Supporting vb2 mmap and dma-buf modes. - Supporting frame-based decode mode. (Note that some H.264 bitstreams require DPB reordering to generate reference lists, the stateless decoder driver cannot access reordered reference lists in this mode, requiring the driver to perform reference list reordering itself) - Supporting NV12/NV21 output. - Supporting Annex B start codes. This driver is tested with Gstreamer. Example: gst-launch-1.0 filesrc location=/tmp/video_640x360_mp4_hevc_450kbps_no_b.mp4 ! parsebin ! v4l2slh264dec ! filesink location=/tmp/output.yuv Retry the compliance test based on kernel 7.1.0: v4l2-compliance 1.30.1, 64 bits, 64-bit time_t Compliance test for aml-vdec-drv device /dev/video0: Driver Info: Driver name : aml-vdec-drv Card type : platform:aml-vdec-drv Bus info : platform:fe320000.video-codec Driver version : 7.1.0 Capabilities : 0x84204000 Video Memory-to-Memory Multiplanar Streaming Extended Pix Format Device Capabilities Device Caps : 0x04204000 Video Memory-to-Memory Multiplanar Streaming Extended Pix Format Detected Stateless Decoder Media Driver Info: Driver name : aml-vdec-drv Model : aml-vdec-drv Serial : Bus info : platform:fe320000.video-codec Media version : 7.1.0 Hardware revision: 0x00000000 (0) Driver version : 7.1.0 Interface Info: ID : 0x0300000c Type : V4L Video Entity Info: ID : 0x00000001 (1) Name : aml_dev_drv-source Function : V4L2 I/O Pad 0x01000002 : 0: Source Link 0x02000008: to remote pad 0x1000004 of entity 'aml_dev_drv-proc' (Video Decoder): Data, Enabled, Immutable Required ioctls: test MC information (see 'Media Driver Info' above): OK test VIDIOC_QUERYCAP: OK test invalid ioctls: OK Allow for multiple opens: test second /dev/video0 open: OK test VIDIOC_QUERYCAP: OK test VIDIOC_G/S_PRIORITY: OK test for unlimited opens: OK Debug ioctls: test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported) test VIDIOC_LOG_STATUS: OK (Not Supported) Input ioctls: test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported) test VIDIOC_G/S_FREQUENCY: OK (Not Supported) test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported) test VIDIOC_ENUMAUDIO: OK (Not Supported) test VIDIOC_G/S/ENUMINPUT: OK (Not Supported) test VIDIOC_G/S_AUDIO: OK (Not Supported) Inputs: 0 Audio Inputs: 0 Tuners: 0 Output ioctls: test VIDIOC_G/S_MODULATOR: OK (Not Supported) test VIDIOC_G/S_FREQUENCY: OK (Not Supported) test VIDIOC_ENUMAUDOUT: OK (Not Supported) test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported) test VIDIOC_G/S_AUDOUT: OK (Not Supported) Outputs: 0 Audio Outputs: 0 Modulators: 0 Input/Output configuration ioctls: test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported) test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported) test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported) test VIDIOC_G/S_EDID: OK (Not Supported) Control ioctls: test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK test VIDIOC_QUERYCTRL: OK test VIDIOC_G/S_CTRL: OK test VIDIOC_G/S/TRY_EXT_CTRLS: OK test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK test VIDIOC_G/S_JPEGCOMP: OK (Not Supported) Standard Controls: 6 Private Controls: 0 Standard Compound Controls: 4 Private Compound Controls: 0 Format ioctls: test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK test VIDIOC_G/S_PARM: OK (Not Supported) test VIDIOC_G_FBUF: OK (Not Supported) test VIDIOC_G_FMT: OK test VIDIOC_TRY_FMT: OK test VIDIOC_S_FMT: OK test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported) test Cropping: OK (Not Supported) test Composing: OK (Not Supported) test Scaling: OK (Not Supported) Codec ioctls: test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported) test VIDIOC_G_ENC_INDEX: OK (Not Supported) test VIDIOC_(TRY_)DECODER_CMD: OK Buffer ioctls: test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK test CREATE_BUFS maximum buffers: OK test VIDIOC_REMOVE_BUFS: OK test VIDIOC_EXPBUF: OK test Requests: OK test blocking wait: OK Total for aml-vdec-drv device /dev/video0: 49, Succeeded: 49, Failed: 0, Warnings: 0 Fluster test result of JVT-AVC_V1. Result: Ran 77/135 tests successfully - 52 test vectors failed due to interlaced or mbaff clips: The Amlogic stateless decoder driver only support bitstreams with frame_mbs_only_flags == 1. Test Vectors: cabac_mot_fld0_full cabac_mot_mbaff0_full cabac_mot_picaff0_full CABREF3_Sand_D CAFI1_SVA_C CAMA1_Sony_C CAMA1_TOSHIBA_B cama1_vtc_c cama2_vtc_b CAMA3_Sand_E cama3_vtc_b CAMACI3_Sony_C CAMANL1_TOSHIBA_B CAMANL2_TOSHIBA_B CAMANL3_Sand_E CAMASL3_Sony_B CAMP_MOT_MBAFF_L30 CAMP_MOT_MBAFF_L31 CANLMA2_Sony_C CANLMA3_Sony_C CAPA1_TOSHIBA_B CAPAMA3_Sand_F cavlc_mot_fld0_full_B cavlc_mot_mbaff0_full_B cavlc_mot_picaff0_full_B CVCANLMA2_Sony_C CVFI1_Sony_D CVFI1_SVA_C CVFI2_Sony_H CVFI2_SVA_C CVMA1_Sony_D CVMA1_TOSHIBA_B CVMANL1_TOSHIBA_B CVMANL2_TOSHIBA_B CVMAPAQP3_Sony_E CVMAQP2_Sony_G CVMAQP3_Sony_D CVMP_MOT_FLD_L30_B CVNLFI1_Sony_C CVNLFI2_Sony_H CVPA1_TOSHIBA_B FI1_Sony_E MR6_BT_B MR7_BT_B MR8_BT_B MR9_BT_B Sharp_MP_Field_1_B Sharp_MP_Field_2_B Sharp_MP_Field_3_B Sharp_MP_PAFF_1r2 Sharp_MP_PAFF_2r CVMP_MOT_FRM_L31_B - 3 test vectors failed due to unsupported bitstream. num_slice_group_minus1 greater than zero is not supported by the hardware. Test Vectors: FM1_BT_B FM1_FT_E FM2_SVA_C - 2 test vectors failed because SP_SLICE type is not supported by the hardware. Test Vectors: SP1_BT_A sp2_bt_b One remain failure is CVFC1_Sony_C, which contains crop information. The md5sum of every decoded YUV indicates that original output from the decoder was correct. The YUV was cropped by gstreamer. The correct cropping method for this bitstream should be to crop 30*2 rows of pixels from both the top and bottom of the image, and 13*2 columns of pixels from both the left and right sides.However, gstreamer cropped 13*4 columns of pixels from the right side and 30*4 rows of pixels from the bottom. We are trying to find out the cause of this. Other failuers mentioned in V1 and V2 were resolved. Changes in v5: - Rename the compatible and the clock item accroding to Krzysztof's feedback. - Use tee & meson_sm helpers to decrypt load the signed decoder firmware. Add the meson_sm describsion and reference to dt-binding and dts. - Link to v4: https://lore.kernel.org/r/20260213-b4-s4-vdec-upstream-v4-0-c7112d00d662 at amlogic.com Changes in v4: - Use %pad to print dma_addr_t type instead of using %llx. - Add initial values to some local variables. - Link to v3: https://lore.kernel.org/r/20260121-b4-s4-vdec-upstream-v3-0-4496aec3d79e at amlogic.com Changes in v3: - Fixed the DT check error: arch/arm64/boot/dts/amlogic/meson-s4-s805x2-aq222.dtb: video-codec at fe320000 (amlogic,s4-vcodec-dec): 'amlogic,canvas' does not match any of the regexes: '^pinctrl-[0-9]+$' from schema $id: http://devicetree.org/schemas/media/amlogic,vcodec-dec.yaml - Added DOS reset lines to dtsi and dt-binding. - Fixed the issue where some B-frames were not decoded correctly(The fluster failures mentioned in patch V1 and V2 were mostly caused by this). - Fixed the issue where canvas_index leaks occurred during the decoding of some bitstreams. - Rework the src/dst format storage. Use v4l2_pix_format_mplane to store formats that related to bitstreams into the context. Add the reset format function to reset all the formats to default value. - Store decoding parameters related to chip platforms, such as maximum width/height and alignment requirement, organized by chip platform. - Link to v2: https://lore.kernel.org/r/20251124-b4-s4-vdec-upstream-v2-0-bdbbce3f11a6 at amlogic.com Changes in v2: - Fixed incorrect generation of the reference lists for some B-frames. - Rename or get rid of some properties in DTS and dt-binding. - Remove some useless code or helper functions, (eg. clk helper functions, reg I/O macros, and some superfluous print messages) replace these functions with existing ones. - Replace all the printk messages with dev_err/dev_info/dev_dbg - Use the helper functions from the existing meson-canvas driver. - Use clk_bulk_data to map clocks from DTS. - Retry the V4L2 Compliance test on 6.18-rc6, fix a newly introduced bug. - Link to v1: https://lore.kernel.org/r/20251027-b4-s4-vdec-upstream-v1-0-620401813b5d at amlogic.com To: Mauro Carvalho Chehab To: Rob Herring To: Krzysztof Kozlowski To: Conor Dooley To: Neil Armstrong To: Kevin Hilman To: Jerome Brunet To: Martin Blumenstingl Cc: linux-media at vger.kernel.org Cc: devicetree at vger.kernel.org Cc: linux-kernel at vger.kernel.org Cc: linux-arm-kernel at lists.infradead.org Cc: linux-amlogic at lists.infradead.org Signed-off-by: Zhentao Guo --- Zhentao Guo (6): firmware: meson: sm: Add video firmware loading SMC call firmware: meson: sm: video firmware loading via secure monitor media: dt-bindings: Add Amlogic V4L2 video decoder decoder: Add V4L2 stateless H.264 decoder driver arm64: dts: amlogic: Add video decoder driver support for S4 SOCs arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC .../devicetree/bindings/media/amlogic,s4-vdec.yaml | 103 + MAINTAINERS | 7 + arch/arm64/boot/dts/amlogic/meson-s4.dtsi | 34 + arch/arm64/configs/defconfig | 1 + drivers/firmware/meson/meson_sm.c | 1 + drivers/media/platform/amlogic/Kconfig | 1 + drivers/media/platform/amlogic/Makefile | 1 + drivers/media/platform/amlogic/vdec/Kconfig | 18 + drivers/media/platform/amlogic/vdec/Makefile | 4 + drivers/media/platform/amlogic/vdec/TODO | 7 + drivers/media/platform/amlogic/vdec/aml_vdec.c | 736 +++++++ drivers/media/platform/amlogic/vdec/aml_vdec.h | 33 + drivers/media/platform/amlogic/vdec/aml_vdec_drv.c | 239 +++ drivers/media/platform/amlogic/vdec/aml_vdec_drv.h | 172 ++ drivers/media/platform/amlogic/vdec/aml_vdec_hw.c | 538 +++++ drivers/media/platform/amlogic/vdec/aml_vdec_hw.h | 159 ++ .../platform/amlogic/vdec/aml_vdec_platform.c | 81 + .../platform/amlogic/vdec/aml_vdec_platform.h | 46 + .../media/platform/amlogic/vdec/aml_vdec_tee_fw.c | 240 +++ .../media/platform/amlogic/vdec/aml_vdec_tee_fw.h | 27 + drivers/media/platform/amlogic/vdec/h264.c | 2128 ++++++++++++++++++++ drivers/media/platform/amlogic/vdec/h264.h | 299 +++ drivers/media/platform/amlogic/vdec/reg_defines.h | 177 ++ include/linux/firmware/meson/meson_sm.h | 1 + 24 files changed, 5053 insertions(+) --- base-commit: d387b06f7c15b4639244ad66b4b0900c6a02b430 change-id: 20251027-b4-s4-vdec-upstream-0603c1a4c84a Best regards, -- Zhentao Guo From zhentao.guo at amlogic.com Tue May 26 00:33:36 2026 From: zhentao.guo at amlogic.com (Zhentao Guo) Date: Tue, 26 May 2026 15:33:36 +0800 Subject: [PATCH RFC v5 1/6] firmware: meson: sm: Add video firmware loading SMC call In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-0-c6edebf5ea89@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-0-c6edebf5ea89@amlogic.com> Message-ID: <20260526-b4-s4-vdec-upstream-v5-1-c6edebf5ea89@amlogic.com> Add SM_LOAD_VIDEO_FW at SMC ID 0xb200000f in the command table to load video firmware. Signed-off-by: Zhentao Guo --- drivers/firmware/meson/meson_sm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c index 3ab67aaa9e5d..5da6c65d684a 100644 --- a/drivers/firmware/meson/meson_sm.c +++ b/drivers/firmware/meson/meson_sm.c @@ -47,6 +47,7 @@ static const struct meson_sm_chip gxbb_chip = { CMD(SM_GET_CHIP_ID, 0x82000044), CMD(SM_A1_PWRC_SET, 0x82000093), CMD(SM_A1_PWRC_GET, 0x82000095), + CMD(SM_LOAD_VIDEO_FW, 0xb200000f), { /* sentinel */ }, }, }; -- 2.42.0 From zhentao.guo at amlogic.com Tue May 26 00:33:37 2026 From: zhentao.guo at amlogic.com (Zhentao Guo) Date: Tue, 26 May 2026 15:33:37 +0800 Subject: [PATCH RFC v5 2/6] firmware: meson: sm: video firmware loading via secure monitor In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-0-c6edebf5ea89@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-0-c6edebf5ea89@amlogic.com> Message-ID: <20260526-b4-s4-vdec-upstream-v5-2-c6edebf5ea89@amlogic.com> Add SM_LOAD_VIDEO_FW to the secure monitor command enum to allow decoder drivers to load firmware through the meson_sm interface. Signed-off-by: Zhentao Guo --- include/linux/firmware/meson/meson_sm.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/firmware/meson/meson_sm.h index 8eaf8922ab02..f40867a000f1 100644 --- a/include/linux/firmware/meson/meson_sm.h +++ b/include/linux/firmware/meson/meson_sm.h @@ -14,6 +14,7 @@ enum { SM_GET_CHIP_ID, SM_A1_PWRC_SET, SM_A1_PWRC_GET, + SM_LOAD_VIDEO_FW, }; struct meson_sm_firmware; -- 2.42.0 From zhentao.guo at amlogic.com Tue May 26 00:33:38 2026 From: zhentao.guo at amlogic.com (Zhentao Guo) Date: Tue, 26 May 2026 15:33:38 +0800 Subject: [PATCH RFC v5 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-0-c6edebf5ea89@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-0-c6edebf5ea89@amlogic.com> Message-ID: <20260526-b4-s4-vdec-upstream-v5-3-c6edebf5ea89@amlogic.com> Describe the initial support for the V4L2 stateless video decoder driver used with the Amlogic S4 (S805X2) platform. Signed-off-by: Zhentao Guo --- .../devicetree/bindings/media/amlogic,s4-vdec.yaml | 103 +++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml b/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml new file mode 100644 index 000000000000..a0f33f6c35a1 --- /dev/null +++ b/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2025 Amlogic, Inc. All rights reserved +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/amlogic,s4-vdec.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic Video Decode Accelerator + +maintainers: + - Zhentao Guo + +description: + The Video Decoder Accelerator present on Amlogic SOCs. + It supports stateless h264 decoding. + +properties: + compatible: + const: amlogic,s4-vdec + + reg: + maxItems: 2 + + reg-names: + items: + - const: dos + - const: dmc + + interrupts: + maxItems: 3 + + clocks: + maxItems: 3 + + clock-names: + items: + - const: dos + - const: vdec + - const: hevcf + + power-domains: + maxItems: 2 + + power-domain-names: + items: + - const: vdec + - const: hevc + + resets: + maxItems: 1 + + amlogic,canvas: + description: should point to a canvas provider node + $ref: /schemas/types.yaml#/definitions/phandle + + secure-monitor: + description: phandle to the secure-monitor node + $ref: /schemas/types.yaml#/definitions/phandle + +required: + - compatible + - reg + - reg-names + - interrupts + - clocks + - clock-names + - power-domains + - power-domain-names + - amlogic,canvas + - secure-monitor + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + video-codec at fe320000 { + compatible = "amlogic,s4-vdec"; + reg = <0xfe320000 0x10000>, + <0xfe036000 0x20>; + amlogic,canvas = <&canvas>; + reg-names = "dos", + "dmc"; + interrupts = , + , + ; + clocks = <&clkc_periphs CLKID_DOS>, + <&clkc_periphs CLKID_VDEC_SEL>, + <&clkc_periphs CLKID_HEVCF_SEL>; + clock-names = "dos", + "vdec", + "hevcf"; + power-domains = <&pwrc PWRC_S4_DOS_VDEC_ID>, + <&pwrc PWRC_S4_DOS_HEVC_ID>; + power-domain-names = "vdec", + "hevc"; + resets = <&reset RESET_DOS>; + secure-monitor = <&sm>; + }; -- 2.42.0 From zhentao.guo at amlogic.com Tue May 26 00:33:39 2026 From: zhentao.guo at amlogic.com (Zhentao Guo) Date: Tue, 26 May 2026 15:33:39 +0800 Subject: [PATCH RFC v5 4/6] decoder: Add V4L2 stateless H.264 decoder driver In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-0-c6edebf5ea89@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-0-c6edebf5ea89@amlogic.com> Message-ID: <20260526-b4-s4-vdec-upstream-v5-4-c6edebf5ea89@amlogic.com> Add initial support for V4L2 stateless video decoder driver on Amlogic S4(S805X2) platform. In phase 1, it supports 8bit H.264 bitstreams decoding. Currently only progressive streams are supported. Signed-off-by: Zhentao Guo --- MAINTAINERS | 7 + drivers/media/platform/amlogic/Kconfig | 1 + drivers/media/platform/amlogic/Makefile | 1 + drivers/media/platform/amlogic/vdec/Kconfig | 18 + drivers/media/platform/amlogic/vdec/Makefile | 4 + drivers/media/platform/amlogic/vdec/TODO | 7 + drivers/media/platform/amlogic/vdec/aml_vdec.c | 736 +++++++ drivers/media/platform/amlogic/vdec/aml_vdec.h | 33 + drivers/media/platform/amlogic/vdec/aml_vdec_drv.c | 239 +++ drivers/media/platform/amlogic/vdec/aml_vdec_drv.h | 172 ++ drivers/media/platform/amlogic/vdec/aml_vdec_hw.c | 538 +++++ drivers/media/platform/amlogic/vdec/aml_vdec_hw.h | 159 ++ .../platform/amlogic/vdec/aml_vdec_platform.c | 81 + .../platform/amlogic/vdec/aml_vdec_platform.h | 46 + .../media/platform/amlogic/vdec/aml_vdec_tee_fw.c | 240 +++ .../media/platform/amlogic/vdec/aml_vdec_tee_fw.h | 27 + drivers/media/platform/amlogic/vdec/h264.c | 2128 ++++++++++++++++++++ drivers/media/platform/amlogic/vdec/h264.h | 299 +++ drivers/media/platform/amlogic/vdec/reg_defines.h | 177 ++ 19 files changed, 4913 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index fe28ba288999..8d35821ff4c3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1406,6 +1406,13 @@ S: Maintained F: Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml F: drivers/spi/spi-amlogic-spisg.c +AMLOGIC VDEC DRIVER +M: Zhentao Guo +L: linux-media at vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/amlogic,s4-vcodec-dec.yaml +F: drivers/media/platform/amlogic/vdec/ + AMPHENOL CHIPCAP 2 DRIVER M: Javier Carrasco L: linux-hwmon at vger.kernel.org diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig index 458acf3d5fa8..7c541ac0d0c3 100644 --- a/drivers/media/platform/amlogic/Kconfig +++ b/drivers/media/platform/amlogic/Kconfig @@ -4,3 +4,4 @@ comment "Amlogic media platform drivers" source "drivers/media/platform/amlogic/c3/Kconfig" source "drivers/media/platform/amlogic/meson-ge2d/Kconfig" +source "drivers/media/platform/amlogic/vdec/Kconfig" diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile index c744afcd1b9e..7409de674c0b 100644 --- a/drivers/media/platform/amlogic/Makefile +++ b/drivers/media/platform/amlogic/Makefile @@ -2,3 +2,4 @@ obj-y += c3/ obj-y += meson-ge2d/ +obj-y += vdec/ diff --git a/drivers/media/platform/amlogic/vdec/Kconfig b/drivers/media/platform/amlogic/vdec/Kconfig new file mode 100644 index 000000000000..d392967c7743 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR MIT) + +config VIDEO_AMLOGIC_VDEC + tristate "Amlogic Video Decoder Driver" + depends on ARCH_MESON || COMPILE_TEST + depends on VIDEO_DEV + depends on V4L_MEM2MEM_DRIVERS + depends on TEE + select VIDEOBUF2_DMA_CONTIG + select V4L2_H264 + select V4L2_MEM2MEM_DEV + select MESON_CANVAS + select MESON_SM + help + This is a v4l2 driver for Amlogic video decoder driver. + This driver is designed to support V4L2 M2M STATELESS + interface. + To compile this driver as a module choose m here. diff --git a/drivers/media/platform/amlogic/vdec/Makefile b/drivers/media/platform/amlogic/vdec/Makefile new file mode 100644 index 000000000000..f752716cbf9e --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +aml-vdec-drv-objs := aml_vdec.o aml_vdec_drv.o aml_vdec_hw.o aml_vdec_platform.o h264.o aml_vdec_tee_fw.o\ + +obj-$(CONFIG_VIDEO_AMLOGIC_VDEC) += aml-vdec-drv.o diff --git a/drivers/media/platform/amlogic/vdec/TODO b/drivers/media/platform/amlogic/vdec/TODO new file mode 100644 index 000000000000..54c60145770e --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/TODO @@ -0,0 +1,7 @@ +TODO list for Amlogic V4L2 stateless decoder driver: + +1. Support decoding for HEVC, VP9, AV1, and MPEG-2. +2. Support more SoCs, including the new T7/S7 series and legacy SoCs (e.g., GXL, SM1, G12B). +3. Support 10-bit decoding and P010 output. + Note: P010 output requires hardware support. +4. Support interlaced stream decoding for H.264, HEVC, and MPEG-2. diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec.c b/drivers/media/platform/amlogic/vdec/aml_vdec.c new file mode 100644 index 000000000000..d4dcd0180d2d --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec.c @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include +#include + +#include "aml_vdec.h" +#include "aml_vdec_hw.h" +#include "aml_vdec_platform.h" +#include "aml_vdec_tee_fw.h" + +#define VCODEC_DRV_NAME "aml-vdec-drv" + +static const struct aml_vdec_v4l2_ctrl controls[] = { + { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_STATELESS_H264_DECODE_PARAMS, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_STATELESS_H264_SPS, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_STATELESS_H264_PPS, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_STATELESS_H264_SCALING_MATRIX, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_STATELESS_H264_DECODE_MODE, + .min = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + .def = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + .max = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_STATELESS_H264_START_CODE, + .min = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + .def = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + .max = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, + .min = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, + .max = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, + .def = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN, + }, + } +}; + +static const struct aml_dec_type dec_type_name[] = { + { + .codec_type = CODEC_TYPE_H264, + .name = "H264", + }, +}; + +static const char *dec_type_to_name(unsigned int type) +{ + int i; + int size = ARRAY_SIZE(dec_type_name); + + for (i = 0; i < size; i++) { + if (dec_type_name[i].codec_type == type) + return dec_type_name[i].name; + } + + return "ERR"; +} + +int aml_vdec_ctrls_setup(struct aml_vdec_ctx *ctx) +{ + int i; + int ctrls_size = ARRAY_SIZE(controls); + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, ctrls_size); + for (i = 0; i < ctrls_size; i++) { + v4l2_ctrl_new_custom(&ctx->ctrl_handler, &controls[i].cfg, NULL); + if (ctx->ctrl_handler.error) { + dev_info(&ctx->dev->plat_dev->dev, "add ctrl for (%d) failed%d\n", + controls[i].cfg.id, ctx->ctrl_handler.error); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return ctx->ctrl_handler.error; + } + } + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + return v4l2_ctrl_handler_setup(&ctx->ctrl_handler); +} + +static void m2mops_vdec_device_run(void *m2m_priv) +{ + struct aml_vdec_ctx *ctx = m2m_priv; + struct aml_vdec_dev *dev = ctx->dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct media_request *src_req; + int ret = 0; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + dev_dbg(&dev->plat_dev->dev, "device run : src buf : %d dst buf %d\n", + src_buf->vb2_buf.index, dst_buf->vb2_buf.index); + if (WARN_ON_ONCE(!ctx->codec_ops->run)) + goto err_cancel_job; + + src_req = src_buf->vb2_buf.req_obj.req; + if (src_req) + v4l2_ctrl_request_setup(src_req, &ctx->ctrl_handler); + dos_enable(dev->dec_hw); + /* incase of bus hang in stop_streaming */ + ctx->dos_clk_en = 1; + + if (ctx->curr_dec_type == CODEC_TYPE_H264) + aml_vdec_reset_core(dev->dec_hw); + + if (load_firmware(dev->dec_hw, ctx->curr_dec_type) < 0) + goto err_cancel_job; + + ret = ctx->codec_ops->run(ctx); + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf); + if (src_req) + v4l2_ctrl_request_complete(src_req, &ctx->ctrl_handler); + if (ret < 0) + goto err_cancel_job; + + return; + +err_cancel_job: + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx, + VB2_BUF_STATE_ERROR); +} + +const struct v4l2_m2m_ops aml_vdec_m2m_ops = { + .device_run = m2mops_vdec_device_run, +}; + +static int vidioc_vdec_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, VCODEC_DRV_NAME, sizeof(cap->driver)); + strscpy(cap->card, "platform:" VCODEC_DRV_NAME, sizeof(cap->card)); + + return 0; +} + +static int vidioc_vdec_enum_fmt(struct aml_vdec_ctx *ctx, + struct v4l2_fmtdesc *f, bool is_output) +{ + struct aml_vdec_dev *dev = ctx->dev; + const struct aml_video_fmt *fmt; + int i = 0, j = 0; + + for (; i < dev->pvdec_data->num_fmts; i++) { + fmt = &dev->pvdec_data->dec_fmt[i]; + if (is_output && fmt->type != AML_FMT_DEC) + continue; + if (!is_output && fmt->type != AML_FMT_FRAME) + continue; + + if (j == f->index) { + f->pixelformat = fmt->fourcc; + strscpy(f->description, fmt->name, + sizeof(f->description)); + return 0; + } + ++j; + } + return -EINVAL; +} + +static const struct aml_video_fmt *aml_vdec_get_video_fmt(struct aml_vdec_dev + *dev, u32 format) +{ + const struct aml_video_fmt *fmt; + unsigned int k; + + for (k = 0; k < dev->pvdec_data->num_fmts; k++) { + fmt = &dev->pvdec_data->dec_fmt[k]; + if (fmt->fourcc == format) + return fmt; + } + + return NULL; +} + +static int aml_vdec_init_dec_inst(struct aml_vdec_ctx *ctx) +{ + struct aml_vdec_dev *dev = ctx->dev; + struct aml_video_fmt *fmt_out = &ctx->dec_fmt[AML_FMT_SRC]; + int ret = -1; + + ctx->codec_ops = &dev->pvdec_data->codec_ops[fmt_out->codec_type]; + if (ctx->codec_ops->init) { + ret = ctx->codec_ops->init(ctx); + if (ret < 0) + return ret; + } + ctx->curr_dec_type = fmt_out->codec_type; + dev_info(&dev->plat_dev->dev, "%s set curr_dec_type %s\n", + __func__, dec_type_to_name(ctx->curr_dec_type)); + + return ret; +} + +static void set_pic_info(struct aml_vdec_ctx *ctx, + struct v4l2_pix_format_mplane *pix_mp, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) { + ctx->pic_info.output_pix_fmt = pix_mp->pixelformat; + ctx->pic_info.coded_width = ALIGN(pix_mp->width, 64); + ctx->pic_info.coded_height = ALIGN(pix_mp->height, 64); + ctx->pic_info.fb_size[0] = + ctx->pic_info.coded_width * ctx->pic_info.coded_height; + ctx->pic_info.fb_size[1] = ctx->pic_info.fb_size[0] / 2; + ctx->pic_info.plane_num = 1; + } +} + +static int vidioc_vdec_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + const struct aml_video_fmt *fmt; + struct aml_vdec_dev *dev = video_drvdata(file); + + if (fsize->index != 0) + return -EINVAL; + + fmt = aml_vdec_get_video_fmt(dev, fsize->pixel_format); + if (!fmt) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise = fmt->stepwise; + + return 0; +} + +static int vdec_try_fmt_mp(struct aml_vdec_ctx *ctx, enum v4l2_buf_type type, + struct v4l2_pix_format_mplane *pix_mp) +{ + struct aml_video_fmt *dec_fmt; + int i, align; + + if (V4L2_TYPE_IS_OUTPUT(type)) + dec_fmt = &ctx->dec_fmt[AML_FMT_SRC]; + else + dec_fmt = &ctx->dec_fmt[AML_FMT_DST]; + + pix_mp->field = V4L2_FIELD_NONE; + + if (V4L2_TYPE_IS_OUTPUT(type)) { + pix_mp->num_planes = dec_fmt->num_planes; + pix_mp->pixelformat = dec_fmt->fourcc; + if (!pix_mp->plane_fmt[0].sizeimage) + pix_mp->plane_fmt[0].sizeimage = + (pix_mp->height * pix_mp->width * 3) / 2; + } + + align = ctx->dev->pvdec_data->dec_fmt->align; + pix_mp->height = ALIGN(pix_mp->height, align); + pix_mp->width = ALIGN(pix_mp->width, align); + + v4l2_apply_frmsize_constraints(&pix_mp->width, &pix_mp->height, + &dec_fmt->stepwise); + dev_dbg(&ctx->dev->plat_dev->dev, + "%s type %d four_cc %d pix_mp->width %d pix_mp->height %d\n", + __func__, type, dec_fmt->fourcc, pix_mp->width, pix_mp->height); + + v4l2_fill_pixfmt_mp(pix_mp, dec_fmt->fourcc, pix_mp->width, + pix_mp->height); + + for (i = 0; i < pix_mp->num_planes; i++) + memset(&pix_mp->plane_fmt[i].reserved[0], 0x0, + sizeof(pix_mp->plane_fmt[0].reserved)); + + memset(pix_mp->reserved, 0x0, sizeof(pix_mp->reserved)); + pix_mp->flags = 0; + + dev_dbg(&ctx->dev->plat_dev->dev, + "%s type %d fmt %d num_plane %d sizeimage0 %d sizeimage1 %d\n", + __func__, type, pix_mp->pixelformat, pix_mp->num_planes, + pix_mp->plane_fmt[0].sizeimage, pix_mp->plane_fmt[1].sizeimage); + + return 0; +} + +static int vdec_s_fmt_output(struct aml_vdec_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + const struct aml_video_fmt *out_fmt; + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (vb2_is_busy(vq) && + pix_mp->pixelformat != ctx->pix_fmt[AML_FMT_SRC].pixelformat) + return -EBUSY; + + out_fmt = aml_vdec_get_video_fmt(ctx->dev, pix_mp->pixelformat); + if (out_fmt) + ctx->dec_fmt[AML_FMT_SRC] = *out_fmt; + else + dev_dbg(&ctx->dev->plat_dev->dev, + "%s fmt %d not supported, use default\n", __func__, + pix_mp->pixelformat); + + ret = vdec_try_fmt_mp(ctx, f->type, pix_mp); + set_pic_info(ctx, pix_mp, f->type); + + ctx->pix_fmt[AML_FMT_SRC] = *pix_mp; + ctx->pix_fmt[AML_FMT_DST] = *pix_mp; + + return ret; +} + +static int vdec_s_fmt_capture(struct aml_vdec_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + const struct aml_video_fmt *cap_fmt; + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (vb2_is_busy(vq)) + return -EBUSY; + + cap_fmt = aml_vdec_get_video_fmt(ctx->dev, pix_mp->pixelformat); + if (cap_fmt) + ctx->dec_fmt[AML_FMT_DST] = *cap_fmt; + else + dev_dbg(&ctx->dev->plat_dev->dev, + "%s fmt %d not supported, use default\n", __func__, + pix_mp->pixelformat); + + ret = vdec_try_fmt_mp(ctx, f->type, pix_mp); + + ctx->pix_fmt[AML_FMT_DST] = *pix_mp; + + return ret; +} + +static void reset_output_fmts(struct aml_vdec_ctx *ctx) +{ + struct aml_vdec_dev *dev = ctx->dev; + const struct aml_video_fmt *out_fmt; + struct v4l2_pix_format_mplane fmt; + + /* reset default output fmt to V4L2_PIX_FMT_H264_SLICE */ + out_fmt = aml_vdec_get_video_fmt(dev, V4L2_PIX_FMT_H264_SLICE); + if (!out_fmt) + return; + + ctx->dec_fmt[AML_FMT_SRC] = *out_fmt; + + memset(&fmt, 0, sizeof(struct v4l2_pix_format_mplane)); + + fmt.height = out_fmt->stepwise.min_height; + fmt.width = out_fmt->stepwise.min_width; + /* reset bytesperline to 0 for output fmts */ + fmt.plane_fmt[0].bytesperline = 0; + fmt.colorspace = V4L2_COLORSPACE_DEFAULT; + fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt.quantization = V4L2_QUANTIZATION_DEFAULT; + fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT; + vdec_try_fmt_mp(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, &fmt); + + ctx->pix_fmt[AML_FMT_SRC] = fmt; +} + +static void reset_capture_fmts(struct aml_vdec_ctx *ctx) +{ + struct aml_vdec_dev *dev = ctx->dev; + const struct aml_video_fmt *cap_fmt; + struct v4l2_pix_format_mplane fmt; + + /* reset default output fmt to V4L2_PIX_FMT_NV12 */ + cap_fmt = aml_vdec_get_video_fmt(dev, V4L2_PIX_FMT_NV12); + if (!cap_fmt) + return; + + ctx->dec_fmt[AML_FMT_DST] = *cap_fmt; + + memset(&fmt, 0, sizeof(struct v4l2_pix_format_mplane)); + + fmt.height = cap_fmt->stepwise.min_height; + fmt.width = cap_fmt->stepwise.min_width; + fmt.colorspace = V4L2_COLORSPACE_DEFAULT; + fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt.quantization = V4L2_QUANTIZATION_DEFAULT; + fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT; + vdec_try_fmt_mp(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, &fmt); + + ctx->pix_fmt[AML_FMT_DST] = fmt; +} + +void aml_vdec_reset_fmts(struct aml_vdec_ctx *ctx) +{ + ctx->m2m_ctx->q_lock = &ctx->v4l2_intf_lock; + reset_output_fmts(ctx); + reset_capture_fmts(ctx); +} + +static int vdec_g_fmt(struct aml_vdec_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + + if (V4L2_TYPE_IS_OUTPUT(f->type)) + *pix_mp = ctx->pix_fmt[AML_FMT_SRC]; + else + *pix_mp = ctx->pix_fmt[AML_FMT_DST]; + + dev_dbg(&ctx->dev->plat_dev->dev, + "%s fmt %d num planes %d\n", __func__, pix_mp->pixelformat, + pix_mp->num_planes); + + return 0; +} + +static int vidioc_try_fmt_cap_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vdec_try_fmt_mp(fh_to_dec_ctx(file), f->type, &f->fmt.pix_mp); +} + +static int vidioc_try_fmt_out_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vdec_try_fmt_mp(fh_to_dec_ctx(file), f->type, &f->fmt.pix_mp); +} + +static int vidioc_vdec_s_fmt_out_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + return vdec_s_fmt_output(ctx, f); +} + +static int vidioc_vdec_s_fmt_cap_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + return vdec_s_fmt_capture(ctx, f); +} + +static int vidioc_vdec_g_fmt_out_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + return vdec_g_fmt(ctx, f); +} + +static int vidioc_vdec_g_fmt_cap_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + return vdec_g_fmt(ctx, f); +} + +static int vidioc_vdec_enum_fmt_out_mplane(struct file *file, + void *priv, struct v4l2_fmtdesc *f) +{ + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + return vidioc_vdec_enum_fmt(ctx, f, 1); +} + +static int vidioc_vdec_enum_fmt_cap_mplane(struct file *file, + void *priv, struct v4l2_fmtdesc *f) +{ + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + return vidioc_vdec_enum_fmt(ctx, f, 0); +} + +const struct v4l2_ioctl_ops aml_vdec_ioctl_ops = { + .vidioc_querycap = vidioc_vdec_querycap, + .vidioc_enum_framesizes = vidioc_vdec_enum_framesizes, + + .vidioc_enum_fmt_vid_cap = vidioc_vdec_enum_fmt_cap_mplane, + .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_cap_mplane, + .vidioc_s_fmt_vid_cap_mplane = vidioc_vdec_s_fmt_cap_mplane, + .vidioc_g_fmt_vid_cap_mplane = vidioc_vdec_g_fmt_cap_mplane, + + .vidioc_enum_fmt_vid_out = vidioc_vdec_enum_fmt_out_mplane, + .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt_out_mplane, + .vidioc_s_fmt_vid_out_mplane = vidioc_vdec_s_fmt_out_mplane, + .vidioc_g_fmt_vid_out_mplane = vidioc_vdec_g_fmt_out_mplane, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_decoder_cmd = v4l2_m2m_ioctl_stateless_decoder_cmd, + .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_stateless_try_decoder_cmd, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, +}; + +static void aml_vdec_release_instance(struct aml_vdec_ctx *ctx) +{ + if (ctx->codec_ops && ctx->codec_ops->exit) + ctx->codec_ops->exit(ctx); +} + +static int vb2ops_vdec_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(vq); + struct v4l2_pix_format_mplane *pix_fmt; + unsigned int i; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + pix_fmt = &ctx->pix_fmt[AML_FMT_SRC]; + else + pix_fmt = &ctx->pix_fmt[AML_FMT_DST]; + + if (*nplanes) { + if (*nplanes != pix_fmt->num_planes) + return -EINVAL; + + for (i = 0; i < *nplanes; i++) { + if (sizes[i] < pix_fmt->plane_fmt[i].sizeimage) { + dev_err(&ctx->dev->plat_dev->dev, + "not supported sizeimage\n"); + return -EINVAL; + } + alloc_devs[i] = &ctx->dev->plat_dev->dev; + } + } else { + if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + *nplanes = pix_fmt->num_planes; + else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + *nplanes = 1; + + for (i = 0; i < *nplanes; i++) { + alloc_devs[i] = &ctx->dev->plat_dev->dev; + sizes[i] = pix_fmt->plane_fmt[i].sizeimage; + } + } + + if (*nplanes) { + dev_dbg(&ctx->dev->plat_dev->dev, + "type: %d, plane: %d, buf cnt: %d, size: [Y: %u, C: %u]\n", + vq->type, *nplanes, *nbuffers, sizes[0], sizes[1]); + return 0; + } + + return -EINVAL; +} + +static int vb2ops_vdec_buf_prepare(struct vb2_buffer *vb) +{ + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_pix_format_mplane *pix_fmt; + unsigned int sizeimage = 0; + int i; + + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) + pix_fmt = &ctx->pix_fmt[AML_FMT_SRC]; + else + pix_fmt = &ctx->pix_fmt[AML_FMT_DST]; + + for (i = 0; i < pix_fmt->num_planes; i++) { + sizeimage = pix_fmt->plane_fmt[i].sizeimage; + if (vb2_plane_size(vb, i) < sizeimage) + return -EINVAL; + + if (V4L2_TYPE_IS_CAPTURE(vb->type)) { + vb2_set_plane_payload(vb, i, + pix_fmt->plane_fmt[i].sizeimage); + dev_dbg(&ctx->dev->plat_dev->dev, + "%s type: %d set plane: %d, sizeimage: %d\n", + __func__, vb->vb2_queue->type, i, + pix_fmt->plane_fmt[i].sizeimage); + } + } + + return 0; +} + +static int vb2_ops_vdec_buf_init(struct vb2_buffer *vb) +{ + return 0; +} + +static void vb2_ops_vdec_buf_queue(struct vb2_buffer *vb) +{ + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vb2_v4l2 = to_vb2_v4l2_buffer(vb); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vb2_v4l2); +} + +static void vb2_ops_vdec_buf_finish(struct vb2_buffer *vb) +{ +} + +static int vb2ops_vdec_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(q); + + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + ctx->is_output_streamon = 1; + if (aml_vdec_init_dec_inst(ctx) < 0) + return -EINVAL; + } else { + ctx->is_cap_streamon = 1; + } + + return 0; +} + +static void vb2ops_vdec_stop_streaming(struct vb2_queue *q) +{ + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *src_buf = NULL, *dst_buf = NULL; + + aml_vdec_release_instance(ctx); + + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + while ((src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx))) + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + ctx->is_output_streamon = 0; + } else { + while ((dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx))) + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + ctx->is_cap_streamon = 0; + } +} + +static int vb2ops_vdec_out_buf_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + return 0; +} + +static void vb2ops_vdec_buf_request_complete(struct vb2_buffer *vb) +{ + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->ctrl_handler); +} + +static const struct vb2_ops aml_vdec_vb2_ops = { + .queue_setup = vb2ops_vdec_queue_setup, + .start_streaming = vb2ops_vdec_start_streaming, + .stop_streaming = vb2ops_vdec_stop_streaming, + + .buf_init = vb2_ops_vdec_buf_init, + .buf_prepare = vb2ops_vdec_buf_prepare, + .buf_out_validate = vb2ops_vdec_out_buf_validate, + .buf_queue = vb2_ops_vdec_buf_queue, + .buf_finish = vb2_ops_vdec_buf_finish, + .buf_request_complete = vb2ops_vdec_buf_request_complete, +}; + +int aml_vdec_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv; + struct aml_vdec_dev *dev = ctx->dev; + int ret = 0; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->drv_priv = ctx; + src_vq->ops = &aml_vdec_vb2_ops; + src_vq->lock = &ctx->v4l2_intf_lock; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->supports_requests = true; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + ret = vb2_queue_init(src_vq); + if (ret) { + v4l2_info(&dev->v4l2_dev, + "Failed to initialize videobuf2 queue(output)"); + return ret; + } + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->ops = &aml_vdec_vb2_ops; + dst_vq->lock = &ctx->v4l2_intf_lock; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + ret = vb2_queue_init(dst_vq); + if (ret) { + v4l2_info(&dev->v4l2_dev, + "Failed to initialize videobuf2 queue(capture)"); + vb2_queue_release(src_vq); + } + + return ret; +} diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec.h b/drivers/media/platform/amlogic/vdec/aml_vdec.h new file mode 100644 index 000000000000..32f7fa245f7e --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#ifndef _AML_VDEC_H_ +#define _AML_VDEC_H_ + +#include "aml_vdec_drv.h" + +/** + * struct aml_vdec_v4l2_ctrl - helper type to declare supported ctrls + * @codec_type: codec id this control belong to (CODEC_TYPE_H264, etc.) + * @cfg: control configuration + */ +struct aml_vdec_v4l2_ctrl { + unsigned int codec_type; + struct v4l2_ctrl_config cfg; +}; + +struct aml_dec_type { + unsigned int codec_type; + const char *name; +}; + +extern const struct v4l2_m2m_ops aml_vdec_m2m_ops; +extern const struct v4l2_ioctl_ops aml_vdec_ioctl_ops; + +int aml_vdec_ctrls_setup(struct aml_vdec_ctx *ctx); +int aml_vdec_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq); +void aml_vdec_reset_fmts(struct aml_vdec_ctx *ctx); +#endif diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c new file mode 100644 index 000000000000..d63cbd4f9e26 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include + +#include "aml_vdec.h" +#include "aml_vdec_hw.h" +#include "aml_vdec_platform.h" + +#define AML_VDEC_DRV_NAME "aml-vdec-drv" + +static int fops_vcodec_open(struct file *file) +{ + struct aml_vdec_dev *dec_dev = video_drvdata(file); + struct aml_vdec_ctx *ctx = NULL; + int ret = 0; + + ctx = kzalloc_obj(*ctx, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mutex_lock(&dec_dev->dev_mutex); + dec_dev->dec_ctx = ctx; + ctx->dev = dec_dev; + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh, file); + dec_dev->filp = file; + mutex_init(&ctx->v4l2_intf_lock); + init_waitqueue_head(&ctx->queue); + ctx->int_cond = 0; + + ctx->m2m_ctx = v4l2_m2m_ctx_init(dec_dev->m2m_dev_dec, ctx, + &aml_vdec_queue_init); + if (IS_ERR(ctx->m2m_ctx)) { + ret = PTR_ERR((__force void *)ctx->m2m_ctx); + v4l2_err(&dec_dev->v4l2_dev, "Failed to v4l2_m2m_ctx_init() (%d)", ret); + goto err_m2m_ctx_init; + } + + ctx->fh.m2m_ctx = ctx->m2m_ctx; + ret = aml_vdec_ctrls_setup(ctx); + if (ret) { + v4l2_err(&dec_dev->v4l2_dev, "Failed to init all ctrls (%d)", ret); + goto err_ctrls_setup; + } + + aml_vdec_reset_fmts(ctx); + mutex_unlock(&dec_dev->dev_mutex); + + return ret; + +err_ctrls_setup: + v4l2_m2m_ctx_release(ctx->m2m_ctx); +err_m2m_ctx_init: + v4l2_fh_del(&ctx->fh, file); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + mutex_unlock(&dec_dev->dev_mutex); + + return ret; +} + +static int fops_vcodec_release(struct file *file) +{ + struct aml_vdec_dev *dec_dev = video_drvdata(file); + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + mutex_lock(&dec_dev->dev_mutex); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_m2m_ctx_release(ctx->m2m_ctx); + v4l2_fh_del(&ctx->fh, file); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + mutex_unlock(&dec_dev->dev_mutex); + + return 0; +} + +static const struct v4l2_file_operations aml_vdec_fops = { + .owner = THIS_MODULE, + .open = fops_vcodec_open, + .release = fops_vcodec_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device dec_dev = { + .name = "aml_dev_drv", + .fops = &aml_vdec_fops, + .ioctl_ops = &aml_vdec_ioctl_ops, + .release = video_device_release, + .vfl_dir = VFL_DIR_M2M, + .device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING, +}; + +static const struct media_device_ops aml_m2m_media_ops = { + .req_validate = vb2_request_validate, + .req_queue = v4l2_m2m_request_queue, +}; + +static int aml_vdec_drv_probe(struct platform_device *pdev) +{ + struct aml_vdec_dev *dev; + struct video_device *vfd_dec; + struct aml_vdec_hw *hw; + int ret = 0; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->plat_dev = pdev; + mutex_init(&dev->dev_mutex); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + return dev_err_probe(&pdev->dev, ret, "v4l2_device_register err\n"); + + vfd_dec = video_device_alloc(); + if (!vfd_dec) { + v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto err_device_alloc; + } + *vfd_dec = dec_dev; + vfd_dec->v4l2_dev = &dev->v4l2_dev; + vfd_dec->lock = &dev->dev_mutex; + video_set_drvdata(vfd_dec, dev); + dev->vfd = vfd_dec; + platform_set_drvdata(pdev, dev); + + hw = devm_kzalloc(&pdev->dev, sizeof(*hw), GFP_KERNEL); + if (!hw) { + ret = -ENOMEM; + goto err_dec_mem_init; + } + dev->dec_hw = hw; + + dev->pvdec_data = of_device_get_match_data(&pdev->dev); + ret = dev->pvdec_data->req_hw_resource(dev); + if (ret < 0) + goto err_hw_init; + + dev->m2m_dev_dec = v4l2_m2m_init(&aml_vdec_m2m_ops); + if (IS_ERR(dev->m2m_dev_dec)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem dec device\n"); + ret = PTR_ERR((__force void *)dev->m2m_dev_dec); + goto err_hw_init; + } + + ret = video_register_device(vfd_dec, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device"); + goto err_vid_dev_register; + } + + dev->mdev.dev = &pdev->dev; + strscpy(dev->mdev.model, AML_VDEC_DRV_NAME, sizeof(dev->mdev.model)); + media_device_init(&dev->mdev); + dev->mdev.ops = &aml_m2m_media_ops; + dev->v4l2_dev.mdev = &dev->mdev; + + ret = v4l2_m2m_register_media_controller(dev->m2m_dev_dec, vfd_dec, + MEDIA_ENT_F_PROC_VIDEO_DECODER); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n"); + goto error_m2m_mc_register; + } + + ret = media_device_register(&dev->mdev); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register media device"); + goto err_media_dev_register; + } + vdec_enable(dev->dec_hw); + return 0; + +err_media_dev_register: + v4l2_m2m_unregister_media_controller(dev->m2m_dev_dec); +error_m2m_mc_register: + media_device_cleanup(&dev->mdev); +err_vid_dev_register: + v4l2_m2m_release(dev->m2m_dev_dec); +err_hw_init: + dev->dec_hw = NULL; +err_dec_mem_init: + video_device_release(vfd_dec); +err_device_alloc: + v4l2_device_unregister(&dev->v4l2_dev); + return ret; +} + +static void aml_vdec_drv_remove(struct platform_device *pdev) +{ + struct aml_vdec_dev *dev = platform_get_drvdata(pdev); + + vdec_disable(dev->dec_hw); + + if (media_devnode_is_registered(dev->mdev.devnode)) { + media_device_unregister(&dev->mdev); + media_device_cleanup(&dev->mdev); + } + + if (dev->m2m_dev_dec) + v4l2_m2m_release(dev->m2m_dev_dec); + if (dev->vfd) + video_unregister_device(dev->vfd); + if (dev->dec_hw) { + dev->pvdec_data->destroy_hw_resource(dev); + dev->dec_hw = NULL; + } + v4l2_device_unregister(&dev->v4l2_dev); +} + +static const struct of_device_id aml_vdec_match[] = { + {.compatible = "amlogic,s4-vdec", .data = &aml_vdec_s4_pdata}, + {}, +}; + +static struct platform_driver aml_vcodec_dec_driver = { + .probe = aml_vdec_drv_probe, + .remove = aml_vdec_drv_remove, + .driver = { + .name = AML_VDEC_DRV_NAME, + .of_match_table = aml_vdec_match, + }, +}; + +module_platform_driver(aml_vcodec_dec_driver); + +MODULE_DESCRIPTION("Amlogic V4L2 decoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_drv.h b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.h new file mode 100644 index 000000000000..fa86233ea5b3 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#ifndef _AML_VDEC_DRV_H_ +#define _AML_VDEC_DRV_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define AML_VCODEC_MAX_PLANES 3 +#define AML_VDEC_MIN_W 64U +#define AML_VDEC_MIN_H 64U +#define AML_VDEC_1080P_MAX_H 1088U +#define AML_VDEC_1080P_MAX_W 1920U + +struct aml_vdec_ctx; +/** + * enum aml_fmt_type - Type of format type + */ +enum aml_fmt_type { + AML_FMT_DEC = 0, + AML_FMT_FRAME = 1, +}; + +/** + * enum aml_codec_type - Type of codec format + */ +enum aml_codec_type { + CODEC_TYPE_H264 = 0, + CODEC_TYPE_FRAME, +}; + +/** + * enum aml_queue_type - Type of queue : cap or output + */ +enum aml_queue_type { + AML_FMT_SRC = 0, + AML_FMT_DST = 1, +}; + +/** + * struct aml_video_fmt - aml video decoder fmt information + * @fourcc: FourCC code of the format. See V4L2_PIX_FMT_*. + * @align: Describe the align width/height required by hardware. + * @is_10_bit_support: If the curr platform support p010 output. + * @type: Curr queue type: capture or output. + * @codec_type: Codec mode related. See aml_codec_type. + * @num_planes: Num planes of the format. + * @name: Name of the format. + * @stepwise: Supported range of frame sizes (only for bitstream formats). + */ +struct aml_video_fmt { + u32 fourcc; + int align; + int is_10_bit_support; + enum aml_fmt_type type; + enum aml_codec_type codec_type; + u32 num_planes; + const u8 *name; + struct v4l2_frmsize_stepwise stepwise; +}; + +/** + * struct aml_vdec_dev - driver data + * @plat_dev: Platform device for the current driver. + * @v4l2_dev: V4L2 device to register video devices for. + * @m2m_dev_dec: Mem2mem device associated to this device. + * @vfd: Video_device associated to this device. + * @mdev: Media_device associated to this device. + * @dec_ctx: Decoder context. See struct aml_vdec_ctx. + * @dec_hw: Decoder hardware resources. See struct aml_vdec_hw. + * @pvdec_data: Decoder platform data. See struct aml_dev_platform_data. + * @dev_mutex: video_device lock. + * @filp: v4l2 file handle pointer. + */ +struct aml_vdec_dev { + struct platform_device *plat_dev; + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m_dev_dec; + struct video_device *vfd; + struct media_device mdev; + + struct aml_vdec_ctx *dec_ctx; + struct aml_vdec_hw *dec_hw; + const struct aml_dev_platform_data *pvdec_data; + + struct mutex dev_mutex; + struct file *filp; +}; + +/** + * struct dec_pic_info - pic information description + * @cap_pix_fmt: Pixel format for capture queue. + * @output_pix_fmt: Pixel format for output queue. + * @coded_width: Width for decode. + * @coded_height: Height for decode. + * @fb_size: Frame buffer size for Y or UV. + * @plane_num: Num for planes for curr format. + */ +struct dec_pic_info { + u32 cap_pix_fmt; + u32 output_pix_fmt; + u32 coded_width; + u32 coded_height; + u32 fb_size[2]; + u32 plane_num; +}; + +/** + * struct aml_vdec_ctx - driver instance context + * @dev: pointer to the aml_vdec_dev of the device. + * @fh: struct v4l2 fh. + * @m2m_ctx: pointer to v4l2_m2m_ctx context. + * @ctrl_handler: V4L2 ctrl handler. + * @v4l2_intf_lock: Mutex lock for v4l2 interface. + * @codec_ops: Codec operation functions. See struct aml_codec_ops. + * @int_cond: Variable used by the waitqueue. + * @queue: Waitqueue to wait for the current decode context finish. + * @pix_fmt: To store the V4L2 pixel format. + * @dec_fmt: To describe the decoding format supported by hardware platform. + * @is_cap_streamon: indicates if the current capture stream is on. + * @is_output_streamon: indicates if the current output stream is on. + * @dos_clk_en: indicates if dos clk is enabled. + * @pic_info: Pic information for curr decoder context. See struct dec_pic_info. + * @curr_dec_type: Current decoder type. (CODEC_TYPE_H264, etc.) + * @codec_priv: Pointer to current decoder instance. + */ +struct aml_vdec_ctx { + struct aml_vdec_dev *dev; + struct v4l2_fh fh; + struct v4l2_m2m_ctx *m2m_ctx; + struct v4l2_ctrl_handler ctrl_handler; + struct mutex v4l2_intf_lock; + + const struct aml_codec_ops *codec_ops; + int int_cond; + wait_queue_head_t queue; + struct v4l2_pix_format_mplane pix_fmt[2]; + struct aml_video_fmt dec_fmt[2]; + + bool is_cap_streamon; + bool is_output_streamon; + bool dos_clk_en; + + struct dec_pic_info pic_info; + u32 curr_dec_type; + void *codec_priv; +}; + +static inline struct aml_vdec_ctx *fh_to_dec_ctx(struct file *file) +{ + struct v4l2_fh *file_fh = file_to_v4l2_fh(file); + + return container_of(file_fh, struct aml_vdec_ctx, fh); +} + +static inline struct aml_vdec_ctx *ctrl_to_dec_ctx(struct v4l2_ctrl_handler *ctrl) +{ + return container_of(ctrl, struct aml_vdec_ctx, ctrl_handler); +} + +#endif diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c new file mode 100644 index 000000000000..79ba2a68bde4 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c @@ -0,0 +1,538 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aml_vdec_drv.h" +#include "aml_vdec_hw.h" +#include "aml_vdec_platform.h" +#include "aml_vdec_tee_fw.h" + +#define MHz (1000000) +#define MC_SIZE (4096 * 16) + +static struct pm_pd_s vdec_domain_data[] = { + [VDEC_1] = {.name = "vdec", }, + [VDEC_HEVC] = {.name = "hevc", }, +}; + +u32 read_dos_reg(struct aml_vdec_hw *hw, u32 addr) +{ + u32 ret_val; + + regmap_read(hw->map[DOS_BUS], addr, &ret_val); + + return ret_val; +} + +static void dos_reg_write_bits(struct aml_vdec_hw *hw, u32 reg, u32 val, int start, int len) +{ + u32 mask = (((1L << (len)) - 1) << (start)); + + regmap_update_bits(hw->map[DOS_BUS], reg, mask, val); +} + +void dos_enable(struct aml_vdec_hw *hw) +{ + dos_reg_write_bits(hw, DOS_GCLK_EN0, 0x3ff, 0, 10); + + regmap_write(hw->map[DOS_BUS], GCLK_EN, 0x3ff); + + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 31), 0); +} + +void aml_vdec_reset_core(struct aml_vdec_hw *hw) +{ + unsigned int mask = 0; + + mask = (1 << 21); + + regmap_update_bits(hw->map[DMC_BUS], 0x0, mask, 0); + usleep_range(60, 70); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, + (1 << 3) | (1 << 4) | (1 << 5) | (1 << 7) | + (1 << 8) | (1 << 9)); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0); + regmap_update_bits(hw->map[DOS_BUS], VDEC_ASSIST_MMC_CTRL1, 1 << 3, 0); + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_MUX_CTRL, 1 << 31, 0); + regmap_write(hw->map[DOS_BUS], MDEC_EXTIF_CFG1, 0); + + regmap_update_bits(hw->map[DMC_BUS], 0x0, mask, mask); +} + +void aml_start_vdec_hw(struct aml_vdec_hw *hw) +{ + u32 reg_read_val; + + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val); + + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, (1 << 12) | (1 << 11)); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0); + + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val); + + regmap_write(hw->map[DOS_BUS], MPSR, 0x0001); +} + +void aml_stop_vdec_hw(struct aml_vdec_hw *hw) +{ + u32 reg_val = 0; + int ret; + + regmap_write(hw->map[DOS_BUS], MPSR, 0); + regmap_write(hw->map[DOS_BUS], CPSR, 0); + + ret = read_poll_timeout_atomic(read_dos_reg, reg_val, + !(reg_val & 0x8000), + 10, 100000, true, + hw, IMEM_DMA_CTRL); + + ret = read_poll_timeout_atomic(read_dos_reg, reg_val, + !(reg_val & 0x8000), + 10, 100000, true, + hw, LMEM_DMA_CTRL); + + ret = read_poll_timeout_atomic(read_dos_reg, reg_val, + !(reg_val & 0xfff), + 10, 800000, true, + hw, WRRSP_LMEM); + if (ret) + dev_err(hw->dev, "%s, ctrl %x, rsp %x, pc %x status %x,%x\n", + __func__, read_dos_reg(hw, LMEM_DMA_CTRL), + read_dos_reg(hw, WRRSP_LMEM), read_dos_reg(hw, MPC_E), + read_dos_reg(hw, AV_SCRATCH_J), read_dos_reg(hw, AV_SCRATCH_9)); + + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val); + + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, (1 << 12) | (1 << 11)); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0); + + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val); +} + +static int vdec_clock_gate_init(struct aml_vdec_hw *hw) +{ + hw->gates[CLK_DOS].id = "dos"; + hw->gates[CLK_VDEC].id = "vdec"; + hw->gates[CLK_HEVCF].id = "hevcf"; + + return devm_clk_bulk_get(hw->dev, CLK_MAX, hw->gates); +} + +static struct clk_bulk_data *vdec_get_clk_by_name(struct aml_vdec_hw *hw, + const char *name) +{ + int i; + + for (i = 0; i < CLK_MAX; i++) { + if (!strcmp(name, hw->gates[i].id)) { + if (hw->gates[i].clk) + return &hw->gates[i]; + } + } + return NULL; +} + +static int pm_vdec_power_domain_init(struct aml_vdec_hw *hw) +{ + int i, err; + const struct power_manager_s *pm = hw->pm; + struct pm_pd_s *pd = pm->pd_data; + + mutex_init(&hw->pm_mutex); + + for (i = 0; i < VDEC_MAX; i++) { + pd[i].dev = dev_pm_domain_attach_by_name(hw->dev, pd[i].name); + if (IS_ERR_OR_NULL(pd[i].dev)) { + err = PTR_ERR(pd[i].dev); + dev_dbg(hw->dev, "Get %s failed, pm-domain: %d\n", + pd[i].name, err); + continue; + } + + pd[i].link = device_link_add(hw->dev, pd[i].dev, + DL_FLAG_PM_RUNTIME | + DL_FLAG_STATELESS); + if (IS_ERR_OR_NULL(pd[i].link)) { + dev_err(hw->dev, "Adding %s device link failed!\n", pd[i].name); + return -ENODEV; + } + + dev_dbg(hw->dev, "power domain: name: %s, dev: %p, link: %p\n", + pd[i].name, pd[i].dev, pd[i].link); + } + + return 0; +} + +static void pm_vdec_power_domain_release(struct aml_vdec_hw *hw) +{ + int i; + const struct power_manager_s *pm = hw->pm; + struct pm_pd_s *pd = pm->pd_data; + + for (i = 0; i < VDEC_MAX; i++) { + if (!IS_ERR_OR_NULL(pd[i].link)) + device_link_del(pd[i].link); + + if (!IS_ERR_OR_NULL(pd[i].dev)) + dev_pm_domain_detach(pd[i].dev, true); + } +} + +static void dos_local_config(struct aml_vdec_hw *hw, bool is_on, int id) +{ + if (is_on) { + usleep_range(20, 100); + + switch (id) { + case VDEC_1: + regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_VDEC, 0); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0xfffffffc); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_VDEC, 0); + break; + case VDEC_HEVC: + regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_HEVC, 0); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET3, 0xffffffff); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET3, 0); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_HEVC, 0); + break; + default: + dev_info(hw->dev, "%s on, not found id %d\n", __func__, id); + break; + } + } else { + switch (id) { + case VDEC_1: + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0xfffffffc); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_VDEC, 0xffffffffUL); + break; + case VDEC_HEVC: + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET3, 0xffffffff); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET3, 0); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_HEVC, 0xffffffffUL); + break; + default: + dev_info(hw->dev, "%s off, not found id %d\n", __func__, id); + break; + } + } + + dev_dbg(hw->dev, "%s end, id %d, is_on %d\n", __func__, id, is_on); +} + +static void pm_vdec_power_domain_power_on(struct aml_vdec_hw *hw, int id) +{ + const struct power_manager_s *pm = hw->pm; + struct device *dev = pm->pd_data[id].dev; + struct clk_bulk_data *gate_node = NULL; + + if (id == VDEC_1) + gate_node = vdec_get_clk_by_name(hw, "vdec"); + else if (id == VDEC_HEVC) + gate_node = vdec_get_clk_by_name(hw, "hevcf"); + + if (gate_node) { + clk_prepare_enable(gate_node->clk); + if (id == VDEC_1) { + clk_set_rate(gate_node->clk, 499999992); + dev_dbg(hw->dev, "after set, vdec clock is %lu Hz\n", + clk_get_rate(gate_node->clk)); + } + dev_dbg(hw->dev, "the %-15s clock on\n", gate_node->id); + } else { + dev_info(hw->dev, "clk %d, unreachable\n", id); + } + + if (dev) { + pm_runtime_get_sync(dev); + dev_dbg(dev, "dev: %p link %p the %-15s power on\n", + dev, pm->pd_data[id].link, pm->pd_data[id].name); + } + + dos_local_config(hw, 1, id); +} + +static void pm_vdec_power_domain_power_off(struct aml_vdec_hw *hw, int id) +{ + const struct power_manager_s *pm = hw->pm; + struct device *dev = pm->pd_data[id].dev; + struct clk_bulk_data *gate_node = NULL; + + dos_local_config(hw, 0, id); + + if (dev) { + pm_runtime_put_sync(dev); + dev_dbg(dev, "dev: %p link %p the %-15s power off\n", + dev, pm->pd_data[id].link, pm->pd_data[id].name); + } + + if (id == VDEC_1) + gate_node = vdec_get_clk_by_name(hw, "vdec"); + else if (id == VDEC_HEVC) + gate_node = vdec_get_clk_by_name(hw, "hevcf"); + + if (gate_node) { + clk_disable_unprepare(gate_node->clk); + dev_dbg(hw->dev, "the %-15s clock off\n", gate_node->id); + } else { + dev_info(hw->dev, "clk %d, unreachable\n", id); + } +} + +static bool pm_vdec_power_domain_power_state(struct aml_vdec_hw *hw, int id) +{ + if (hw->pm->pd_data[id].dev) + return pm_runtime_active(hw->pm->pd_data[id].dev); + else + return false; +} + +static void vdec_poweron(struct aml_vdec_hw *hw, enum vdec_type_e core) +{ + if (core >= VDEC_MAX) + return; + + mutex_lock(&hw->pm_mutex); + if (!hw->pm->pd_data[core].dev) + goto out; + + hw->pm->pd_data[core].ref_count++; + if (hw->pm->pd_data[core].ref_count > 1) + goto out; + + if (hw->pm->power_state(hw, core)) + goto out; + + hw->pm->power_on(hw, core); + +out: + mutex_unlock(&hw->pm_mutex); +} + +static void vdec_poweroff(struct aml_vdec_hw *hw, enum vdec_type_e core) +{ + if (core >= VDEC_MAX) + return; + + mutex_lock(&hw->pm_mutex); + if (hw->pm->pd_data[core].ref_count == 0) + goto out; + + hw->pm->pd_data[core].ref_count--; + if (hw->pm->pd_data[core].ref_count > 0) + goto out; + + hw->pm->power_off(hw, core); + +out: + mutex_unlock(&hw->pm_mutex); +} + +int vdec_enable(struct aml_vdec_hw *hw) +{ + vdec_poweron(hw, VDEC_1); + + return 0; +} + +int vdec_disable(struct aml_vdec_hw *hw) +{ + vdec_poweroff(hw, VDEC_1); + + return 0; +} + +static const struct power_manager_s pm[] = { + [AML_PM_PD] = { + .pd_data = vdec_domain_data, + .init = pm_vdec_power_domain_init, + .release = pm_vdec_power_domain_release, + .power_on = pm_vdec_power_domain_power_on, + .power_off = pm_vdec_power_domain_power_off, + .power_state = pm_vdec_power_domain_power_state, + }, +}; + +static irqreturn_t vdec_irq_handler(int irq, void *priv) +{ + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + struct aml_vdec_hw *hw = dev->dec_hw; + irqreturn_t ret = IRQ_HANDLED; + + if (hw->hw_ops.irq_handler) + ret = hw->hw_ops.irq_handler(irq, priv); + + return ret; +} + +static irqreturn_t vdec_threaded_isr_handler(int irq, void *priv) +{ + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + struct aml_vdec_hw *hw = dev->dec_hw; + irqreturn_t ret = IRQ_HANDLED; + + if (hw->hw_ops.irq_threaded_func) + ret = hw->hw_ops.irq_threaded_func(irq, priv); + + return ret; +} + +struct aml_vdec_hw *vdec_get_hw(void *priv) +{ + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + + return dev->dec_hw; +} + +static const struct regmap_config dos_regmap_conf = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x10000, +}; + +static const struct regmap_config dmc_regmap_conf = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x20, +}; + +int dev_request_hw_resources(void *priv) +{ + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + struct aml_vdec_hw *hw; + struct platform_device *pdev; + struct device_node *sm_np; + void __iomem *reg_base[MAX_REG_BUS]; + struct resource res; + int i; + int ret = -1; + + if (!dev || !dev->dec_hw) + return -1; + + pdev = dev->plat_dev; + hw = dev->dec_hw; + hw->dev = &pdev->dev; + + hw->dec_irq = platform_get_irq(pdev, VDEC_IRQ_1); + if (hw->dec_irq < 0) { + dev_err(&pdev->dev, "get irq failed\n"); + return hw->dec_irq; + } + ret = devm_request_threaded_irq(&pdev->dev, hw->dec_irq, vdec_irq_handler, + vdec_threaded_isr_handler, IRQF_ONESHOT, + "vdec-1", dev); + if (ret) { + dev_err(&pdev->dev, "failed to install irq %d (%d)", + hw->dec_irq, ret); + return -1; + } + + for (i = 0; i < MAX_REG_BUS; i++) { + if (of_address_to_resource(pdev->dev.of_node, i, &res)) { + dev_err(&pdev->dev, "of_address_to_resource %d failed\n", i); + return -EINVAL; + } + reg_base[i] = devm_ioremap_resource(&pdev->dev, &res); + + if (IS_ERR(reg_base[i])) + return PTR_ERR(reg_base[i]); + + if (i == DOS_BUS) { + hw->map[i] = devm_regmap_init_mmio(&pdev->dev, reg_base[i], + &dos_regmap_conf); + } else if (i == DMC_BUS) { + hw->map[i] = devm_regmap_init_mmio(&pdev->dev, reg_base[i], + &dmc_regmap_conf); + } + + if (IS_ERR(hw->map[i])) + return PTR_ERR(hw->map[i]); + + dev_dbg(&pdev->dev, "%s, res start %llx, end %llx, iomap: %p\n", + __func__, (unsigned long long)res.start, + (unsigned long long)res.end, reg_base[i]); + } + hw->canvas = meson_canvas_get(&pdev->dev); + if (IS_ERR(hw->canvas)) + return PTR_ERR(hw->canvas); + + sm_np = of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0); + if (IS_ERR(sm_np)) + return PTR_ERR(hw->canvas); + + hw->sec_fw = meson_sm_get(sm_np); + of_node_put(sm_np); + if (IS_ERR(hw->sec_fw)) + return PTR_ERR(hw->sec_fw); + + hw->pm = &pm[dev->pvdec_data->power_type]; + if (hw->pm->init) { + ret = hw->pm->init(hw); + if (ret < 0) { + dev_err(&pdev->dev, "power mgr init failed!\n"); + return ret; + } + } + + ret = vdec_clock_gate_init(hw); + if (ret) { + dev_err(&pdev->dev, "clk bulk init failed!\n"); + return ret; + } + + ret = aml_tee_fw_preload(hw); + if (ret) + return ret; + + dev_dbg(&pdev->dev, "##Amlogic hw resource init OK##\n"); + + return 0; +} + +void dev_destroy_hw_resources(void *priv) +{ + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + struct aml_vdec_hw *hw; + + if (!dev || !dev->dec_hw) + return; + + hw = dev->dec_hw; + + if (hw->pm->release) + hw->pm->release(hw); + + dev_dbg(hw->dev, "##Amlogic hw resource release OK##\n"); +} diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_hw.h b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.h new file mode 100644 index 000000000000..443f48226239 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#ifndef _AML_VDEC_HW_H_ +#define _AML_VDEC_HW_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "reg_defines.h" + +#define VDEC_FIFO_ALIGN 8 +#define VLD_PADDING_SIZE 1024 + +/** + * struct aml_vdec_hw_ops - codec mode specific operations for hw + * @load_firmware_ex: Load firmware for current dec specific. + * @irq_handler: mandatory call when the ISR triggers + * @irq_threaded_func: mandatory call for the threaded ISR + * @canvas_alloc: Alloc canvas for curr frame + * @canvas_free: Release canvas. + * @config_canvas: Config for curr frame, such as w/h, Y/UV start addr etc. + */ +struct aml_vdec_hw_ops { + int (*load_firmware_ex)(void *priv, const u8 *data, u32 len); + irqreturn_t (*irq_handler)(int irq, void *priv); + irqreturn_t (*irq_threaded_func)(int irq, void *priv); + int (*canvas_alloc)(u8 *canvas_index); + void (*canvas_free)(u8 canvas_index); + void (*config_canvas)(u8 canvas_index, + ulong addr, u32 width, u32 height, + u32 wrap, u32 blkmode, u32 endian); +}; + +/** + * enum vdec_type_e - Type of decoder hardware. + */ +enum vdec_type_e { + VDEC_1 = 0, + VDEC_HEVC, + VDEC_MAX, +}; + +/** + * enum vdec_irq_num - Definition of the irq. + */ +enum vdec_irq_num { + VDEC_IRQ_0 = 0, + VDEC_IRQ_1, + VDEC_IRQ_2, + VDEC_IRQ_MAX, +}; + +/** + * enum vdec_type_e - Type of decoder clock. + */ +enum clk_type_e { + CLK_DOS = 0, + CLK_VDEC, + CLK_HEVCF, + CLK_MAX, +}; + +/** + * enum aml_power_type_e - Type of decoder power. + */ +enum aml_power_type_e { + AML_PM_PD = 0, +}; + +/** + * enum mm_bus_e - Type of decoder hardware bus. + */ +enum mm_bus_e { + DOS_BUS = 0, + DMC_BUS, + MAX_REG_BUS +}; + +/** + * struct pm_pd_s - power domain definition + * @name: Power domain name. + * @dev: Pointer to device structure. + * @mutex: Pointer to device_link structure. + * @ref_count: Curr power domain instance ref count. + */ +struct pm_pd_s { + u8 *name; + struct device *dev; + struct device_link *link; + int ref_count; +}; + +/** + * struct aml_vdec_hw - decoder hardware resources definition + * @pdev: Pointer to struct platform_device. + * @dev: Pointer to struct device. + * @regs: Reg base for dos/dmc hardware. + * @pm_mutex: Mutex for pm->pd_data. + * @pm: Pointer to struct power_manager_s. + * @hw_ops: Hardware resource operation functions. See struct aml_vdec_hw_ops. + * @gates: Clk instance used by curr decoder context. + * @dec_irq: Irq registered. + * @curr_ctx: Pointer to curr decoder context. + */ +struct aml_vdec_hw { + struct platform_device *pdev; + struct device *dev; + struct regmap *map[MAX_REG_BUS]; + struct mutex pm_mutex; + struct meson_canvas *canvas; + struct meson_sm_firmware *sec_fw; + const struct power_manager_s *pm; + struct aml_vdec_hw_ops hw_ops; + struct clk_bulk_data gates[CLK_MAX]; + int dec_irq; + void *curr_ctx; +}; + +/** + * struct power_manager_s - Power manager & opertion function + * @pd_data: Pointer to struct pm_pd_s + * @init: Power manager init. + * @release: Power manager release. + * @power_on: Power on for decoder hw. + * @power_off: Power off for decoder hw. + * @power_state: Query the power state. + */ +struct power_manager_s { + struct pm_pd_s *pd_data; + int (*init)(struct aml_vdec_hw *hw); + void (*release)(struct aml_vdec_hw *hw); + void (*power_on)(struct aml_vdec_hw *hw, int id); + void (*power_off)(struct aml_vdec_hw *hw, int id); + bool (*power_state)(struct aml_vdec_hw *hw, int id); +}; + +int dev_request_hw_resources(void *priv); +void dev_destroy_hw_resources(void *priv); +struct aml_vdec_hw *vdec_get_hw(void *priv); +u32 read_dos_reg(struct aml_vdec_hw *hw, u32 reg_addr); +int vdec_enable(struct aml_vdec_hw *hw); +int vdec_disable(struct aml_vdec_hw *hw); +void dos_enable(struct aml_vdec_hw *hw); +void aml_start_vdec_hw(struct aml_vdec_hw *hw); +void aml_stop_vdec_hw(struct aml_vdec_hw *hw); +void aml_vdec_reset_core(struct aml_vdec_hw *hw); + +#endif diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_platform.c b/drivers/media/platform/amlogic/vdec/aml_vdec_platform.c new file mode 100644 index 000000000000..60d20efb1d74 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_platform.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#include "aml_vdec_platform.h" +#include "aml_vdec_hw.h" +#include "h264.h" + +static struct aml_video_fmt aml_s4_video_formats[] = { + { + .name = "H.264", + .fourcc = V4L2_PIX_FMT_H264_SLICE, + .type = AML_FMT_DEC, + .align = 64, + .is_10_bit_support = 0, + .codec_type = CODEC_TYPE_H264, + .num_planes = 1, + .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2, + AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2}, + }, + { + .name = "NV21M", + .fourcc = V4L2_PIX_FMT_NV21M, + .type = AML_FMT_FRAME, + .align = 64, + .codec_type = CODEC_TYPE_FRAME, + .num_planes = 2, + .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2, + AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2}, + }, + { + .name = "NV21", + .fourcc = V4L2_PIX_FMT_NV21, + .type = AML_FMT_FRAME, + .align = 64, + .codec_type = CODEC_TYPE_FRAME, + .num_planes = 1, + .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2, + AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2}, + }, + { + .name = "NV12M", + .fourcc = V4L2_PIX_FMT_NV12M, + .type = AML_FMT_FRAME, + .align = 64, + .codec_type = CODEC_TYPE_FRAME, + .num_planes = 2, + .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2, + AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2}, + + }, + { + .name = "NV12", + .fourcc = V4L2_PIX_FMT_NV12, + .type = AML_FMT_FRAME, + .align = 64, + .codec_type = CODEC_TYPE_FRAME, + .num_planes = 1, + .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2, + AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2}, + }, +}; + +const struct aml_codec_ops aml_S4_dec_ops[] = { + [CODEC_TYPE_H264] = { + .init = aml_h264_init, + .exit = aml_h264_exit, + .run = aml_h264_dec_run, + }, +}; + +const struct aml_dev_platform_data aml_vdec_s4_pdata = { + .codec_ops = aml_S4_dec_ops, + .dec_fmt = aml_s4_video_formats, + .num_fmts = ARRAY_SIZE(aml_s4_video_formats), + .power_type = AML_PM_PD, + .req_hw_resource = dev_request_hw_resources, + .destroy_hw_resource = dev_destroy_hw_resources, +}; + diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_platform.h b/drivers/media/platform/amlogic/vdec/aml_vdec_platform.h new file mode 100644 index 000000000000..a167abfe7b51 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_platform.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#ifndef AML_VDEC_PLATFORM_H_ +#define AML_VDEC_PLATFORM_H_ + +#include +#include "aml_vdec_drv.h" + +/** + * struct aml_codec_ops - codec mode specific operations + * @init: Used for decoder initialization. + * @exit: If needed, can be used to undo the .init phase. + * @run: Start a single decoding job. Called from atomic context. + * Caller should ensure that a pair of buffers is ready and the + * hardware is powered on and clk is enabled. Returns zero if OK, + * a negative value in error cases. + */ +struct aml_codec_ops { + int (*init)(void *priv); + void (*exit)(void *priv); + int (*run)(void *priv); +}; + +/** + * struct aml_dev_platform_data - compatible data for each chip. + * @dec_fmt: Support dec format. + * @codec_ops: Codec operation function. + * @req_hw_resource: Operation function to request the hardware resource. + * @destroy_hw_resource: Operation function to release the hardware resource. + * @power_type: Type of power that the current chip need. See aml_power_type_e. + */ +struct aml_dev_platform_data { + const struct aml_codec_ops *codec_ops; + const struct aml_video_fmt *dec_fmt; + int num_fmts; + int (*req_hw_resource)(void *priv); + void (*destroy_hw_resource)(void *priv); + int power_type; +}; + +extern const struct aml_dev_platform_data aml_vdec_s4_pdata; + +#endif diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c new file mode 100644 index 000000000000..ad4156249f55 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ +#include +#include + +#include "aml_vdec_tee_fw.h" +#include "aml_vdec_drv.h" + +#define VIDEO_DEC_H264_MULTI 15 + +#define CORE_VDEC_LEGENCY 0 + +#define FIRMWARE_PATH "video_ucode.bin" +#define ONCE_SENT_SIZE (1024 * 128) +#define UCODE_HEADER_SIZE (1024 * 32) + +#define TEEC_SUCCESS 0x0 +#define TEEC_ERROR_BUSY 0xffff000d +#define FIRMWARE_CMD_PROCESS 0 + +#define TEE_SMC_FUNCID_LOAD_VIDEO_FW 15 +#define TEE_SMC_LOAD_VIDEO_FW \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_TRUSTED_OS, TEE_SMC_FUNCID_LOAD_VIDEO_FW) + +#define PTA_LOAD_FW UUID_INIT(0x526fc4fc, 0x7ee6, 0x4a12, \ + 0x96, 0xe3, 0x83, 0xda, 0x95, 0x65, 0xbc, 0xe8) + +#define TEE_PARAM_NUM 4 + +static struct aml_tee_fw firmware[] = { + [CODEC_TYPE_H264] = { + .fw_format = VIDEO_DEC_H264_MULTI, + .core = CORE_VDEC_LEGENCY, + .is_swap = 1, + }, +}; + +static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) +{ + return (ver->impl_id == TEE_IMPL_ID_OPTEE); +} + +static void prepare_tee_grgs(size_t firmware_size, struct tee_param *param0, + struct tee_param *param1) +{ + memset(param0, 0, TEE_PARAM_NUM * sizeof(*param0)); + memset(param1, 0, TEE_PARAM_NUM * sizeof(*param1)); + + param0[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; + param0[0].u.value.a = firmware_size; + + param0[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; + + param0[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + param0[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + + param1[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; + param1[0].u.memref.size = firmware_size; + + param1[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + param1[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + param1[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; +} + +static int tee_pta_invoke_cmd(struct aml_vdec_hw *hw, struct tee_context *ctx, + uuid_t uuid, u32 cmd, void *firmware_data, + struct tee_param *param_init, + struct tee_param *param_invoke) +{ + int ret = 0; + struct tee_ioctl_open_session_arg sess_arg = { 0 }; + struct tee_ioctl_invoke_arg inv_arg = { 0 }; + u32 sent_size = 0; + u32 fw_size = 0; + struct tee_shm *shm = NULL; + void *shm_va = NULL; + + fw_size = param_invoke[0].u.memref.size; + + shm = tee_shm_alloc_kernel_buf(ctx, ONCE_SENT_SIZE); + if (IS_ERR(shm)) { + dev_info(hw->dev, "Failed to allocate shared memory size %d\n", + ONCE_SENT_SIZE); + ret = PTR_ERR(shm); + goto out; + } + + shm_va = tee_shm_get_va(shm, 0); + if (IS_ERR(shm_va)) { + dev_info(hw->dev, "Failed to get VA for shared memory\n"); + ret = PTR_ERR(shm_va); + goto free_shm; + } + + /* Open session */ + memcpy(sess_arg.uuid, uuid.b, TEE_IOCTL_UUID_LEN); + sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC; + sess_arg.num_params = TEE_PARAM_NUM; + ret = tee_client_open_session(ctx, &sess_arg, param_init); + if (ret < 0 || sess_arg.ret != TEEC_SUCCESS) { + dev_info(hw->dev, + "%s open session failed, cmd = %u, ret = %d, res = 0x%x, origin = 0x%x\n", + __func__, cmd, ret, sess_arg.ret, sess_arg.ret_origin); + ret = sess_arg.ret; + goto free_shm; + } + + inv_arg.func = cmd; + inv_arg.session = sess_arg.session; + inv_arg.num_params = TEE_PARAM_NUM; + + while (sent_size < fw_size) { + memset(shm_va, 0, ONCE_SENT_SIZE); + if (fw_size - sent_size > ONCE_SENT_SIZE) { + memcpy(shm_va, (firmware_data + sent_size), + ONCE_SENT_SIZE); + param_invoke[0].u.memref.size = ONCE_SENT_SIZE; + } else { + memcpy(shm_va, (firmware_data + sent_size), + fw_size - sent_size); + param_invoke[0].u.memref.size = (fw_size - sent_size); + } + param_invoke[0].u.memref.shm = shm; + ret = tee_client_invoke_func(ctx, &inv_arg, param_invoke); + if (ret < 0 || (inv_arg.ret != TEEC_SUCCESS && inv_arg.ret != TEEC_ERROR_BUSY)) { + dev_info(hw->dev, + "%s invoke func failed, cmd = %u, ret= %d, res = 0x%x, origin = 0x%x\n", + __func__, cmd, ret, inv_arg.ret, + inv_arg.ret_origin); + ret = inv_arg.ret; + goto close_session; + } + sent_size += param_invoke[0].u.memref.size; + } +close_session: + tee_client_close_session(ctx, sess_arg.session); +free_shm: + tee_shm_free(shm); +out: + return ret; +} + +int load_firmware(struct aml_vdec_hw *hw, u32 type) +{ + int ret = -1; + struct aml_tee_fw *video_fw; + + if (type >= CODEC_TYPE_FRAME) { + dev_info(hw->dev, "codec type %d invalid\n", type); + return ret; + } + video_fw = &firmware[type]; + + meson_sm_call(hw->sec_fw, SM_LOAD_VIDEO_FW, &ret, + video_fw->fw_format, video_fw->core, + video_fw->is_swap, 0, 0); + if (ret < 0) + dev_err(hw->dev, "loading fw type %d core %d, ret %x\n", + video_fw->fw_format, video_fw->core, ret); + + return ret; +} + +static int get_firmware(const char *path, void **data, size_t *size) +{ + const struct firmware *fw = NULL; + int ret; + void *buf; + + ret = request_firmware(&fw, FIRMWARE_PATH, NULL); + if (ret) + return ret; + + /* get rid of the first 32K bytes plaintext */ + buf = kzalloc((fw->size - UCODE_HEADER_SIZE), GFP_KERNEL); + if (!buf) { + release_firmware(fw); + return -ENOMEM; + } + + memcpy(buf, fw->data + UCODE_HEADER_SIZE, fw->size - UCODE_HEADER_SIZE); + release_firmware(fw); + + *data = buf; + *size = fw->size - UCODE_HEADER_SIZE; + + return 0; +} + +static int pass_firmware_to_tee(struct aml_vdec_hw *hw) +{ + int ret; + struct tee_context *ctx = NULL; + uuid_t uuid = PTA_LOAD_FW; + struct tee_param param_init[TEE_PARAM_NUM]; + struct tee_param param_invoke[TEE_PARAM_NUM]; + void *firmware_data; + size_t firmware_size; + + ret = get_firmware(FIRMWARE_PATH, &firmware_data, &firmware_size); + if (ret) { + dev_info(hw->dev, "Failed get firmware %s from FS\n", + FIRMWARE_PATH); + return ret; + } + + ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL); + if (IS_ERR(ctx)) { + dev_info(hw->dev, "Failed to open TEE context\n"); + ret = PTR_ERR(ctx); + goto free_firmware; + } + + prepare_tee_grgs(firmware_size, param_init, param_invoke); + + ret = tee_pta_invoke_cmd(hw, ctx, uuid, FIRMWARE_CMD_PROCESS, + firmware_data, param_init, param_invoke); + if (ret) + dev_info(hw->dev, "TEE firmware processing failed, ret = %d\n", + ret); + + tee_client_close_context(ctx); +free_firmware: + kfree(firmware_data); + return ret; +} + +int aml_tee_fw_preload(struct aml_vdec_hw *hw) +{ + int ret; + + ret = pass_firmware_to_tee(hw); + if (ret) + dev_err(hw->dev, "Failed to preload firmware via TEE\n"); + + return ret; +} diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.h b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.h new file mode 100644 index 000000000000..04b47e8f8654 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#ifndef AML_VDEC_TEE_FW_H_ +#define AML_VDEC_TEE_FW_H_ + +#include "aml_vdec_hw.h" + +/** + * struct aml_tee_fw - specify the firmware format for each dec type + * @fw_format: Specify firmware format for current decoder. + * @core: Specify which hardware core is needed. + * @is_swap: Specify if the swap memory is needed. + */ +struct aml_tee_fw { + u32 fw_format; + u32 core; + u32 is_swap; +}; + +int aml_tee_fw_preload(struct aml_vdec_hw *hw); +int load_firmware(struct aml_vdec_hw *hw, u32 type); + +#endif + diff --git a/drivers/media/platform/amlogic/vdec/h264.c b/drivers/media/platform/amlogic/vdec/h264.c new file mode 100644 index 000000000000..bd3aef44409f --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/h264.c @@ -0,0 +1,2128 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include "aml_vdec.h" +#include "aml_vdec_hw.h" +#include "h264.h" + +#define INVALID_POC 0xffffffff + +#define H264_SLICE_HEADER_DONE 0x1 +#define H264_SLICE_DATA_DONE 0x2 + +#define H264_MAX_COL_BUF 32 +#define H264_MAX_CANVAS_POS 26 + +#define DECODER_TIMEOUT_MS 500 + +#define COL_BUFFER_MARGIN 2 +#define COL_SIZE_FOR_ONE_MB 96 + +struct vdec_h264_stateless_ctrl_ref { + const struct v4l2_ctrl_h264_decode_params *decode; + const struct v4l2_ctrl_h264_scaling_matrix *scaling; + const struct v4l2_ctrl_h264_sps *sps; + const struct v4l2_ctrl_h264_pps *pps; +}; + +enum SliceType { + P_SLICE = 0, + B_SLICE = 1, + I_SLICE = 2, + SP_SLICE = 3, + SI_SLICE = 4, + MAX_SLICE_TYPES = 5 +}; + +#define I_Slice 2 +#define P_Slice 5 +#define B_Slice 6 +#define P_Slice_0 0 +#define B_Slice_1 1 +#define I_Slice_7 7 + +/* Used by firmware */ +union param { + struct { + unsigned short data[RPM_END - RPM_BEGIN]; + } l; + struct { + unsigned short dump[DPB_OFFSET]; + unsigned short dpb_base[FRAME_IN_DPB << 3]; + + unsigned short dpb_max_buffer_frame; + unsigned short actual_dpb_size; + + unsigned short colocated_buf_status; + + unsigned short num_forward_short_term_reference_pic; + unsigned short num_short_term_reference_pic; + unsigned short num_reference_pic; + + unsigned short current_dpb_index; + unsigned short current_decoded_frame_num; + unsigned short current_reference_frame_num; + + unsigned short l0_size; + unsigned short l1_size; + /** + * [6:5] : nal_ref_idc + * [4:0] : nal_unit_type + */ + unsigned short NAL_info_mmco; + /** + * [1:0] : 00 - top field, 01 - bottom field, + * 10 - frame, 11 - mbaff frame + */ + unsigned short picture_structure_mmco; + unsigned short frame_num; + unsigned short pic_order_cnt_lsb; + + unsigned short num_ref_idx_l0_active_minus1; + unsigned short num_ref_idx_l1_active_minus1; + + unsigned short PrevPicOrderCntLsb; + unsigned short PreviousFrameNum; + + /* 32 bits variables */ + unsigned short delta_pic_order_cnt_bottom[2]; + unsigned short delta_pic_order_cnt_0[2]; + unsigned short delta_pic_order_cnt_1[2]; + + unsigned short PrevPicOrderCntMsb[2]; + unsigned short PrevFrameNumOffset[2]; + + unsigned short frame_pic_order_cnt[2]; + unsigned short top_field_pic_order_cnt[2]; + unsigned short bottom_field_pic_order_cnt[2]; + + unsigned short colocated_mv_addr_start[2]; + unsigned short colocated_mv_addr_end[2]; + unsigned short colocated_mv_wr_addr[2]; + } dpb; + struct { + unsigned short dump[MMCO_OFFSET]; + + /* array base address for offset_for_ref_frame */ + unsigned short offset_for_ref_frame_base[128]; + + /** + * 0 - Index in DPB + * 1 - Picture Flag + * [2] : 0 - short term reference, + * 1 - long term reference + * [1] : bottom field + * [0] : top field + * 2 - Picture Number (short term or long term) low 16 bits + * 3 - Picture Number (short term or long term) high 16 bits + */ + unsigned short reference_base[128]; + + /* command and parameter, until command is 3 */ + unsigned short l0_reorder_cmd[REORDER_CMD_MAX]; + unsigned short l1_reorder_cmd[REORDER_CMD_MAX]; + + /* command and parameter, until command is 0 */ + unsigned short mmco_cmd[44]; + + unsigned short l0_base[40]; + unsigned short l1_base[40]; + } mmco; + struct { + /* from ucode lmem, do not change this struct */ + } p; +}; + +struct h264_canvas { + u32 canvas_pos; + int poc; +}; + +struct h264_decode_buf_spec { + struct v4l2_h264_dpb_entry *dpb; + u32 canvas_pos; + u32 dpb_index; + int poc; + int col_buf_index; + u8 y_canvas_index; + u8 u_canvas_index; + u8 v_canvas_index; + u8 used; + u8 long_term_flag; + dma_addr_t y_dma_addr; + dma_addr_t c_dma_addr; +}; + +#define REORDERING_COMMAND_MAX_SIZE 33 +struct slice { + int frame_num; + /*modification */ + int slice_type; + int num_ref_idx_l0; + int num_ref_idx_l1; + int first_mb_in_slice; + int ref_pic_list_reordering_flag[2]; + int modification_of_pic_nums_idc[2][REORDERING_COMMAND_MAX_SIZE]; + int abs_diff_pic_num_minus1[2][REORDERING_COMMAND_MAX_SIZE]; + int long_term_pic_idx[2][REORDERING_COMMAND_MAX_SIZE]; + unsigned char dec_ref_pic_marking_buffer_valid; +}; + +struct aml_h264_ctx { + struct aml_vdec_ctx *v4l2_ctx; + u8 init_flag; + u8 new_pic_flag; + u8 mc_cpu_loaded; + u8 param_set; + u8 colocated_buf_num; + u8 reg_iqidct_control_init_flag; + u32 reg_iqidct_control; + u32 reg_vcop_ctrl_reg; + u32 reg_rv_ai_mb_count; + u32 vld_dec_control; + u32 save_avscratch_f; + u32 seq_info; + u32 decode_pic_count; + union param dpb_param; + u32 dec_status; + struct slice mslice; + struct h264_decode_buf_spec curr_spec; + struct h264_decode_buf_spec ref_list0[V4L2_H264_NUM_DPB_ENTRIES + 1]; + struct h264_decode_buf_spec ref_list1[V4L2_H264_NUM_DPB_ENTRIES + 1]; + struct h264_decode_buf_spec ref_list0_unreordered[V4L2_H264_NUM_DPB_ENTRIES + 1]; + struct h264_decode_buf_spec ref_list1_unreordered[V4L2_H264_NUM_DPB_ENTRIES + 1]; + u8 list_size[2]; + u32 canvas_pos_map; + struct h264_canvas ref_canvas[V4L2_H264_NUM_DPB_ENTRIES + 1]; + dma_addr_t lmem_phy_addr; + void *lmem_addr; + dma_addr_t mc_cpu_paddr; + void *mc_cpu_vaddr; + dma_addr_t cma_alloc_addr; + void *cma_alloc_vaddr; + dma_addr_t collated_cma_addr; + dma_addr_t collated_cma_addr_end; + void *collated_cma_vaddr; + dma_addr_t workspace_offset; + void *workspace_vaddr; + u32 col_buf_alloc_size; + u32 one_col_buf_size; + u32 colocated_buf_map; + int colocated_buf_poc[H264_MAX_COL_BUF]; + + u32 frame_width; + u32 frame_height; + u32 mb_width; + u32 mb_height; + u32 mb_total; + u32 max_num_ref_frames; + + struct vdec_h264_stateless_ctrl_ref ctrl_ref; +}; + +static inline int get_flag(u32 flag, u32 mask) +{ + return (flag & mask) ? 1 : 0; +} + +static inline void write_lmem(unsigned short *base, u32 offset, u32 value) +{ + base[offset] = value; +} + +static inline uint32_t spec2canvas(struct h264_decode_buf_spec *buf_spec) +{ + return (buf_spec->v_canvas_index << 16) | + (buf_spec->u_canvas_index << 8) | + (buf_spec->y_canvas_index << 0); +} + +static struct h264_decode_buf_spec *find_spec_by_dpb_index(struct aml_h264_ctx + *h264_ctx, int index, int list) +{ + int i; + int size; + struct h264_decode_buf_spec *ref_list; + + size = h264_ctx->list_size[list]; + if (list == 0) + ref_list = &h264_ctx->ref_list0[0]; + else + ref_list = &h264_ctx->ref_list1[0]; + + for (i = 0; i < size; i++) { + if (index == ref_list[i].dpb_index) + return &ref_list[i]; + } + + return NULL; +} + +static int h264_prepare_input(struct aml_vdec_ctx *ctx) +{ + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + struct vb2_v4l2_buffer *src; + struct vb2_buffer *vb; + dma_addr_t src_dma; + u32 payload_size; + int dummy; + + src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + if (!src) { + dev_info(hw->dev, "no input buffer available!\n"); + return -1; + } + vb = &src->vb2_buf; + payload_size = vb2_get_plane_payload(vb, 0); + src_dma = vb2_dma_contig_plane_dma_addr(vb, 0); + + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, 0); + /* reset VLD fifo for all vdec */ + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, + (1 << 5) | (1 << 4) | (1 << 3)); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0); + regmap_write(hw->map[DOS_BUS], POWER_CTL_VLD, 1 << 4); + + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_START_PTR, src_dma); + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_END_PTR, + (src_dma + payload_size)); + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CURR_PTR, + round_down(src_dma, VDEC_FIFO_ALIGN)); + + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, 1); + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, 0); + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_BUF_CNTL, 2); + + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_RP, + round_down(src_dma, VDEC_FIFO_ALIGN)); + dummy = payload_size + VLD_PADDING_SIZE; + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_WP, + round_down((src_dma + dummy), VDEC_FIFO_ALIGN)); + + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_BUF_CNTL, 3); + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_BUF_CNTL, 2); + + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, + (0x11 << 16) | (1 << 10) | (7 << 3)); + + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_1, 0x0); + regmap_write(hw->map[DOS_BUS], H264_DECODE_INFO, (1 << 13)); + regmap_write(hw->map[DOS_BUS], H264_DECODE_SIZE, payload_size); + regmap_write(hw->map[DOS_BUS], VIFF_BIT_CNT, payload_size * 8); + + return 0; +} + +static void config_sps_params(struct aml_h264_ctx *h264_ctx, + unsigned short *sps_base, + const struct v4l2_ctrl_h264_sps *sps) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + u32 cfg_tmp = 0; + u32 frame_size; + u32 offset = 0; + unsigned short data_tmp[0x100]; + int i, ii; + + memset(sps_base, 0, 0x100); + + h264_ctx->frame_width = (sps->pic_width_in_mbs_minus1 + 1) << 4; + h264_ctx->frame_height = (sps->pic_height_in_map_units_minus1 + 1) << 4; + + data_tmp[offset] = PARAM_BASE_VAL; + offset += 2; + + data_tmp[offset++] = GET_SPS_PROFILE_IDC(sps->profile_idc); + + data_tmp[offset++] = GET_SPS_SEQ_PARAM_SET_ID(sps->seq_parameter_set_id) | + GET_SPS_LEVEL_IDC(sps->level_idc); + + if (sps->profile_idc >= 100) { + data_tmp[offset++] = GET_SPS_CHROMA_FORMAT_IDC(sps->chroma_format_idc); + + data_tmp[offset++] = ((sps->chroma_format_idc ^ 1) << 1); + } + + data_tmp[offset++] = GET_SPS_LOG2_MAX_FRAME_NUM(sps->log2_max_frame_num_minus4); + data_tmp[offset++] = GET_SPS_PIC_ORDER_TYPE(sps->pic_order_cnt_type); + + if (sps->pic_order_cnt_type == 0) { + data_tmp[offset++] = + GET_SPS_PIC_ORDER_CNT_LSB(sps->log2_max_pic_order_cnt_lsb_minus4); + } else if (sps->pic_order_cnt_type == 1) { + data_tmp[offset++] = + get_flag(sps->flags, + V4L2_H264_SPS_FLAG_DELTA_PIC_ORDER_ALWAYS_ZERO); + data_tmp[offset++] = + GET_SPS_OFFSET_FOR_NONREF_PIC_LOW(sps->offset_for_non_ref_pic); + data_tmp[offset++] = + GET_SPS_OFFSET_FOR_NONREF_PIC_HIGH(sps->offset_for_non_ref_pic); + data_tmp[offset++] = + GET_SPS_OFFSET_FOR_TOP_BOT_FIELD_LOW(sps->offset_for_top_to_bottom_field); + data_tmp[offset++] = + GET_SPS_OFFSET_FOR_TOP_BOT_FIELD_HIGH(sps->offset_for_top_to_bottom_field); + data_tmp[offset++] = sps->num_ref_frames_in_pic_order_cnt_cycle; + } + + data_tmp[offset++] = GET_SPS_NUM_REF_FRAMES(sps->max_num_ref_frames) | + GET_SPS_GAPS_ALLOWED_FLAG(get_flag(sps->flags, + V4L2_H264_SPS_FLAG_GAPS_IN_FRAME_NUM_VALUE_ALLOWED)); + + data_tmp[offset++] = GET_SPS_PIC_WIDTH_IN_MBS(sps->pic_width_in_mbs_minus1); + + data_tmp[offset++] = GET_SPS_PIC_HEIGHT_IN_MBS(sps->pic_height_in_map_units_minus1); + data_tmp[offset++] = + GET_SPS_DIRECT_8X8_FLAGS + (get_flag(sps->flags, + V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE)) | + GET_SPS_MB_ADAPTIVE_FRAME_FIELD_FLAGS + (get_flag(sps->flags, + V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD)) | + GET_SPS_FRAME_MBS_ONLY_FLAGS(get_flag(sps->flags, + V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY)); + + for (i = 0; i < 0x100; i += 4) { + for (ii = 0; ii < 4; ii++) + sps_base[i + 3 - ii] = data_tmp[i + ii]; + } + + frame_size = (sps->pic_width_in_mbs_minus1 + 1) * (sps->pic_height_in_map_units_minus1 + 1); + cfg_tmp = (get_flag(sps->flags, V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY) << 31) | + (sps->max_num_ref_frames << 24) | (frame_size << 8) | + (sps->pic_width_in_mbs_minus1 + 1); + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_1, cfg_tmp); + h264_ctx->seq_info = cfg_tmp; + + cfg_tmp = 0; + cfg_tmp = (get_flag(sps->flags, V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE) << 15) | + (sps->chroma_format_idc); + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_2, cfg_tmp); + + cfg_tmp = 0; + cfg_tmp = (sps->max_num_ref_frames << 8) | (sps->level_idc); + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_B, cfg_tmp); + + cfg_tmp = ((sps->level_idc & 0xff) << 7) | + (get_flag(sps->flags, V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY) << 2); + regmap_write(hw->map[DOS_BUS], NAL_SEARCH_CTL, + read_dos_reg(hw, NAL_SEARCH_CTL) | cfg_tmp); + + h264_ctx->mb_width = (sps->pic_width_in_mbs_minus1 + 4) & 0xfffffffc; + h264_ctx->mb_height = (sps->pic_height_in_map_units_minus1 + 4) & 0xfffffffc; + h264_ctx->mb_total = h264_ctx->mb_width * h264_ctx->mb_height; + h264_ctx->max_num_ref_frames = sps->max_num_ref_frames; +} + +static void config_pps_params(struct aml_h264_ctx *h264_ctx, + unsigned short *pps_base, + const struct v4l2_ctrl_h264_pps *pps) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + u32 offset = 0; + unsigned short data_tmp[0x100]; + u32 max_reference_size = V4L2_H264_NUM_DPB_ENTRIES; + u32 max_list_size; + int i, ii; + + memset(pps_base, 0, 0x100); + + data_tmp[offset++] = PARAM_BASE_VAL; + + data_tmp[offset++] = + GET_PPS_PIC_PARAM_SET_ID(pps->pic_parameter_set_id) | + GET_PPS_SEQ_PARAM_SET_ID(pps->seq_parameter_set_id) | + GET_PPS_ENTROPY_CODING_MODE_FLAG + (get_flag(pps->flags, + V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE)) | + GET_PPS_PIC_ORDER_PRESENT_FLAG + (get_flag(pps->flags, + V4L2_H264_PPS_FLAG_BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT)); + + data_tmp[offset++] = + GET_PPS_WEIGHTED_BIPRED_IDC(pps->weighted_bipred_idc) | + GET_PPS_WEIGHTED_PRED_FLAG(get_flag(pps->flags, + V4L2_H264_PPS_FLAG_WEIGHTED_PRED)) | + GET_PPS_NUM_IDX_REF_L1_MINUS1(pps->num_ref_idx_l1_default_active_minus1) | + GET_PPS_NUM_IDX_REF_L0_MINUS1(pps->num_ref_idx_l0_default_active_minus1); + + data_tmp[offset++] = GET_PPS_INIT_QS_MINUS26(pps->pic_init_qs_minus26) | + GET_PPS_INIT_QP_MINUS26(pps->pic_init_qp_minus26); + + data_tmp[offset] = + GET_PPS_CHROMA_QP_INDEX_OFFSET(pps->chroma_qp_index_offset) | + GET_PPS_DEBLOCK_FILTER_CTRL_PRESENT_FLAG + (get_flag(pps->flags, + V4L2_H264_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT)) | + GET_PPS_CONSTRAIN_INTRA_PRED_FLAG + (get_flag(pps->flags, + V4L2_H264_PPS_FLAG_CONSTRAINED_INTRA_PRED)) | + GET_PPS_REDUNDANT_PIC_CNT_PRESENT_FLAG + (get_flag(pps->flags, + V4L2_H264_PPS_FLAG_REDUNDANT_PIC_CNT_PRESENT)); + if (get_flag(pps->flags, V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE | + V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT)) + data_tmp[offset] |= (1 << 11); + offset++; + + data_tmp[offset++] = + GET_PPS_SCALING_MATRIX_PRESENT_FLAG(get_flag + (pps->flags, + V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT)) | + GET_PPS_TRANSFORM_8X8_FLAG(get_flag(pps->flags, + V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE)); + + data_tmp[offset++] = + GET_PPS_GET_SECOND_CHROMA_QP_OFFSET(pps->second_chroma_qp_index_offset); + + max_list_size = (pps->num_ref_idx_l1_default_active_minus1 + 1) + + (pps->num_ref_idx_l0_default_active_minus1 + 1); + + h264_ctx->max_num_ref_frames = max_list_size > h264_ctx->max_num_ref_frames ? + max_list_size : h264_ctx->max_num_ref_frames; + + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_0, + ((h264_ctx->max_num_ref_frames + 1) << 24) | + (max_reference_size << 16) | (max_reference_size << 8)); + + for (i = 0; i < 0x100; i += 4) { + for (ii = 0; ii < 4; ii++) + pps_base[i + 3 - ii] = data_tmp[i + ii]; + } +} + +static void h264_config_params(struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv; + unsigned short *p_sps_base, *p_pps_base; + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + const struct v4l2_ctrl_h264_sps *sps = ctrls->sps; + const struct v4l2_ctrl_h264_pps *pps = ctrls->pps; + + p_sps_base = (unsigned short *)(h264_ctx->workspace_vaddr + + MEM_SPS_BASE + sps->seq_parameter_set_id * 0x400); + p_pps_base = (unsigned short *)(h264_ctx->workspace_vaddr + + MEM_PPS_BASE + pps->pic_parameter_set_id * 0x200); + + dev_dbg(&ctx->dev->plat_dev->dev, "%s sps id %d, pps id %d\n", + __func__, sps->seq_parameter_set_id, pps->pic_parameter_set_id); + + config_sps_params(h264_ctx, p_sps_base, sps); + config_pps_params(h264_ctx, p_pps_base, pps); +} + +static void config_decode_canvas(struct aml_vdec_hw *hw, + struct h264_decode_buf_spec *buf_spec, + u32 mb_width, u32 mb_height) +{ + int canvas_alloc_result = 0; + int blkmode = 0x0; + + canvas_alloc_result = meson_canvas_alloc(hw->canvas, &buf_spec->y_canvas_index); + canvas_alloc_result = meson_canvas_alloc(hw->canvas, &buf_spec->u_canvas_index); + buf_spec->v_canvas_index = buf_spec->u_canvas_index; + + if (!canvas_alloc_result) { + /* config y canvas */ + meson_canvas_config(hw->canvas, + buf_spec->y_canvas_index, buf_spec->y_dma_addr, + mb_width << 4, mb_height << 4, + MESON_CANVAS_WRAP_NONE, MESON_CANVAS_BLKMODE_LINEAR, + MESON_CANVAS_ENDIAN_SWAP64); + regmap_write(hw->map[DOS_BUS], VDEC_ASSIST_CANVAS_BLK32, + (1 << 11) | /* canvas_blk32_wr */ + (blkmode << 10) | /* canvas_blk32 */ + (1 << 8) | /* canvas_index_wr */ + (buf_spec->y_canvas_index << 0) /* canvas index */ + ); + + /* config uv canvas */ + meson_canvas_config(hw->canvas, + buf_spec->u_canvas_index, buf_spec->c_dma_addr, + mb_width << 4, mb_height << 3, + MESON_CANVAS_WRAP_NONE, MESON_CANVAS_BLKMODE_LINEAR, + MESON_CANVAS_ENDIAN_SWAP64); + regmap_write(hw->map[DOS_BUS], VDEC_ASSIST_CANVAS_BLK32, + (1 << 11) | /* canvas_blk32_wr */ + (blkmode << 10) | /* canvas_blk32 */ + (1 << 8) | /* canvas_index_wr */ + (buf_spec->u_canvas_index << 0) /* canvas index */ + ); + + regmap_write(hw->map[DOS_BUS], ANC0_CANVAS_ADDR + (buf_spec->canvas_pos << 2), + spec2canvas(buf_spec)); + } +} + +static int allocate_colocate_buf(struct aml_h264_ctx *h264_ctx, int poc) +{ + int i; + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + + for (i = 0; i < h264_ctx->colocated_buf_num; i++) { + if (((h264_ctx->colocated_buf_map >> i) & 0x1) == 0) { + h264_ctx->colocated_buf_map |= (1 << i); + break; + } + } + + if (i == h264_ctx->colocated_buf_num) + return -1; + + h264_ctx->colocated_buf_poc[i] = poc; + dev_dbg(&ctx->dev->plat_dev->dev, "%s colocated_buf_index %d poc %d\n", + __func__, i, h264_ctx->colocated_buf_poc[i]); + + return i; +} + +static void release_colocate_buf(struct aml_h264_ctx *h264_ctx, int index) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + + if (index >= 0) { + if (index >= h264_ctx->colocated_buf_num) { + dev_dbg + (&ctx->dev->plat_dev->dev, + "%s error, index %d is bigger than buf count %d\n", + __func__, index, h264_ctx->max_num_ref_frames); + } else { + if (h264_ctx->colocated_buf_poc[index] != INVALID_POC && + ((h264_ctx->colocated_buf_map >> index) & 0x1) == 0x1) { + h264_ctx->colocated_buf_map &= (~(1 << index)); + dev_dbg + (&ctx->dev->plat_dev->dev, + "%s colocated_buf_index %d released poc %d\n", + __func__, index, + h264_ctx->colocated_buf_poc[index]); + } + h264_ctx->colocated_buf_poc[index] = INVALID_POC; + } + } +} + +static int get_col_buf_index_by_poc(struct aml_h264_ctx *h264_ctx, int poc) +{ + int idx; + + for (idx = 0; idx < h264_ctx->colocated_buf_num; idx++) { + if (h264_ctx->colocated_buf_poc[idx] == poc) + break; + } + + if (idx == h264_ctx->colocated_buf_num) + idx = -1; + + return idx; +} + +static int alloc_colocate_cma(struct aml_h264_ctx *h264_ctx, + struct aml_vdec_ctx *ctx) +{ + int alloc_size = 0; + int i; + struct aml_vdec_hw *hw; + + if (h264_ctx->collated_cma_vaddr) + return 0; + + hw = vdec_get_hw(ctx->dev); + if (!hw) + return -1; + + /* 96 :col buf size for each mb */ + h264_ctx->one_col_buf_size = h264_ctx->mb_total * 96; + alloc_size = PAGE_ALIGN(h264_ctx->one_col_buf_size * + (h264_ctx->max_num_ref_frames + COL_BUFFER_MARGIN)); + h264_ctx->collated_cma_vaddr = dma_alloc_coherent(hw->dev, alloc_size, + &h264_ctx->collated_cma_addr, GFP_KERNEL); + if (!h264_ctx->collated_cma_vaddr) + return -ENOMEM; + + dev_dbg + (&ctx->dev->plat_dev->dev, + "collated_cma_addr = %pad, one_col_buf_size = %x alloc_size = %x\n", + &h264_ctx->collated_cma_addr, h264_ctx->one_col_buf_size, + alloc_size); + h264_ctx->collated_cma_addr_end = + h264_ctx->collated_cma_addr + alloc_size; + memset(h264_ctx->collated_cma_vaddr, 0, alloc_size); + h264_ctx->col_buf_alloc_size = alloc_size; + h264_ctx->colocated_buf_map = 0; + h264_ctx->colocated_buf_num = h264_ctx->max_num_ref_frames + COL_BUFFER_MARGIN; + + for (i = 0; i < H264_MAX_COL_BUF; i++) + h264_ctx->colocated_buf_poc[i] = INVALID_POC; + + return 0; +} + +static void config_p_reflist(struct aml_h264_ctx *h264_ctx, + struct v4l2_h264_reference *v4l2_p0_reflist, + u32 list_size) +{ + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + struct v4l2_ctrl_h264_decode_params *decode = + (struct v4l2_ctrl_h264_decode_params *)ctrls->decode; + struct v4l2_h264_dpb_entry *dpb = decode->dpb; + u8 index; + int i; + + for (i = 0; i < list_size; i++) { + index = v4l2_p0_reflist[i].index; + h264_ctx->ref_list0[i].used = 1; + h264_ctx->ref_list0[i].dpb = &dpb[index]; + h264_ctx->ref_list0[i].poc = dpb[index].top_field_order_cnt; + h264_ctx->ref_list0[i].long_term_flag = + dpb[index].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM ? true : false; + h264_ctx->ref_list0[i].dpb_index = index; + } + h264_ctx->list_size[0] = list_size; +} + +static void config_b_reflist(struct aml_h264_ctx *h264_ctx, + struct v4l2_h264_reference *v4l2_b0_reflist, + struct v4l2_h264_reference *v4l2_b1_reflist, + u32 list_size) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + struct v4l2_ctrl_h264_decode_params *decode = + (struct v4l2_ctrl_h264_decode_params *)ctrls->decode; + struct v4l2_h264_dpb_entry *dpb = decode->dpb; + u8 index; + int i, j; + + h264_ctx->list_size[0] = list_size; + for (i = 0; i < list_size; i++) { + index = v4l2_b0_reflist[i].index; + h264_ctx->ref_list0[i].used = 1; + h264_ctx->ref_list0[i].dpb = &dpb[index]; + h264_ctx->ref_list0[i].poc = dpb[index].top_field_order_cnt; + h264_ctx->ref_list0[i].long_term_flag = + dpb[index].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM ? true : false; + h264_ctx->ref_list0[i].col_buf_index = + get_col_buf_index_by_poc(h264_ctx, dpb[index].top_field_order_cnt); + h264_ctx->ref_list0[i].dpb_index = index; + } + + h264_ctx->list_size[1] = list_size; + for (j = 0; j < list_size; j++) { + index = v4l2_b1_reflist[j].index; + h264_ctx->ref_list1[j].used = 1; + h264_ctx->ref_list1[j].dpb = &dpb[index]; + h264_ctx->ref_list1[j].poc = dpb[index].top_field_order_cnt; + h264_ctx->ref_list1[j].long_term_flag = + dpb[index].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM ? true : false; + h264_ctx->ref_list1[j].col_buf_index = + get_col_buf_index_by_poc(h264_ctx, dpb[index].top_field_order_cnt); + h264_ctx->ref_list1[j].dpb_index = index; + } + + if ((h264_ctx->list_size[1] + h264_ctx->list_size[0]) < list_size) + dev_info(&ctx->dev->plat_dev->dev, "ref list incorrect list0 %d list0 %d list_size%d\n", + h264_ctx->list_size[0], h264_ctx->list_size[1], list_size); +} + +static int poc_is_in_dpb(int poc, const struct v4l2_h264_dpb_entry *dpb) +{ + int i; + int ret = 0; + + for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) { + if (poc == dpb[i].top_field_order_cnt) { + ret = 1; + break; + } + } + + return ret; +} + +static int get_ref_list_size(struct aml_h264_ctx *h264_ctx, int cur_list) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + unsigned short override_flag = h264_ctx->dpb_param.l.data[REF_IDC_OVERRIDE_FLAG]; + int num_ref_idx_lx_active_minus1; + + if (cur_list == 0) { + num_ref_idx_lx_active_minus1 = + h264_ctx->ctrl_ref.pps->num_ref_idx_l0_default_active_minus1; + if (override_flag) + num_ref_idx_lx_active_minus1 = + h264_ctx->dpb_param.dpb.num_ref_idx_l0_active_minus1; + } else { + num_ref_idx_lx_active_minus1 = + h264_ctx->ctrl_ref.pps->num_ref_idx_l1_default_active_minus1; + } + dev_dbg(&ctx->dev->plat_dev->dev, "%s get list %d size %d\n", + __func__, cur_list, num_ref_idx_lx_active_minus1 + 1); + + return num_ref_idx_lx_active_minus1 + 1; +} + +static int get_refidx_by_picnum(struct aml_h264_ctx *h264_ctx, int pic_num, + int curr_list) +{ + int i; + struct h264_decode_buf_spec *ref_list; + + if (curr_list == 0) + ref_list = &h264_ctx->ref_list0[0]; + else + ref_list = &h264_ctx->ref_list1[0]; + + for (i = 0; ref_list[i].dpb; i++) { + if (pic_num == ref_list[i].dpb->pic_num) + return i; + } + + return -1; +} + +static struct h264_decode_buf_spec *get_st_refpic_by_num(struct aml_h264_ctx *h264_ctx, + int pic_num, int curr_list) +{ + int i; + struct h264_decode_buf_spec *ref_list; + + if (curr_list == 0) + ref_list = &h264_ctx->ref_list0_unreordered[0]; + else + ref_list = &h264_ctx->ref_list1_unreordered[0]; + + for (i = 0; ref_list[i].dpb; i++) { + if (pic_num == ref_list[i].dpb->pic_num && ref_list[i].long_term_flag == 0) + return &ref_list[i]; + } + + return NULL; +} + +static struct h264_decode_buf_spec *get_lt_refpic_by_num(struct aml_h264_ctx *h264_ctx, + int pic_num, int curr_list) +{ + int i; + struct h264_decode_buf_spec *ref_list; + + if (curr_list == 0) + ref_list = &h264_ctx->ref_list0_unreordered[0]; + else + ref_list = &h264_ctx->ref_list1_unreordered[0]; + + for (i = 0; ref_list[i].dpb; i++) { + if (pic_num == ref_list[i].dpb->pic_num && ref_list[i].long_term_flag == 1) + return &ref_list[i]; + } + + return NULL; +} + +static void reorder_short_term(struct slice *curr_slice, int cur_list, + int pic_num_lx, int *ref_idx_lx) +{ + struct aml_h264_ctx *h264_ctx = + container_of(curr_slice, struct aml_h264_ctx, mslice); + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + int c_idx, n_idx; + int num_ref_idx_lx_active; + struct h264_decode_buf_spec *pic_lx = NULL; + struct h264_decode_buf_spec *ref_list_reordered; + + if (cur_list == 0) + ref_list_reordered = &h264_ctx->ref_list0[0]; + else + ref_list_reordered = &h264_ctx->ref_list1[0]; + + num_ref_idx_lx_active = get_ref_list_size(h264_ctx, cur_list); + + /* find short-term ref frame with pic_num is pic_num_lx */ + pic_lx = get_st_refpic_by_num(h264_ctx, pic_num_lx, cur_list); + if (!pic_lx) { + dev_dbg(&ctx->dev->plat_dev->dev, "cannot find st pic_lx for %d\n", pic_num_lx); + return; + } + + if (*ref_idx_lx == get_refidx_by_picnum(h264_ctx, pic_num_lx, cur_list)) { + dev_dbg(&ctx->dev->plat_dev->dev, "no need to move pic lx %d\n", *ref_idx_lx); + *ref_idx_lx = *ref_idx_lx + 1; + return; + } + + for (c_idx = num_ref_idx_lx_active; c_idx > *ref_idx_lx; c_idx--) + memcpy(&ref_list_reordered[c_idx], &ref_list_reordered[c_idx - 1], + sizeof(struct h264_decode_buf_spec)); + + memcpy(&ref_list_reordered[*ref_idx_lx], pic_lx, sizeof(struct h264_decode_buf_spec)); + dev_dbg(&ctx->dev->plat_dev->dev, "%s : RefPicListX[%d ] = pic %p pic_num(%d)\n", __func__, + *ref_idx_lx, pic_lx, ref_list_reordered[*ref_idx_lx].dpb->pic_num); + *ref_idx_lx = *ref_idx_lx + 1; + + n_idx = *ref_idx_lx; + for (c_idx = *ref_idx_lx; c_idx <= num_ref_idx_lx_active; c_idx++) { + if (ref_list_reordered[c_idx].long_term_flag || !ref_list_reordered[c_idx].dpb || + ref_list_reordered[c_idx].dpb->pic_num != pic_num_lx) + memcpy(&ref_list_reordered[n_idx++], &ref_list_reordered[c_idx], + sizeof(struct h264_decode_buf_spec)); + } + + h264_ctx->list_size[cur_list] = num_ref_idx_lx_active; +} + +static void reorder_long_term(struct slice *curr_slice, int cur_list, + int lt_pic_num, int *ref_idx_lx) +{ + struct aml_h264_ctx *h264_ctx = + container_of(curr_slice, struct aml_h264_ctx, mslice); + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + int num_ref_idx_lx_active; + int c_idx, n_idx; + struct h264_decode_buf_spec *ref_list; + struct h264_decode_buf_spec *pic_lt = NULL; + + if (cur_list == 0) + ref_list = &h264_ctx->ref_list0[0]; + else + ref_list = &h264_ctx->ref_list1[0]; + + num_ref_idx_lx_active = get_ref_list_size(h264_ctx, cur_list); + + /* find long-term ref frame with pic_num is lt_pic_num */ + pic_lt = get_lt_refpic_by_num(h264_ctx, lt_pic_num, cur_list); + if (!pic_lt) { + dev_dbg(&ctx->dev->plat_dev->dev, "cannot find lt pic_lx for %d\n", lt_pic_num); + return; + } + + if (*ref_idx_lx == get_refidx_by_picnum(h264_ctx, lt_pic_num, cur_list)) { + dev_dbg(&ctx->dev->plat_dev->dev, "no need to move pic lx %d\n", *ref_idx_lx); + *ref_idx_lx = *ref_idx_lx + 1; + return; + } + + for (c_idx = num_ref_idx_lx_active; c_idx > *ref_idx_lx; c_idx--) + memcpy(&ref_list[c_idx], &ref_list[c_idx - 1], sizeof(struct h264_decode_buf_spec)); + + memcpy(&ref_list[*ref_idx_lx], pic_lt, sizeof(struct h264_decode_buf_spec)); + dev_dbg(&ctx->dev->plat_dev->dev, "%s : RefPicListX[%d ] = pic %p pic_num(%d)\n", __func__, + *ref_idx_lx, pic_lt, ref_list[*ref_idx_lx].dpb->pic_num); + *ref_idx_lx = *ref_idx_lx + 1; + + n_idx = *ref_idx_lx; + /* Pointer dpb is NULL means this is a dummy frame store */ + for (c_idx = *ref_idx_lx; c_idx <= num_ref_idx_lx_active; c_idx++) { + if (!ref_list[c_idx].long_term_flag || !ref_list[c_idx].dpb || + ref_list[c_idx].dpb->pic_num != lt_pic_num) + memcpy(&ref_list[n_idx++], &ref_list[c_idx], + sizeof(struct h264_decode_buf_spec)); + } + + h264_ctx->list_size[cur_list] = num_ref_idx_lx_active; +} + +static void get_modification_cmd(unsigned short *reorder_cmd, + struct slice *curr_slice, int list) +{ + int i, j, val; + + val = curr_slice->ref_pic_list_reordering_flag[list]; + if (val) { + i = 0; + j = 0; + do { + curr_slice->modification_of_pic_nums_idc[list][i] = + reorder_cmd[j++]; + if (j >= REORDER_CMD_MAX) { + curr_slice->modification_of_pic_nums_idc[list][i] = 0; + break; + } + + val = curr_slice->modification_of_pic_nums_idc[list][i]; + if (val == 0 || val == 1) + curr_slice->abs_diff_pic_num_minus1[list][i] = reorder_cmd[j++]; + else if (val == 2) + curr_slice->long_term_pic_idx[list][i] = reorder_cmd[j++]; + + i++; + + if (i >= REORDERING_COMMAND_MAX_SIZE) { + curr_slice->ref_pic_list_reordering_flag[list] = 0; + break; + }; + if (j > REORDER_CMD_MAX) { + curr_slice->ref_pic_list_reordering_flag[list] = 0; + break; + }; + } while (val != 3); + } +} + +static void reorder_pics(struct aml_h264_ctx *h264_ctx, + struct slice *curr_slice, int cur_list) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + int *modification_of_pic_nums_idc = + curr_slice->modification_of_pic_nums_idc[cur_list]; + int *abs_diff_pic_num_minus1 = + curr_slice->abs_diff_pic_num_minus1[cur_list]; + int *long_term_pic_idx = curr_slice->long_term_pic_idx[cur_list]; + int pic_num_lx_nowarp, pic_num_lx_pred, pic_num_lx; + int curr_pic_num = curr_slice->frame_num; + int max_pic_num = + 1 << (4 + h264_ctx->ctrl_ref.sps->log2_max_frame_num_minus4); + int ref_idx_lx = 0; + int nowarp_tmp = 0; + int i; + + pic_num_lx_pred = curr_pic_num; + for (i = 0; i < REORDERING_COMMAND_MAX_SIZE && modification_of_pic_nums_idc[i] != 3; i++) { + if (modification_of_pic_nums_idc[i] > 3) { + dev_info(&ctx->dev->plat_dev->dev, "error, Invalid modification_of_pic_nums_idc command\n"); + break; + } + + if (modification_of_pic_nums_idc[i] < 2) { + if (modification_of_pic_nums_idc[i] == 0) { + nowarp_tmp = pic_num_lx_pred - (abs_diff_pic_num_minus1[i] + 1); + pic_num_lx_nowarp = nowarp_tmp + (nowarp_tmp < 0 ? max_pic_num : 0); + } else if (modification_of_pic_nums_idc[i] == 1) { + nowarp_tmp = pic_num_lx_pred + (abs_diff_pic_num_minus1[i] + 1); + pic_num_lx_nowarp = nowarp_tmp - + (nowarp_tmp > max_pic_num ? max_pic_num : 0); + } + pic_num_lx_pred = pic_num_lx_nowarp; + if (pic_num_lx_nowarp > curr_pic_num) + pic_num_lx = pic_num_lx_nowarp - max_pic_num; + else + pic_num_lx = pic_num_lx_nowarp; + + reorder_short_term(curr_slice, cur_list, pic_num_lx, &ref_idx_lx); + } else { + reorder_long_term(curr_slice, cur_list, long_term_pic_idx[i], &ref_idx_lx); + } + } +} + +static void copy_ref_list(struct aml_h264_ctx *h264_ctx, int curr_list) +{ + if (curr_list == 0) + memcpy(h264_ctx->ref_list0_unreordered, h264_ctx->ref_list0, + sizeof(h264_ctx->ref_list0)); + else + memcpy(h264_ctx->ref_list1_unreordered, h264_ctx->ref_list0, + sizeof(h264_ctx->ref_list1)); +} + +static void h264_reorder_reflists(struct aml_h264_ctx *h264_ctx) +{ + unsigned short *reorder_cmd; + struct slice *curr_slice = &h264_ctx->mslice; + + if (curr_slice->slice_type != I_SLICE && curr_slice->slice_type != SI_SLICE) { + reorder_cmd = &h264_ctx->dpb_param.mmco.l0_reorder_cmd[0]; + /* 3:parsed by ucode, means no reorder needed */ + if (reorder_cmd[0] != 3) + curr_slice->ref_pic_list_reordering_flag[0] = 1; + else + curr_slice->ref_pic_list_reordering_flag[0] = 0; + + get_modification_cmd(reorder_cmd, curr_slice, 0); + } + + if (curr_slice->slice_type == B_SLICE) { + reorder_cmd = &h264_ctx->dpb_param.mmco.l1_reorder_cmd[0]; + /* 3:parsed by ucode, means no reorder needed */ + if (reorder_cmd[0] != 3) + curr_slice->ref_pic_list_reordering_flag[1] = 1; + else + curr_slice->ref_pic_list_reordering_flag[1] = 0; + + get_modification_cmd(reorder_cmd, curr_slice, 1); + } + + if (curr_slice->slice_type != I_SLICE && + curr_slice->slice_type != SI_SLICE && + curr_slice->ref_pic_list_reordering_flag[0] != 0) { + copy_ref_list(h264_ctx, 0); + reorder_pics(h264_ctx, curr_slice, 0); + } + + if (curr_slice->slice_type == B_SLICE && + curr_slice->ref_pic_list_reordering_flag[1] != 0) { + copy_ref_list(h264_ctx, 1); + reorder_pics(h264_ctx, curr_slice, 1); + } +} + +static void h264_config_ref_lists(struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv; + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + struct v4l2_ctrl_h264_decode_params *decode = + (struct v4l2_ctrl_h264_decode_params *)ctrls->decode; + struct v4l2_ctrl_h264_sps *sps = + (struct v4l2_ctrl_h264_sps *)ctrls->sps; + const struct v4l2_h264_dpb_entry *dpb = decode->dpb; + struct v4l2_h264_reflist_builder builder; + struct v4l2_h264_reference v4l2_p0_reflist[V4L2_H264_REF_LIST_LEN]; + struct v4l2_h264_reference v4l2_b0_reflist[V4L2_H264_REF_LIST_LEN]; + struct v4l2_h264_reference v4l2_b1_reflist[V4L2_H264_REF_LIST_LEN]; + struct slice *curr_slice = &h264_ctx->mslice; + + if (decode->flags == V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC) + return; + + v4l2_h264_init_reflist_builder(&builder, decode, sps, dpb); + dev_dbg(&ctx->dev->plat_dev->dev, "%s num_valid = %d", __func__, + builder.num_valid); + + if (curr_slice->slice_type == P_SLICE && + (decode->flags & V4L2_H264_DECODE_PARAM_FLAG_PFRAME)) { + v4l2_h264_build_p_ref_list(&builder, v4l2_p0_reflist); + config_p_reflist(h264_ctx, v4l2_p0_reflist, builder.num_valid); + } else if (curr_slice->slice_type == B_SLICE && + (decode->flags & V4L2_H264_DECODE_PARAM_FLAG_BFRAME)) { + v4l2_h264_build_b_ref_lists(&builder, v4l2_b0_reflist, v4l2_b1_reflist); + config_b_reflist(h264_ctx, v4l2_b0_reflist, v4l2_b1_reflist, + builder.num_valid); + } +} + +static int allocate_canvas_pos(struct aml_h264_ctx *h264_ctx, int poc) +{ + int i; + int ret = -1; + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + + for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) { + if (((h264_ctx->canvas_pos_map >> i) & 0x1) == 0) { + h264_ctx->canvas_pos_map |= (1 << i); + h264_ctx->ref_canvas[i].poc = poc; + h264_ctx->ref_canvas[i].canvas_pos = i; + ret = i; + + dev_dbg(&ctx->dev->plat_dev->dev, + "%s i %d pos_poc %d\n", __func__, i, + h264_ctx->ref_canvas[i].poc); + break; + } + } + + return ret; +} + +static void release_canvas_pos(struct aml_h264_ctx *h264_ctx, int index) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + + if (index >= 0) { + if (index > V4L2_H264_NUM_DPB_ENTRIES) { + dev_dbg(&ctx->dev->plat_dev->dev, + "%s error, index %d is bigger than buf count %d\n", + __func__, index, h264_ctx->max_num_ref_frames); + } else { + if (h264_ctx->ref_canvas[index].poc != INVALID_POC && + ((h264_ctx->canvas_pos_map >> index) & 0x1) == + 0x1) { + h264_ctx->canvas_pos_map &= (~(1 << index)); + dev_dbg(&ctx->dev->plat_dev->dev, + "%s canvas_pos index %d released poc %d, canvas_pos_map 0x%x\n", + __func__, index, h264_ctx->ref_canvas[index].poc, + h264_ctx->canvas_pos_map); + h264_ctx->ref_canvas[index].poc = INVALID_POC; + h264_ctx->ref_canvas[index].canvas_pos = -1; + } + } + } +} + +static int get_canvas_pos_by_poc(struct aml_h264_ctx *h264_ctx, int poc) +{ + int i; + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + int ret_pos = -1; + + for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) { + if (h264_ctx->ref_canvas[i].poc == poc) { + ret_pos = h264_ctx->ref_canvas[i].canvas_pos; + dev_dbg(&ctx->dev->plat_dev->dev, "%s canvas_pos %d\n", + __func__, ret_pos); + return ret_pos; + } + } + + dev_dbg(&ctx->dev->plat_dev->dev, + "%s error, no find canvas pos %d, poc %d\n", __func__, ret_pos, poc); + + return ret_pos; +} + +static void clear_unused_col_buf(struct aml_h264_ctx *h264_ctx, + struct v4l2_ctrl_h264_decode_params *decode) +{ + int i, col_poc; + + /* flush all col buffers when IDR */ + if (decode->flags == V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC) { + /* 32 : max index of co-locate buffer */ + for (i = 0; i < 32; i++) + release_colocate_buf(h264_ctx, i); + for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) + release_canvas_pos(h264_ctx, i); + return; + } + + for (i = 0; i < h264_ctx->colocated_buf_num; i++) { + col_poc = h264_ctx->colocated_buf_poc[i]; + if (col_poc != INVALID_POC && + (poc_is_in_dpb(col_poc, decode->dpb) != 1)) + release_colocate_buf(h264_ctx, i); + } + + for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) { + col_poc = h264_ctx->ref_canvas[i].poc; + if (col_poc != INVALID_POC && + (poc_is_in_dpb(col_poc, decode->dpb) != 1)) + release_canvas_pos(h264_ctx, i); + } +} + +static void h264_config_decode_spec(struct aml_vdec_hw *hw, struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx; + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + struct v4l2_ctrl_h264_decode_params *decode = + (struct v4l2_ctrl_h264_decode_params *)ctrls->decode; + struct h264_decode_buf_spec *buf_spec_l0, *buf_spec_l1; + struct vb2_buffer *vb; + struct vb2_v4l2_buffer *vb2_v4l2; + struct vb2_queue *vq; + int i; + + clear_unused_col_buf(h264_ctx, decode); + + vb2_v4l2 = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + vb = &vb2_v4l2->vb2_buf; + + h264_ctx->curr_spec.y_dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); + if (ctx->pic_info.plane_num > 1) + h264_ctx->curr_spec.c_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 1); + else + h264_ctx->curr_spec.c_dma_addr = + h264_ctx->curr_spec.y_dma_addr + ctx->pic_info.fb_size[0]; + h264_ctx->curr_spec.canvas_pos = + allocate_canvas_pos(h264_ctx, decode->top_field_order_cnt); + if (h264_ctx->curr_spec.canvas_pos < 0) + dev_err(&ctx->dev->plat_dev->dev, "curr_spec.canvas error\n"); + + if (decode->nal_ref_idc) + h264_ctx->curr_spec.col_buf_index = + allocate_colocate_buf(h264_ctx, + decode->top_field_order_cnt); + else + h264_ctx->curr_spec.col_buf_index = -1; + h264_ctx->curr_spec.poc = decode->top_field_order_cnt; + + h264_config_ref_lists(ctx); + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) { + struct v4l2_h264_dpb_entry *dpb = &decode->dpb[i]; + + if (!(dpb->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) + break; + + buf_spec_l0 = find_spec_by_dpb_index(h264_ctx, i, 0); + if (buf_spec_l0) { + buf_spec_l0->canvas_pos = + get_canvas_pos_by_poc(h264_ctx, + dpb->top_field_order_cnt); + if (buf_spec_l0->canvas_pos < 0) { + dev_err(&ctx->dev->plat_dev->dev, + "l0 canvas_pos %d error\n", + buf_spec_l0->canvas_pos); + continue; + } + vb = vb2_find_buffer(vq, dpb->reference_ts); + if (!vb) { + dev_err(&ctx->dev->plat_dev->dev, + "ref pic for ts %llu lost\n", dpb->reference_ts); + continue; + } + + buf_spec_l0->y_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 0); + if (ctx->pic_info.plane_num > 1) + buf_spec_l0->c_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 1); + else + buf_spec_l0->c_dma_addr = + buf_spec_l0->y_dma_addr + + ctx->pic_info.fb_size[0]; + dev_dbg(&ctx->dev->plat_dev->dev, + "config canvas for poc %d canvas %d y_dma_addr %pad c_dma_addr %pad\n", + buf_spec_l0->dpb->top_field_order_cnt, + buf_spec_l0->canvas_pos, + &buf_spec_l0->y_dma_addr, + &buf_spec_l0->c_dma_addr); + } + + buf_spec_l1 = find_spec_by_dpb_index(h264_ctx, i, 1); + if (!buf_spec_l0 && buf_spec_l1) { + buf_spec_l1->canvas_pos = + get_canvas_pos_by_poc(h264_ctx, + dpb->top_field_order_cnt); + if (buf_spec_l1->canvas_pos < 0) { + dev_err(&ctx->dev->plat_dev->dev, + "l1 canvas_pos %d error\n", + buf_spec_l1->canvas_pos); + continue; + } + vb = vb2_find_buffer(vq, dpb->reference_ts); + if (!vb) { + dev_err(&ctx->dev->plat_dev->dev, + "ref pic for ts %llu lost\n", dpb->reference_ts); + continue; + } + + buf_spec_l1->y_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 0); + if (ctx->pic_info.plane_num > 1) + buf_spec_l1->c_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 1); + else + buf_spec_l1->c_dma_addr = + buf_spec_l1->y_dma_addr + + ctx->pic_info.fb_size[0]; + dev_dbg(&ctx->dev->plat_dev->dev, + "config canvas for poc %d canvas %d y_dma_addr %pad c_dma_addr %pad\n", + buf_spec_l1->dpb->top_field_order_cnt, + buf_spec_l1->canvas_pos, + &buf_spec_l1->y_dma_addr, + &buf_spec_l1->c_dma_addr); + } else if (buf_spec_l0 && buf_spec_l1) { + memcpy(buf_spec_l1, buf_spec_l0, + sizeof(struct h264_decode_buf_spec)); + dev_dbg(&ctx->dev->plat_dev->dev, + "config canvas for poc %d canvas %d y_dma_addr %pad c_dma_addr %pad\n", + buf_spec_l1->dpb->top_field_order_cnt, + buf_spec_l1->canvas_pos, + &buf_spec_l1->y_dma_addr, + &buf_spec_l1->c_dma_addr); + } + } +} + +static int get_poc_by_canvas_pos(struct aml_h264_ctx *h264_ctx, int canvas_pos) +{ + int i; + + for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) { + if (h264_ctx->ref_canvas[i].canvas_pos == canvas_pos) + return h264_ctx->ref_canvas[i].poc; + } + return -1; +} + +static struct v4l2_h264_dpb_entry *get_dpb_by_poc(struct v4l2_ctrl_h264_decode_params *decode, + int poc) +{ + int i; + + for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) { + if (decode->dpb[i].top_field_order_cnt == poc) + return &decode->dpb[i]; + } + return NULL; +} + +static int h264_config_decode_buf(struct aml_vdec_hw *hw, + struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx; + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + struct v4l2_ctrl_h264_decode_params *decode = + (struct v4l2_ctrl_h264_decode_params *)ctrls->decode; + unsigned int canvas_adr; + unsigned int ref_cfg; + unsigned int ref_cfg_once = 0; + struct slice *curr_slice = &h264_ctx->mslice; + unsigned int type_cfg = 0x3; /* 0x3: frame type */ + unsigned int colocate_adr_offset = 0; + unsigned int colocate_wr_adr; + unsigned int info0; + unsigned int info1; + unsigned int info2; + int i, j; + int h264_buffer_info_data_write_count = 0; + u8 canvas_pos; + u8 use_mode_8x8_flag; + u32 reg_val; + + regmap_write(hw->map[DOS_BUS], H264_CURRENT_POC_IDX_RESET, 0); + regmap_write(hw->map[DOS_BUS], H264_CURRENT_POC, decode->top_field_order_cnt); + regmap_write(hw->map[DOS_BUS], H264_CURRENT_POC, decode->top_field_order_cnt); + regmap_write(hw->map[DOS_BUS], H264_CURRENT_POC, decode->bottom_field_order_cnt); + regmap_write(hw->map[DOS_BUS], CURR_CANVAS_CTRL, h264_ctx->curr_spec.canvas_pos << 24); + regmap_read(hw->map[DOS_BUS], CURR_CANVAS_CTRL, &canvas_adr); + canvas_adr &= 0xffffff; + dev_dbg(hw->dev, "canvas_pos = %d canvas_adr 0x%x\n", + h264_ctx->curr_spec.canvas_pos, canvas_adr); + + regmap_write(hw->map[DOS_BUS], REC_CANVAS_ADDR, canvas_adr); + regmap_write(hw->map[DOS_BUS], DBKR_CANVAS_ADDR, canvas_adr); + regmap_write(hw->map[DOS_BUS], DBKW_CANVAS_ADDR, canvas_adr); + + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_INDEX, 16); + + for (j = 0; j < (V4L2_H264_NUM_DPB_ENTRIES + 1); j++) { + int poc; + struct v4l2_h264_dpb_entry *dpb = NULL; + + info0 = 0; + info1 = 0; + info2 = 0; + + poc = get_poc_by_canvas_pos(h264_ctx, j); + if (poc == decode->top_field_order_cnt) { + info0 = 0xf480 | 0xf; + info1 = decode->top_field_order_cnt; + info2 = decode->bottom_field_order_cnt; + if (decode->bottom_field_order_cnt < + decode->top_field_order_cnt) + info0 |= 0x100; + } else { + dpb = get_dpb_by_poc(decode, poc); + if (dpb && (dpb->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) { + info0 = 0xf480; + if (dpb->bottom_field_order_cnt < + dpb->top_field_order_cnt) + info0 |= 0x100; + info1 = dpb->top_field_order_cnt; + info2 = dpb->bottom_field_order_cnt; + if (dpb->flags & + V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM) + info0 |= ((1 << 5) | (1 << 4)); + } + } + + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, info0); + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, info1); + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, info2); + } + + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_INDEX, 0); + /* when frame width <= 256, Disable DDR_BYTE64_CACHE */ + if (ctx->pic_info.coded_width <= 256) { + regmap_update_bits(hw->map[DOS_BUS], IQIDCT_CONTROL, (1 << 16), (1 << 16)); + regmap_write(hw->map[DOS_BUS], DCAC_DDR_BYTE64_CTL, + (read_dos_reg(hw, DCAC_DDR_BYTE64_CTL) & (~0xf)) | 0xa); + } else { + regmap_update_bits(hw->map[DOS_BUS], IQIDCT_CONTROL, (1 << 16), 0); + regmap_write(hw->map[DOS_BUS], DCAC_DDR_BYTE64_CTL, + (read_dos_reg(hw, DCAC_DDR_BYTE64_CTL) & (~0xf))); + } + + ref_cfg = 0; + j = 0; + + for (i = 0; i < h264_ctx->list_size[0]; i++) { + canvas_pos = h264_ctx->ref_list0[i].canvas_pos; + /* bit 0:3 canvas_pos bit 5:6 frame struct cfg */ + ref_cfg_once = (canvas_pos & 0x1f) | (type_cfg << 5); + ref_cfg <<= 8; + ref_cfg |= ref_cfg_once; + j++; + + if (j == 4) { + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, + ref_cfg); + dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", + ref_cfg); + h264_buffer_info_data_write_count++; + j = 0; + } + } + + if (j != 0) { + while (j != 4) { + ref_cfg <<= 8; + ref_cfg |= ref_cfg_once; + j++; + } + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, ref_cfg); + dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", ref_cfg); + h264_buffer_info_data_write_count++; + } + ref_cfg = (ref_cfg_once << 24) | (ref_cfg_once << 16) | + (ref_cfg_once << 8) | ref_cfg_once; + for (j = h264_buffer_info_data_write_count; j < 8; j++) { + dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", ref_cfg); + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, ref_cfg); + } + + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_INDEX, 8); + j = 0; + ref_cfg = 0; + + for (i = 0; i < h264_ctx->list_size[1]; i++) { + canvas_pos = h264_ctx->ref_list1[i].canvas_pos; + ref_cfg_once = (canvas_pos & 0x1f) | (type_cfg << 5); + ref_cfg <<= 8; + ref_cfg |= ref_cfg_once; + j++; + + if (j == 4) { + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, ref_cfg); + dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", ref_cfg); + j = 0; + } + } + + if (j != 0) { + while (j != 4) { + ref_cfg <<= 8; + ref_cfg |= ref_cfg_once; + j++; + } + dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", ref_cfg); + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, ref_cfg); + } + + if (get_flag(ctrls->sps->flags, V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY) && + get_flag(ctrls->sps->flags, V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE)) + use_mode_8x8_flag = 1; + else + use_mode_8x8_flag = 0; + + read_poll_timeout(read_dos_reg, reg_val, + !(reg_val & 0x800), + 10, 0, true, hw, H264_CO_MB_RW_CTL); + + /* col buf for curr frame */ + colocate_adr_offset = COL_SIZE_FOR_ONE_MB; + if (use_mode_8x8_flag) + colocate_adr_offset >>= 2; + colocate_adr_offset *= curr_slice->first_mb_in_slice; + + if (h264_ctx->curr_spec.col_buf_index >= 0 && + h264_ctx->curr_spec.col_buf_index < h264_ctx->colocated_buf_num) { + colocate_wr_adr = h264_ctx->collated_cma_addr + + ((h264_ctx->one_col_buf_size * + h264_ctx->curr_spec.col_buf_index) >> (use_mode_8x8_flag ? 2 : 0)); + if (colocate_adr_offset > h264_ctx->one_col_buf_size || + colocate_wr_adr + h264_ctx->one_col_buf_size > + h264_ctx->collated_cma_addr_end) { + dev_err(hw->dev, + "Error, colocate buf is not enough, index is %d\n", + h264_ctx->curr_spec.col_buf_index); + return -1; + } + regmap_write(hw->map[DOS_BUS], H264_CO_MB_WR_ADDR, + (colocate_wr_adr + colocate_adr_offset)); + dev_dbg(hw->dev, "col buffer addr = 0x%x col_buf_index %d\n", + (colocate_wr_adr + colocate_adr_offset), + h264_ctx->curr_spec.col_buf_index); + } else { + regmap_write(hw->map[DOS_BUS], H264_CO_MB_WR_ADDR, 0xffffffff); + dev_dbg(hw->dev, "col buffer addr = 0xffffffff\n"); + } + + if (h264_ctx->list_size[1] > 0) { + struct h264_decode_buf_spec *colocate_pic = + &h264_ctx->ref_list1[0]; + struct h264_decode_buf_spec *curr_pic = &h264_ctx->curr_spec; + int l10_structure = 2; /* for pic struct == FRAME, default to 2 */ + int cur_colocate_ref_type; + unsigned int colocate_rd_adr; + unsigned int colocate_rd_adr_offset = 0; + unsigned int val; + + cur_colocate_ref_type = + (abs(curr_pic->poc - colocate_pic->dpb->top_field_order_cnt) < + abs(curr_pic->poc - colocate_pic->dpb->bottom_field_order_cnt)) ? 0 : 1; + colocate_rd_adr_offset = COL_SIZE_FOR_ONE_MB; + if (use_mode_8x8_flag) + colocate_rd_adr_offset >>= 2; + + colocate_rd_adr_offset *= curr_slice->first_mb_in_slice; + if (colocate_pic->col_buf_index >= 0 && + colocate_pic->col_buf_index < h264_ctx->colocated_buf_num) { + colocate_rd_adr = h264_ctx->collated_cma_addr + + ((h264_ctx->one_col_buf_size * + colocate_pic->col_buf_index) >> (use_mode_8x8_flag + ? 2 : 0)); + if (colocate_rd_adr + h264_ctx->one_col_buf_size > + h264_ctx->collated_cma_addr_end) { + dev_err(hw->dev, + "Error, colocate rd buf is not enough, index is %d\n", + colocate_pic->col_buf_index); + return -1; + } + val = ((colocate_rd_adr_offset + colocate_rd_adr) >> 3) | + (cur_colocate_ref_type << 29) | + (l10_structure << 30); + regmap_write(hw->map[DOS_BUS], H264_CO_MB_RD_ADDR, val); + } else { + dev_err + (hw->dev, + "Error, reference pic has no colocated buf poc %d\n", + curr_pic->poc); + return -1; + } + } + + return 0; +} + +static void get_canvas_index(struct aml_vdec_hw *hw, struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx; + int i; + struct h264_decode_buf_spec *buf; + + config_decode_canvas(hw, &h264_ctx->curr_spec, + h264_ctx->mb_width, h264_ctx->mb_height); + if (h264_ctx->list_size[0] > 0) { + for (i = 0; i < h264_ctx->list_size[0]; i++) { + buf = &h264_ctx->ref_list0[i]; + config_decode_canvas(hw, buf, h264_ctx->mb_width, + h264_ctx->mb_height); + } + } + + if (h264_ctx->list_size[1] > 0) { + for (i = 0; i < h264_ctx->list_size[1]; i++) { + buf = &h264_ctx->ref_list1[i]; + config_decode_canvas(hw, buf, h264_ctx->mb_width, + h264_ctx->mb_height); + } + } +} + +static void release_canvas_index(struct aml_vdec_hw *hw, + struct h264_decode_buf_spec *buf) +{ + if (buf->y_canvas_index >= 0) { + dev_dbg(hw->dev, "free y_canvas %d\n", buf->y_canvas_index); + meson_canvas_free(hw->canvas, buf->y_canvas_index); + buf->y_canvas_index = -1; + } + + if (buf->u_canvas_index >= 0) { + dev_dbg(hw->dev, "free uv_canvas_index %d\n", + buf->u_canvas_index); + meson_canvas_free(hw->canvas, buf->u_canvas_index); + buf->u_canvas_index = -1; + buf->v_canvas_index = -1; + } +} + +static void h264_release_decode_spec(struct aml_vdec_hw *hw, struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx; + int i; + struct h264_decode_buf_spec *buf; + + release_canvas_index(hw, &h264_ctx->curr_spec); + + if (h264_ctx->list_size[0] > 0) { + for (i = 0; i < h264_ctx->list_size[0]; i++) { + buf = &h264_ctx->ref_list0[i]; + if (buf->used) { + buf->dpb = NULL; + release_canvas_index(hw, buf); + buf->used = 0; + } + } + h264_ctx->list_size[0] = 0; + } + + if (h264_ctx->list_size[1] > 0) { + for (i = 0; i < h264_ctx->list_size[1]; i++) { + buf = &h264_ctx->ref_list1[i]; + if (buf->used) { + buf->dpb = NULL; + release_canvas_index(hw, buf); + buf->used = 0; + } + } + h264_ctx->list_size[1] = 0; + } +} + +static void save_reg_status(struct aml_h264_ctx *h264_ctx) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + + regmap_read(hw->map[DOS_BUS], IQIDCT_CONTROL, &h264_ctx->reg_iqidct_control); + h264_ctx->reg_iqidct_control_init_flag = 1; + regmap_read(hw->map[DOS_BUS], VCOP_CTRL_REG, &h264_ctx->reg_vcop_ctrl_reg); + regmap_read(hw->map[DOS_BUS], RV_AI_MB_COUNT, &h264_ctx->reg_rv_ai_mb_count); + regmap_read(hw->map[DOS_BUS], VLD_DECODE_CONTROL, &h264_ctx->vld_dec_control); +} + +static void h264_get_slice_params(struct aml_h264_ctx *h264_ctx) +{ + struct slice *curr_slice = &h264_ctx->mslice; + + memset(curr_slice, 0, sizeof(struct slice)); + /* parsed by ucode */ + switch (h264_ctx->dpb_param.l.data[SLICE_TYPE]) { + case I_Slice: + curr_slice->slice_type = I_SLICE; + break; + case P_Slice: + curr_slice->slice_type = P_SLICE; + break; + case B_Slice: + curr_slice->slice_type = B_SLICE; + break; + default: + curr_slice->slice_type = MAX_SLICE_TYPES; + break; + } + + curr_slice->first_mb_in_slice = + h264_ctx->dpb_param.l.data[FIRST_MB_IN_SLICE]; + curr_slice->num_ref_idx_l0 = + h264_ctx->dpb_param.dpb.num_ref_idx_l0_active_minus1 + 1; + curr_slice->num_ref_idx_l1 = + h264_ctx->dpb_param.dpb.num_ref_idx_l1_active_minus1 + 1; + curr_slice->frame_num = h264_ctx->ctrl_ref.decode->frame_num; +} + +static irqreturn_t h264_isr(int irq, void *priv) +{ + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + + regmap_write(dev->dec_hw->map[DOS_BUS], VDEC_ASSIST_MBOX1_CLR_REG, 1); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t h264_threaded_isr_func(int irq, void *priv) +{ + u32 dec_status; + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)dev->dec_hw->curr_ctx; + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)dev->dec_ctx; + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + unsigned short *p = (unsigned short *)h264_ctx->lmem_addr; + int i, ii; + + regmap_read(hw->map[DOS_BUS], DPB_STATUS_REG, &dec_status); + h264_ctx->dec_status = dec_status; + dev_dbg + (&dev->plat_dev->dev, + "%s, dec_status 0x%x VIFF_BIT_CNT 0x%x MBY_MBX 0x%x VLD_SHIFT_STATUS 0x%x\n", + __func__, dec_status, read_dos_reg(hw, VIFF_BIT_CNT), + read_dos_reg(hw, MBY_MBX), read_dos_reg(hw, VLD_SHIFT_STATUS)); + + regmap_read(hw->map[DOS_BUS], AV_SCRATCH_F, &h264_ctx->save_avscratch_f); + + switch (dec_status) { + case H264_SLICE_HEADER_DONE: + for (i = 0; i < 0x400; i += 4) + for (ii = 0; ii < 4; ii++) + h264_ctx->dpb_param.l.data[i + ii] = p[i + 3 - ii]; + save_reg_status(h264_ctx); + h264_get_slice_params(h264_ctx); + if (h264_ctx->mslice.first_mb_in_slice != 0) + h264_release_decode_spec(hw, ctx); + + h264_config_decode_spec(hw, ctx); + h264_reorder_reflists(h264_ctx); + get_canvas_index(hw, ctx); + + if (h264_config_decode_buf(hw, ctx) < 0) { + h264_release_decode_spec(hw, ctx); + ctx->int_cond = 1; + wake_up_interruptible(&ctx->queue); + goto irq_handled; + } + if (h264_ctx->new_pic_flag == 1) { + regmap_write(hw->map[DOS_BUS], DPB_STATUS_REG, H264_ACTION_DECODE_NEWPIC); + dev_dbg(&dev->plat_dev->dev, "action decode new pic\n"); + h264_ctx->new_pic_flag = 0; + } else { + regmap_write(hw->map[DOS_BUS], DPB_STATUS_REG, H264_ACTION_DECODE_SLICE); + dev_dbg(&dev->plat_dev->dev, "action decode new slice\n"); + } + break; + case H264_SLICE_DATA_DONE: + h264_release_decode_spec(hw, ctx); + h264_ctx->decode_pic_count++; + ctx->int_cond = 1; + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx, + VB2_BUF_STATE_DONE); + wake_up_interruptible(&ctx->queue); + break; + default: + h264_release_decode_spec(hw, ctx); + ctx->int_cond = 1; + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx, + VB2_BUF_STATE_ERROR); + wake_up_interruptible(&ctx->queue); + break; + } +irq_handled: + return IRQ_HANDLED; +} + +static int h264_restore_hw_ctx(struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv; + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + + regmap_write(hw->map[DOS_BUS], POWER_CTL_VLD, + (read_dos_reg(hw, POWER_CTL_VLD) | (0 << 10) | (1 << 9) | (1 << 6))); + + regmap_write(hw->map[DOS_BUS], PSCALE_CTRL, 0); + + /* clear mailbox interrupt */ + regmap_write(hw->map[DOS_BUS], VDEC_ASSIST_MBOX1_CLR_REG, 1); + + /* enable mailbox interrupt */ + regmap_write(hw->map[DOS_BUS], VDEC_ASSIST_MBOX1_MASK, 1); + + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 17), (1 << 17)); + if (ctx->dec_fmt[AML_FMT_DST].fourcc == V4L2_PIX_FMT_NV21 || + ctx->dec_fmt[AML_FMT_DST].fourcc == V4L2_PIX_FMT_NV21M) + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, + (1 << 16), (1 << 16)); + else + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 16), 0); + + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, + (0xbf << 24), (0xbf << 24)); + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (0xbf << 24), 0); + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 31), 0); + + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_MUX_CTRL, (1 << 31), 0); + regmap_write(hw->map[DOS_BUS], MDEC_EXTIF_CFG1, 0); + regmap_write(hw->map[DOS_BUS], MDEC_PIC_DC_THRESH, 0x404038aa); + + regmap_write(hw->map[DOS_BUS], DPB_STATUS_REG, 0); + + regmap_write(hw->map[DOS_BUS], LMEM_DUMP_ADR, h264_ctx->lmem_phy_addr); + regmap_write(hw->map[DOS_BUS], FRAME_COUNTER_REG, h264_ctx->decode_pic_count); + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_8, h264_ctx->workspace_offset); + + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_F, + ((h264_ctx->save_avscratch_f & 0xffffffc3) | (1 << 4))); + regmap_update_bits(hw->map[DOS_BUS], AV_SCRATCH_F, (1 << 6), 0); + + regmap_write(hw->map[DOS_BUS], MDEC_PIC_DC_THRESH, 0x404038aa); + + if (h264_ctx->reg_iqidct_control_init_flag == 0) + regmap_write(hw->map[DOS_BUS], IQIDCT_CONTROL, 0x200); + + if (h264_ctx->reg_iqidct_control) + regmap_write(hw->map[DOS_BUS], IQIDCT_CONTROL, h264_ctx->reg_iqidct_control); + + if (h264_ctx->reg_vcop_ctrl_reg) + regmap_write(hw->map[DOS_BUS], VCOP_CTRL_REG, h264_ctx->reg_vcop_ctrl_reg); + + if (h264_ctx->vld_dec_control) + regmap_write(hw->map[DOS_BUS], VLD_DECODE_CONTROL, h264_ctx->vld_dec_control); + + dev_dbg + (hw->dev, + "IQIDCT_CONTROL = 0x%x, VCOP_CTRL_REG 0x%x VLD_DECODE_CONTROL 0x%x\n", + read_dos_reg(hw, IQIDCT_CONTROL), read_dos_reg(hw, VCOP_CTRL_REG), + read_dos_reg(hw, VLD_DECODE_CONTROL)); + + return 0; +} + +static void *aml_h264_get_ctrl(struct v4l2_ctrl_handler *hdl, u32 id) +{ + struct v4l2_ctrl *ctrl; + + ctrl = v4l2_ctrl_find(hdl, id); + return ctrl ? ctrl->p_cur.p : NULL; +} + +static int aml_h264_get_stateless_ctrl_ref(struct aml_h264_ctx *h264_ctx) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + + ctrls->sps = + (struct v4l2_ctrl_h264_sps *)aml_h264_get_ctrl(&ctx->ctrl_handler, + V4L2_CID_STATELESS_H264_SPS); + if (WARN_ON(!ctrls->sps)) + return -EINVAL; + + ctrls->pps = + (struct v4l2_ctrl_h264_pps *)aml_h264_get_ctrl(&ctx->ctrl_handler, + V4L2_CID_STATELESS_H264_PPS); + if (WARN_ON(!ctrls->pps)) + return -EINVAL; + + ctrls->decode = + (struct v4l2_ctrl_h264_decode_params *)aml_h264_get_ctrl(&ctx->ctrl_handler, + V4L2_CID_STATELESS_H264_DECODE_PARAMS); + if (WARN_ON(!ctrls->decode)) + return -EINVAL; + + ctrls->scaling = + (struct v4l2_ctrl_h264_scaling_matrix *)aml_h264_get_ctrl(&ctx->ctrl_handler, + V4L2_CID_STATELESS_H264_SCALING_MATRIX); + if (WARN_ON(!ctrls->scaling)) + return -EINVAL; + + return 0; +} + +static void copy_mc_cpu_fw(void *mc_cpu_addr, const u8 *data) +{ + /*header */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_HEADER, + data + 0x4000, MC_SWAP_SIZE); + /*data */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_DATA, + data + 0x2000, MC_SWAP_SIZE); + /*mmco */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_MMCO, + data + 0x6000, MC_SWAP_SIZE); + /*list */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_LIST, + data + 0x3000, MC_SWAP_SIZE); + /*slice */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_SLICE, + data + 0x5000, MC_SWAP_SIZE); + /*main */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_MAIN, data, 0x2000); + /*data */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_MAIN + 0x2000, + data + 0x2000, 0x1000); + /*slice */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_MAIN + 0x3000, + data + 0x5000, 0x1000); +} + +static int aml_h264_load_fw_ext(void *priv, const u8 *data, u32 len) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)priv; + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)h264_ctx->v4l2_ctx; + struct aml_vdec_hw *dec_hw; + + if (h264_ctx->mc_cpu_loaded) + return 0; + + dec_hw = vdec_get_hw(ctx->dev); + if (!dec_hw) + return -1; + + if (len > MC_TOTAL_SIZE) { + dev_info(dec_hw->dev, "size of mc_cpu_fw id invalid\n"); + return -1; + } + + h264_ctx->mc_cpu_vaddr = dma_alloc_coherent(dec_hw->dev, MC_TOTAL_SIZE, + &h264_ctx->mc_cpu_paddr, + GFP_KERNEL); + if (!h264_ctx->mc_cpu_vaddr) + return -ENOMEM; + + copy_mc_cpu_fw(h264_ctx->mc_cpu_vaddr, data); + + h264_ctx->mc_cpu_loaded = true; + + dev_dbg(dec_hw->dev, "h264 mccpu fw loaded\n"); + + return 0; +} + +int aml_h264_init(void *priv) +{ + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv; + struct aml_vdec_hw *dec_hw; + struct aml_h264_ctx *h264_ctx; + int ret = 0; + + h264_ctx = kzalloc_obj(*h264_ctx, GFP_KERNEL); + if (!h264_ctx) + return -ENOMEM; + + h264_ctx->v4l2_ctx = ctx; + dec_hw = vdec_get_hw(ctx->dev); + if (!dec_hw) + return -1; + + h264_ctx->mc_cpu_loaded = false; + dec_hw->hw_ops.irq_handler = h264_isr; + dec_hw->hw_ops.irq_threaded_func = h264_threaded_isr_func; + dec_hw->hw_ops.load_firmware_ex = aml_h264_load_fw_ext; + + h264_ctx->lmem_addr = dma_alloc_coherent(dec_hw->dev, LMEM_DUMP_SIZE, + &h264_ctx->lmem_phy_addr, + GFP_KERNEL); + if (!h264_ctx->lmem_addr) { + ret = -ENOMEM; + goto err_alloc_lmem; + } + + h264_ctx->cma_alloc_vaddr = + dma_alloc_coherent(dec_hw->dev, V_BUF_ADDR_OFFSET, + &h264_ctx->cma_alloc_addr, GFP_KERNEL); + if (!h264_ctx->cma_alloc_vaddr) { + ret = -ENOMEM; + goto err_alloc_workspace; + } + + h264_ctx->workspace_offset = h264_ctx->cma_alloc_addr + DCAC_READ_MARGIN; + h264_ctx->workspace_vaddr = h264_ctx->cma_alloc_vaddr + DCAC_READ_MARGIN; + + ctx->codec_priv = h264_ctx; + dec_hw->curr_ctx = h264_ctx; + h264_ctx->col_buf_alloc_size = 0; + h264_ctx->init_flag = 0; + h264_ctx->new_pic_flag = 0; + h264_ctx->param_set = 0; + h264_ctx->reg_iqidct_control_init_flag = 0; + h264_ctx->decode_pic_count = 0; + + return 0; + +err_alloc_workspace: + dma_free_coherent(dec_hw->dev, LMEM_DUMP_SIZE, + h264_ctx->lmem_addr, h264_ctx->lmem_phy_addr); +err_alloc_lmem: + kfree(h264_ctx); + + return ret; +} + +void aml_h264_exit(void *priv) +{ + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv; + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv; + struct aml_vdec_hw *dec_hw; + + if (!h264_ctx) { + dev_info(&ctx->dev->plat_dev->dev, + "h264 decoder is already destroyed or not created!\n"); + return; + } + dec_hw = vdec_get_hw(ctx->dev); + h264_ctx->param_set = 0; + + if (ctx->dos_clk_en) + aml_stop_vdec_hw(dec_hw); + + if (h264_ctx->collated_cma_vaddr) { + dma_free_coherent(dec_hw->dev, h264_ctx->col_buf_alloc_size, + h264_ctx->collated_cma_vaddr, + h264_ctx->collated_cma_addr); + h264_ctx->col_buf_alloc_size = 0; + } + + if (h264_ctx->mc_cpu_vaddr) { + dma_free_coherent(dec_hw->dev, MC_TOTAL_SIZE, + h264_ctx->mc_cpu_vaddr, + h264_ctx->mc_cpu_paddr); + h264_ctx->mc_cpu_loaded = false; + } + + if (h264_ctx->lmem_addr) + dma_free_coherent(dec_hw->dev, LMEM_DUMP_SIZE, + h264_ctx->lmem_addr, h264_ctx->lmem_phy_addr); + + if (h264_ctx->cma_alloc_vaddr) + dma_free_coherent(dec_hw->dev, V_BUF_ADDR_OFFSET, + h264_ctx->cma_alloc_vaddr, + h264_ctx->cma_alloc_addr); + + kfree(ctx->codec_priv); + dec_hw->curr_ctx = NULL; + ctx->codec_priv = NULL; +} + +static void config_decode_mode(struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv; + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + + regmap_write(hw->map[DOS_BUS], H264_DECODE_MODE, 0x1); /*decode mode framebase */ + regmap_write(hw->map[DOS_BUS], HEAD_PADDING_REG, 0); + regmap_write(hw->map[DOS_BUS], H264_DECODE_SEQINFO, h264_ctx->seq_info); + regmap_write(hw->map[DOS_BUS], INIT_FLAG_REG, 1); +} + +int aml_h264_dec_run(void *priv) +{ + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv; + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv; + struct aml_vdec_hw *dec_hw = vdec_get_hw(ctx->dev); + int ret = -1; + int i; + + ret = aml_h264_get_stateless_ctrl_ref(h264_ctx); + if (ret < 0) { + dev_err(&ctx->dev->plat_dev->dev, "not ctrl ref for h264 decoder\n"); + return ret; + } + + h264_ctx->new_pic_flag = 1; + h264_config_params(ctx); + + if (h264_prepare_input(ctx) < 0) + return ret; + + if (alloc_colocate_cma(h264_ctx, ctx) < 0) + return ret; + + h264_restore_hw_ctx(ctx); + + config_decode_mode(ctx); + /* enable stream input hardware */ + regmap_update_bits(dec_hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, 0x6, 0x6); + /* enable hardware timer */ + regmap_write(dec_hw->map[DOS_BUS], NAL_SEARCH_CTL, + read_dos_reg(dec_hw, NAL_SEARCH_CTL) | (1 << 16)); + regmap_write(dec_hw->map[DOS_BUS], MDEC_EXTIF_CFG2, + read_dos_reg(dec_hw, MDEC_EXTIF_CFG2) | 0x20); + regmap_write(dec_hw->map[DOS_BUS], NAL_SEARCH_CTL, + read_dos_reg(dec_hw, NAL_SEARCH_CTL) & (~0x2)); + regmap_update_bits(dec_hw->map[DOS_BUS], VDEC_ASSIST_MMC_CTRL1, + (1 << 3), 0); + + aml_start_vdec_hw(dec_hw); + h264_ctx->init_flag = 1; + + regmap_write(dec_hw->map[DOS_BUS], DPB_STATUS_REG, H264_ACTION_SEARCH_HEAD); + + ret = wait_event_interruptible_timeout(ctx->queue, ctx->int_cond, + msecs_to_jiffies(DECODER_TIMEOUT_MS)); + ctx->int_cond = 0; + if (!ret) { + ret = -1; + dev_err(&ctx->dev->plat_dev->dev, "dec timeout=%u\n", DECODER_TIMEOUT_MS); + for (i = 0; i < 16; i++) { /* 16 : show ucode PC 16 times when timeout */ + dev_info(&ctx->dev->plat_dev->dev, "decoder timeout, pc 0x%x\n", + read_dos_reg(dec_hw, MPC_E)); + usleep_range(10, 20); + } + h264_release_decode_spec(dec_hw, ctx); + } else if (-ERESTARTSYS == ret) { + ret = -1; + h264_release_decode_spec(dec_hw, ctx); + dev_err(&ctx->dev->plat_dev->dev, "dec inter fail\n"); + } + + aml_stop_vdec_hw(dec_hw); + h264_ctx->init_flag = 0; + + return ret; +} diff --git a/drivers/media/platform/amlogic/vdec/h264.h b/drivers/media/platform/amlogic/vdec/h264.h new file mode 100644 index 000000000000..830ab3241a1e --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/h264.h @@ -0,0 +1,299 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ +#ifndef _H264_H_ +#define _H264_H_ + +#define RPM_BEGIN 0x0 +#define FRAME_IN_DPB 24 +#define RPM_END 0x400 +#define DPB_OFFSET 0x100 +#define MMCO_OFFSET 0x200 +#define SPS_OFFSET 0x100 +#define PPS_OFFSET 0x300 +#define PARAM_BASE_VAL 0x414d +#define MEM_MMCO_BASE 0x01c3000 +#define MEM_SPS_BASE 0x01c3c00 +#define MEM_PPS_BASE 0x01cbc00 +#define MC_TOTAL_SIZE ((20 + 16) * SZ_1K) +#define MC_SWAP_SIZE (4 * SZ_1K) +#define LMEM_DUMP_SIZE 4096 +#define V_BUF_ADDR_OFFSET (0x200000 + 0x8000 + 0x20000 + 0x1000) +#define DCAC_READ_MARGIN (64 * 1024) +#define MC_OFFSET_HEADER 0x0000 +#define MC_OFFSET_DATA 0x1000 +#define MC_OFFSET_MMCO 0x2000 +#define MC_OFFSET_LIST 0x3000 +#define MC_OFFSET_SLICE 0x4000 +#define MC_OFFSET_MAIN 0x5000 + +/* Rename the dos regs */ +#define H264_DECODE_INFO M4_CONTROL_REG +#define INIT_FLAG_REG AV_SCRATCH_2 +#define HEAD_PADDING_REG AV_SCRATCH_3 +#define UCODE_WATCHDOG_REG AV_SCRATCH_7 +#define LMEM_DUMP_ADR AV_SCRATCH_L +#define DEBUG_REG1 AV_SCRATCH_M +#define DEBUG_REG2 AV_SCRATCH_N +#define FRAME_COUNTER_REG AV_SCRATCH_I +#define RPM_CMD_REG AV_SCRATCH_A +#define H264_DECODE_SIZE AV_SCRATCH_E +#define H264_DECODE_MODE AV_SCRATCH_4 +#define H264_DECODE_SEQINFO AV_SCRATCH_5 +/** + * NAL_SEARCH_CTL: bit 0, enable itu_t35 + * NAL_SEARCH_CTL: bit 1, enable mmu + * NAL_SEARCH_CTL: bit 2, detect frame_mbs_only_flag whether switch resolution + * NAL_SEARCH_CTL: bit 3, recover the correct sps pps + * NAL_SEARCH_CTL: bit 7-14,level_idc + * NAL_SEARCH_CTL: bit 15,bitstream_restriction_flag + */ +#define NAL_SEARCH_CTL AV_SCRATCH_9 +#define DPB_STATUS_REG AV_SCRATCH_J +#define ERROR_STATUS_REG AV_SCRATCH_9 + +#define H264_BUFFER_INFO_INDEX PMV3_X /* 0xc24 */ +#define H264_BUFFER_INFO_DATA PMV2_X /* 0xc22 */ +#define H264_CURRENT_POC_IDX_RESET LAST_SLICE_MV_ADDR /* 0xc30 */ +#define H264_CURRENT_POC LAST_MVY /* 0xc32 shared with conceal MV */ +#define H264_CO_MB_WR_ADDR VLD_C38 +#define H264_CO_MB_RD_ADDR VLD_C39 +#define H264_CO_MB_RW_CTL VLD_C3D +#define MBY_MBX MB_MOTION_MODE + +#define H264_ACTION_SEARCH_HEAD 0xf0 +#define H264_ACTION_DECODE_SLICE 0xf1 +#define H264_ACTION_CONFIG_DONE 0xf2 +#define H264_ACTION_DECODE_NEWPIC 0xf3 +#define H264_ACTION_DECODE_START 0xff + +/* RPM memory definition */ +#define FIXED_FRAME_RATE_FLAG 0X21 +#define OFFSET_DELIMITER_LO 0x2f +#define OFFSET_DELIMITER_HI 0x30 +#define SLICE_IPONLY_BREAK 0X5C +#define PREV_MAX_REFERENCE_FRAME_NUM 0X5D +#define EOS 0X5E +#define FRAME_PACKING_TYPE 0X5F +#define OLD_POC_PAR_1 0X60 +#define OLD_POC_PAR_2 0X61 +#define PREV_MBX 0X62 +#define PREV_MBY 0X63 +#define ERROR_SKIP_MB_NUM 0X64 +#define ERROR_MB_STATUS 0X65 +#define L0_PIC0_STATUS 0X66 +#define TIMEOUT_COUNTER 0X67 +#define BUFFER_SIZE 0X68 +#define BUFFER_SIZE_HI 0X69 +#define CROPPING_LEFT_RIGHT 0X6A +#define CROPPING_TOP_BOTTOM 0X6B +/** + * sps_flags2: + * bit 3, bitstream_restriction_flag + * bit 2, pic_struct_present_flag + * bit 1, vcl_hrd_parameters_present_flag + * bit 0, nal_hrd_parameters_present_flag + */ +#define SPS_FLAGS2 0x6C +#define NUM_REORDER_FRAMES 0x6D +#define MAX_BUFFER_FRAME 0X6E + +#define NON_CONFORMING_STREAM 0X70 +#define RECOVERY_POINT 0X71 +#define POST_CANVAS 0X72 +#define POST_CANVAS_H 0X73 +#define SKIP_PIC_COUNT 0X74 +#define TARGET_NUM_SCALING_LIST 0X75 +#define FF_POST_ONE_FRAME 0X76 +#define PREVIOUS_BIT_CNT 0X77 +#define MB_NOT_SHIFT_COUNT 0X78 +#define PIC_STATUS 0X79 +#define FRAME_COUNTER 0X7A +#define NEW_SLICE_TYPE 0X7B +#define NEW_PICTURE_STRUCTURE 0X7C +#define NEW_FRAME_NUM 0X7D +#define NEW_IDR_PIC_ID 0X7E +#define IDR_PIC_ID 0X7F + +/* h264 LOCAL */ +#define NAL_UNIT_TYPE 0X80 +#define NAL_REF_IDC 0X81 +#define SLICE_TYPE 0X82 +#define LOG2_MAX_FRAME_NUM 0X83 +#define FRAME_MBS_ONLY_FLAG 0X84 +#define PIC_ORDER_CNT_TYPE 0X85 +#define LOG2_MAX_PIC_ORDER_CNT_LSB 0X86 +#define PIC_ORDER_PRESENT_FLAG 0X87 +#define REDUNDANT_PIC_CNT_PRESENT_FLAG 0X88 +#define PIC_INIT_QP_MINUS26 0X89 +#define DEBLOCKING_FILTER_CONTROL_PRESENT_FLAG 0X8A +#define NUM_SLICE_GROUPS_MINUS1 0X8B +#define MODE_8X8_FLAGS 0X8C +#define ENTROPY_CODING_MODE_FLAG 0X8D +#define SLICE_QUANT 0X8E +#define TOTAL_MB_HEIGHT 0X8F +#define PICTURE_STRUCTURE 0X90 +#define TOP_INTRA_TYPE 0X91 +#define RV_AI_STATUS 0X92 +#define AI_READ_START 0X93 +#define AI_WRITE_START 0X94 +#define AI_CUR_BUFFER 0X95 +#define AI_DMA_BUFFER 0X96 +#define AI_READ_OFFSET 0X97 +#define AI_WRITE_OFFSET 0X98 +#define AI_WRITE_OFFSET_SAVE 0X99 +#define RV_AI_BUFF_START 0X9A +#define I_PIC_MB_COUNT 0X9B +#define AI_WR_DCAC_DMA_CTRL 0X9C +#define SLICE_MB_COUNT 0X9D +#define PICTYPE 0X9E +#define SLICE_GROUP_MAP_TYPE 0X9F +#define MB_TYPE 0XA0 +#define MB_AFF_ADDED_DMA 0XA1 +#define PREVIOUS_MB_TYPE 0XA2 +#define WEIGHTED_PRED_FLAG 0XA3 +#define WEIGHTED_BIPRED_IDC 0XA4 +/* bit 3:2 - PICTURE_STRUCTURE + * bit 1 - MB_ADAPTIVE_FRAME_FIELD_FLAG + * bit 0 - FRAME_MBS_ONLY_FLAG + */ +#define MBFF_INFO 0XA5 +#define TOP_INTRA_TYPE_TOP 0XA6 +#define RV_AI_BUFF_INC 0xA7 +#define DEFAULT_MB_INFO_LO 0xA8 +/* 0 -- no need to read + * 1 -- need to wait Left + * 2 -- need to read Intra + * 3 -- need to read back MV + */ +#define NEED_READ_TOP_INFO 0xA9 +/* 0 -- idle + * 1 -- wait Left + * 2 -- reading top Intra + * 3 -- reading back MV + */ +#define READ_TOP_INFO_STATE 0xAA +#define DCAC_MBX 0xAB +#define TOP_MB_INFO_OFFSET 0xAC +#define TOP_MB_INFO_RD_IDX 0xAD +#define TOP_MB_INFO_WR_IDX 0xAE + +#define VLD_NO_WAIT 0 +#define VLD_WAIT_BUFFER 1 +#define VLD_WAIT_HOST 2 +#define VLD_WAIT_GAP 3 + +#define VLD_WAITING 0xAF + +#define MB_X_NUM 0xB0 +#define MB_HEIGHT 0xB2 +#define MBX 0xB3 +#define TOTAL_MBY 0xB4 +#define INTR_MSK_SAVE 0xB5 +#define NEED_DISABLE_PPE 0xB6 +#define IS_NEW_PICTURE 0XB7 +#define PREV_NAL_REF_IDC 0XB8 +#define PREV_NAL_UNIT_TYPE 0XB9 +#define FRAME_MB_COUNT 0XBA +#define REF_IDC_OVERRIDE_FLAG 0XBB +#define SLICE_GROUP_CHANGE_RATE 0XBC +#define SLICE_GROUP_CHANGE_CYCLE_LEN 0XBD +#define DELAY_LENGTH 0XBE +#define PICTURE_STRUCT 0XBF +#define DCAC_PREVIOUS_MB_TYPE 0xC1 + +#define TIME_STAMP 0XC2 +#define H_TIME_STAMP 0XC3 +#define VPTS_MAP_ADDR 0XC4 +#define H_VPTS_MAP_ADDR 0XC5 +#define PIC_INSERT_FLAG 0XC7 +#define TIME_STAMP_START 0XC8 +#define TIME_STAMP_END 0XDF +#define OFFSET_FOR_NON_REF_PIC 0XE0 +#define OFFSET_FOR_TOP_TO_BOTTOM_FIELD 0XE2 +#define MAX_REFERENCE_FRAME_NUM 0XE4 +#define FRAME_NUM_GAP_ALLOWED 0XE5 +#define NUM_REF_FRAMES_IN_PIC_ORDER_CNT_CYCLE 0XE6 +#define PROFILE_IDC_MMCO 0XE7 +#define LEVEL_IDC_MMCO 0XE8 +#define FRAME_SIZE_IN_MB 0XE9 +#define DELTA_PIC_ORDER_ALWAYS_ZERO_FLAG 0XEA +#define PPS_NUM_REF_IDX_L0_ACTIVE_MINUS1 0XEB +#define PPS_NUM_REF_IDX_L1_ACTIVE_MINUS1 0XEC +#define CURRENT_SPS_ID 0XED +#define CURRENT_PPS_ID 0XEE +/* bit 0 - sequence parameter set may change + * bit 1 - picture parameter set may change + * bit 2 - new dpb just inited + * bit 3 - IDR picture not decoded yet + * bit 5:4 - 0: mb level code loaded 1: picture + * level code loaded 2: slice level code loaded + */ +#define DECODE_STATUS 0XEF +#define FIRST_MB_IN_SLICE 0XF0 +#define PREV_MB_WIDTH 0XF1 +#define PREV_FRAME_SIZE_IN_MB 0XF2 +/* bit 0 - aspect_ratio_info_present_flag + * bit 1 - timing_info_present_flag + * bit 2 - nal_hrd_parameters_present_flag + * bit 3 - vcl_hrd_parameters_present_flag + * bit 4 - pic_struct_present_flag + * bit 5 - bitstream_restriction_flag + */ +#define VUI_STATUS 0XF4 +#define ASPECT_RATIO_IDC 0XF5 +#define ASPECT_RATIO_SAR_WIDTH 0XF6 +#define ASPECT_RATIO_SAR_HEIGHT 0XF7 +#define NUM_UNITS_IN_TICK 0XF8 +#define TIME_SCALE 0XFA +#define CURRENT_PIC_INFO 0XFC +#define DPB_BUFFER_INFO 0XFD +#define REFERENCE_POOL_INFO 0XFE +#define REFERENCE_LIST_INFO 0XFF + +#define REORDER_CMD_MAX 66 + +/* config parameters to DDR lmem */ +#define GET_SPS_PROFILE_IDC(x) (((x) & 0xff) << 8) +#define GET_SPS_LEVEL_IDC(x) ((x) & 0xff) +#define GET_SPS_SEQ_PARAM_SET_ID(x) (((x) & 0x1f) << 8) +#define GET_SPS_CHROMA_FORMAT_IDC(x) ((x) << 8) +#define GET_SPS_NUM_REF_FRAMES(x) ((x) & 0xff) +#define GET_SPS_GAPS_ALLOWED_FLAG(x) ((x) << 8) +#define GET_SPS_LOG2_MAX_FRAME_NUM(x) ((x) + 4) +#define GET_SPS_PIC_ORDER_CNT_LSB(x) ((x) + 4) +#define GET_SPS_PIC_ORDER_TYPE(x) (x) +#define GET_SPS_OFFSET_FOR_NONREF_PIC_HIGH(x) (((x) & 0xffff0000) >> 16) +#define GET_SPS_OFFSET_FOR_NONREF_PIC_LOW(x) ((x) & 0xffff) +#define GET_SPS_OFFSET_FOR_TOP_BOT_FIELD_HIGH(x) (((x) & 0xffff0000) >> 16) +#define GET_SPS_OFFSET_FOR_TOP_BOT_FIELD_LOW(x) ((x) & 0xffff) +#define GET_SPS_PIC_WIDTH_IN_MBS(x) ((x) + 1) +#define GET_SPS_PIC_HEIGHT_IN_MBS(x) ((x) + 1) +#define GET_SPS_DIRECT_8X8_FLAGS(x) (((x) & 0x1) << 2) +#define GET_SPS_MB_ADAPTIVE_FRAME_FIELD_FLAGS(x) (((x) & 0x1) << 1) +#define GET_SPS_FRAME_MBS_ONLY_FLAGS(x) ((x) & 0x1) + +#define GET_PPS_PIC_PARAM_SET_ID(x) ((x) & 0xff) +#define GET_PPS_SEQ_PARAM_SET_ID(x) (((x) & 0x1f) << 8) +#define GET_PPS_ENTROPY_CODING_MODE_FLAG(x) (((x) & 0x1) << 13) +#define GET_PPS_PIC_ORDER_PRESENT_FLAG(x) (((x) & 0x1) << 14) +#define GET_PPS_NUM_IDX_REF_L0_MINUS1(x) ((x) & 0x1f) +#define GET_PPS_NUM_IDX_REF_L1_MINUS1(x) (((x) & 0x1f) << 5) +#define GET_PPS_WEIGHTED_PRED_FLAG(x) (((x) & 0x1) << 10) +#define GET_PPS_WEIGHTED_BIPRED_IDC(x) (((x) & 0x3) << 11) +#define GET_PPS_INIT_QS_MINUS26(x) (((x) & 0xff) << 8) +#define GET_PPS_INIT_QP_MINUS26(x) ((x) & 0xff) +#define GET_PPS_CHROMA_QP_INDEX_OFFSET(x) ((x) & 0xff) +#define GET_PPS_DEBLOCK_FILTER_CTRL_PRESENT_FLAG(x) (((x) & 0x1) << 8) +#define GET_PPS_CONSTRAIN_INTRA_PRED_FLAG(x) (((x) & 0x1) << 9) +#define GET_PPS_REDUNDANT_PIC_CNT_PRESENT_FLAG(x) (((x) & 0x1) << 10) +#define GET_PPS_SCALING_MATRIX_PRESENT_FLAG(x) (((x) & 0x1) << 1) +#define GET_PPS_TRANSFORM_8X8_FLAG(x) ((x) & 0x1) +#define GET_PPS_GET_SECOND_CHROMA_QP_OFFSET(x) (x) + +int aml_h264_init(void *priv); +void aml_h264_exit(void *priv); +int aml_h264_dec_run(void *priv); + +#endif diff --git a/drivers/media/platform/amlogic/vdec/reg_defines.h b/drivers/media/platform/amlogic/vdec/reg_defines.h new file mode 100644 index 000000000000..ea50018a078d --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/reg_defines.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#ifndef _REG_DEFINES_H_ +#define _REG_DEFINES_H_ + +#define REG_ALIGN(x) ((x) << 2) + +#define VDEC_ASSIST_MMC_CTRL0 REG_ALIGN(0x0001) +#define VDEC_ASSIST_MMC_CTRL1 REG_ALIGN(0x0002) + +#define VDEC_ASSIST_CANVAS_BLK32 REG_ALIGN(0x0005) + +#define VDEC_ASSIST_MBOX1_CLR_REG REG_ALIGN(0x0075) +#define VDEC_ASSIST_MBOX1_MASK REG_ALIGN(0x0076) + +#define MPSR REG_ALIGN(0x0301) +#define MPC_P REG_ALIGN(0x0306) +#define MPC_D REG_ALIGN(0x0307) +#define MPC_E REG_ALIGN(0x0308) +#define MPC_W REG_ALIGN(0x0309) +#define CPSR REG_ALIGN(0x0321) +#define IMEM_DMA_CTRL REG_ALIGN(0x0340) +#define IMEM_DMA_ADR REG_ALIGN(0x0341) +#define IMEM_DMA_COUNT REG_ALIGN(0x0342) +#define WRRSP_IMEM REG_ALIGN(0x0343) +#define LMEM_DMA_CTRL REG_ALIGN(0x0350) +#define WRRSP_LMEM REG_ALIGN(0x0353) + +#define PSCALE_CTRL REG_ALIGN(0x0911) +#define GCLK_EN REG_ALIGN(0x0983) +#define MDEC_PIC_DC_CTRL REG_ALIGN(0x098e) +#define MDEC_PIC_DC_MUX_CTRL REG_ALIGN(0x098d) +#define ANC0_CANVAS_ADDR REG_ALIGN(0x0990) +#define ANC1_CANVAS_ADDR REG_ALIGN(0x0991) +#define ANC2_CANVAS_ADDR REG_ALIGN(0x0992) +#define ANC3_CANVAS_ADDR REG_ALIGN(0x0993) +#define ANC4_CANVAS_ADDR REG_ALIGN(0x0994) +#define ANC5_CANVAS_ADDR REG_ALIGN(0x0995) +#define ANC6_CANVAS_ADDR REG_ALIGN(0x0996) +#define ANC7_CANVAS_ADDR REG_ALIGN(0x0997) +#define ANC8_CANVAS_ADDR REG_ALIGN(0x0998) +#define ANC9_CANVAS_ADDR REG_ALIGN(0x0999) +#define ANC10_CANVAS_ADDR REG_ALIGN(0x099a) +#define ANC11_CANVAS_ADDR REG_ALIGN(0x099b) +#define ANC12_CANVAS_ADDR REG_ALIGN(0x099c) +#define ANC13_CANVAS_ADDR REG_ALIGN(0x099d) +#define ANC14_CANVAS_ADDR REG_ALIGN(0x099e) +#define ANC15_CANVAS_ADDR REG_ALIGN(0x099f) +#define ANC16_CANVAS_ADDR REG_ALIGN(0x09a0) +#define ANC17_CANVAS_ADDR REG_ALIGN(0x09a1) +#define ANC18_CANVAS_ADDR REG_ALIGN(0x09a2) +#define ANC19_CANVAS_ADDR REG_ALIGN(0x09a3) +#define ANC20_CANVAS_ADDR REG_ALIGN(0x09a4) +#define ANC21_CANVAS_ADDR REG_ALIGN(0x09a5) +#define ANC22_CANVAS_ADDR REG_ALIGN(0x09a6) +#define ANC23_CANVAS_ADDR REG_ALIGN(0x09a7) +#define ANC24_CANVAS_ADDR REG_ALIGN(0x09a8) +#define ANC25_CANVAS_ADDR REG_ALIGN(0x09a9) +#define ANC26_CANVAS_ADDR REG_ALIGN(0x09aa) +#define ANC27_CANVAS_ADDR REG_ALIGN(0x09ab) +#define ANC28_CANVAS_ADDR REG_ALIGN(0x09ac) +#define ANC29_CANVAS_ADDR REG_ALIGN(0x09ad) +#define ANC30_CANVAS_ADDR REG_ALIGN(0x09ae) +#define ANC31_CANVAS_ADDR REG_ALIGN(0x09af) +#define DBKR_CANVAS_ADDR REG_ALIGN(0x09b0) +#define DBKW_CANVAS_ADDR REG_ALIGN(0x09b1) +#define REC_CANVAS_ADDR REG_ALIGN(0x09b2) +#define CURR_CANVAS_CTRL REG_ALIGN(0x09b3) +#define MDEC_PIC_DC_THRESH REG_ALIGN(0x09b8) +#define AV_SCRATCH_0 REG_ALIGN(0x09c0) +#define AV_SCRATCH_1 REG_ALIGN(0x09c1) +#define AV_SCRATCH_2 REG_ALIGN(0x09c2) +#define AV_SCRATCH_3 REG_ALIGN(0x09c3) +#define AV_SCRATCH_4 REG_ALIGN(0x09c4) +#define AV_SCRATCH_5 REG_ALIGN(0x09c5) +#define AV_SCRATCH_6 REG_ALIGN(0x09c6) +#define AV_SCRATCH_7 REG_ALIGN(0x09c7) +#define AV_SCRATCH_8 REG_ALIGN(0x09c8) +#define AV_SCRATCH_9 REG_ALIGN(0x09c9) +#define AV_SCRATCH_A REG_ALIGN(0x09ca) +#define AV_SCRATCH_B REG_ALIGN(0x09cb) +#define AV_SCRATCH_C REG_ALIGN(0x09cc) +#define AV_SCRATCH_D REG_ALIGN(0x09cd) +#define AV_SCRATCH_E REG_ALIGN(0x09ce) +#define AV_SCRATCH_F REG_ALIGN(0x09cf) +#define AV_SCRATCH_G REG_ALIGN(0x09d0) +#define AV_SCRATCH_H REG_ALIGN(0x09d1) +#define AV_SCRATCH_I REG_ALIGN(0x09d2) +#define AV_SCRATCH_J REG_ALIGN(0x09d3) +#define AV_SCRATCH_K REG_ALIGN(0x09d4) +#define AV_SCRATCH_L REG_ALIGN(0x09d5) +#define AV_SCRATCH_M REG_ALIGN(0x09d6) +#define AV_SCRATCH_N REG_ALIGN(0x09d7) +#define WRRSP_VLD REG_ALIGN(0x09da) +#define MDEC_DOUBLEW_CFG0 REG_ALIGN(0x09db) +#define MDEC_DOUBLEW_CFG1 REG_ALIGN(0x09dc) +#define MDEC_DOUBLEW_CFG2 REG_ALIGN(0x09dd) +#define MDEC_DOUBLEW_CFG3 REG_ALIGN(0x09de) +#define MDEC_DOUBLEW_CFG4 REG_ALIGN(0x09df) +#define MDEC_DOUBLEW_CFG5 REG_ALIGN(0x09e0) +#define MDEC_DOUBLEW_CFG6 REG_ALIGN(0x09e1) +#define MDEC_DOUBLEW_CFG7 REG_ALIGN(0x09e2) +#define MDEC_DOUBLEW_STATUS REG_ALIGN(0x09e3) +#define MDEC_EXTIF_CFG0 REG_ALIGN(0x09e4) + +#define MDEC_EXTIF_CFG1 REG_ALIGN(0x09e5) +#define MDEC_EXTIF_CFG2 REG_ALIGN(0x09e6) + +#define POWER_CTL_VLD REG_ALIGN(0x0c08) +#define VLD_DECODE_CONTROL REG_ALIGN(0x0c18) + +#define PMV1_X REG_ALIGN(0x0c20) +#define PMV1_Y REG_ALIGN(0x0c21) +#define PMV2_X REG_ALIGN(0x0c22) +#define PMV2_Y REG_ALIGN(0x0c23) +#define PMV3_X REG_ALIGN(0x0c24) +#define PMV3_Y REG_ALIGN(0x0c25) +#define PMV4_X REG_ALIGN(0x0c26) +#define PMV4_Y REG_ALIGN(0x0c27) +#define M4_TABLE_SELECT REG_ALIGN(0x0c28) +#define M4_CONTROL_REG REG_ALIGN(0x0c29) +#define BLOCK_NUM REG_ALIGN(0x0c2a) +#define PATTERN_CODE REG_ALIGN(0x0c2b) +#define MB_INFO REG_ALIGN(0x0c2c) +#define VLD_DC_PRED REG_ALIGN(0x0c2d) +#define VLD_ERROR_MASK REG_ALIGN(0x0c2e) +#define VLD_DC_PRED_C REG_ALIGN(0x0c2f) +#define LAST_SLICE_MV_ADDR REG_ALIGN(0x0c30) +#define LAST_MVX REG_ALIGN(0x0c31) +#define LAST_MVY REG_ALIGN(0x0c32) + +#define MB_MOTION_MODE REG_ALIGN(0x0c07) +#define VIFF_BIT_CNT REG_ALIGN(0x0c1a) +#define M4_CONTROL_REG REG_ALIGN(0x0c29) +#define VLD_C38 REG_ALIGN(0x0c38) +#define VLD_C39 REG_ALIGN(0x0c39) +#define VLD_SHIFT_STATUS REG_ALIGN(0x0c3b) +#define VLD_C3D REG_ALIGN(0x0c3d) +#define VLD_MEM_VIFIFO_START_PTR REG_ALIGN(0x0c40) +#define VLD_MEM_VIFIFO_CURR_PTR REG_ALIGN(0x0c41) +#define VLD_MEM_VIFIFO_END_PTR REG_ALIGN(0x0c42) +#define VLD_MEM_VIFIFO_BYTES_AVAIL REG_ALIGN(0x0c43) +#define VLD_MEM_VIFIFO_CONTROL REG_ALIGN(0x0c44) +#define VLD_MEM_VIFIFO_WP REG_ALIGN(0x0c45) +#define VLD_MEM_VIFIFO_RP REG_ALIGN(0x0c46) +#define VLD_MEM_VIFIFO_LEVEL REG_ALIGN(0x0c47) +#define VLD_MEM_VIFIFO_BUF_CNTL REG_ALIGN(0x0c48) + +#define VCOP_CTRL_REG REG_ALIGN(0x0e00) +#define RV_AI_MB_COUNT REG_ALIGN(0x0e0c) +#define IQIDCT_CONTROL REG_ALIGN(0x0e0e) +#define DCAC_DDR_BYTE64_CTL REG_ALIGN(0x0e1d) + +#define VDEC2_IMEM_DMA_CTRL REG_ALIGN(0x2340) +#define VDEC2_IMEM_DMA_ADR REG_ALIGN(0x2341) +#define VDEC2_IMEM_DMA_COUNT REG_ALIGN(0x2342) + +#define DOS_SW_RESET0 REG_ALIGN(0x3f00) +#define DOS_GCLK_EN0 REG_ALIGN(0x3f01) +#define DOS_GCLK_EN1 REG_ALIGN(0x3f09) +#define DOS_GCLK_EN3 REG_ALIGN(0x3f35) + +#define DOS_MEM_PD_VDEC REG_ALIGN(0x3f30) +#define DOS_MEM_PD_VDEC2 REG_ALIGN(0x3f31) +#define DOS_MEM_PD_HCODEC REG_ALIGN(0x3f32) +/*add from M8M2*/ +#define DOS_MEM_PD_HEVC REG_ALIGN(0x3f33) + +#define DOS_SW_RESET3 REG_ALIGN(0x3f34) +#define DOS_GCLK_EN3 REG_ALIGN(0x3f35) +#define DOS_HEVC_INT_EN REG_ALIGN(0x3f36) + +#endif + -- 2.42.0 From bartosz.golaszewski at oss.qualcomm.com Tue May 26 01:03:05 2026 From: bartosz.golaszewski at oss.qualcomm.com (Bartosz Golaszewski) Date: Tue, 26 May 2026 10:03:05 +0200 Subject: [PATCH] dt-bindings: gpio: meson-axg: Fix whitespace issue In-Reply-To: <20260524154954.385778-1-jerrysteve1101@gmail.com> References: <20260524154954.385778-1-jerrysteve1101@gmail.com> Message-ID: <177978258287.4574.1235390656102820525.b4-ty@oss.qualcomm.com> On Sun, 24 May 2026 23:49:53 +0800, Jun Yan wrote: > Clean up whitespace misalignment in meson-axg-gpio.h > > Applied, thanks! [1/1] dt-bindings: gpio: meson-axg: Fix whitespace issue https://git.kernel.org/brgl/c/820017813b818a9b6411e481fcc98f5260b6e6c1 Best regards, -- Bartosz Golaszewski From devnull+zhentao.guo.amlogic.com at kernel.org Tue May 26 01:40:21 2026 From: devnull+zhentao.guo.amlogic.com at kernel.org (Zhentao Guo via B4 Relay) Date: Tue, 26 May 2026 16:40:21 +0800 Subject: [PATCH RFC RESEND v5 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> Message-ID: <20260526-b4-s4-vdec-upstream-v5-5-33bc817f93f4@amlogic.com> From: Zhentao Guo Add vdec node to enable Amlogic V4L2 stateless video decoder support. Signed-off-by: Zhentao Guo --- arch/arm64/boot/dts/amlogic/meson-s4.dtsi | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi index 2a6fbd530836..5ad826e4b554 100644 --- a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi +++ b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi @@ -96,6 +96,11 @@ saradc: adc at fe026000 { status = "disabled"; }; + optee { + compatible = "linaro,optee-tz"; + method = "smc"; + }; + soc { compatible = "simple-bus"; #address-cells = <2>; @@ -907,5 +912,34 @@ emmc: mmc at fe08c000 { assigned-clocks = <&clkc_periphs CLKID_SD_EMMC_C>; assigned-clock-rates = <24000000>; }; + + canvas: video-lut at fe036048 { + compatible = "amlogic,canvas"; + reg = <0x0 0xfe036048 0x0 0x14>; + }; + + video-codec at fe320000 { + compatible = "amlogic,s4-vdec"; + reg = <0x0 0xfe320000 0x0 0x10000>, + <0x0 0xfe036000 0x0 0x20>; + amlogic,canvas = <&canvas>; + reg-names = "dos", + "dmc"; + interrupts = , + , + ; + clocks = <&clkc_periphs CLKID_DOS>, + <&clkc_periphs CLKID_VDEC_SEL>, + <&clkc_periphs CLKID_HEVCF_SEL>; + clock-names = "dos", + "vdec", + "hevcf"; + power-domains = <&pwrc PWRC_S4_DOS_VDEC_ID>, + <&pwrc PWRC_S4_DOS_HEVC_ID>; + power-domain-names = "vdec", + "hevc"; + resets = <&reset RESET_DOS>; + secure-monitor = <&sm>; + }; }; }; -- 2.42.0 From devnull+zhentao.guo.amlogic.com at kernel.org Tue May 26 01:40:16 2026 From: devnull+zhentao.guo.amlogic.com at kernel.org (Zhentao Guo via B4 Relay) Date: Tue, 26 May 2026 16:40:16 +0800 Subject: [PATCH RFC RESEND v5 0/6] Add Amlogic stateless H.264 video decoder for S4 Message-ID: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> Introduce initial driver support for Amlogic's new video acceleration hardware architecture, designed for video stream decoding. Compared to the current Amlogic video decoder hardware architecture, this new implementation eliminates the Esparser hardware component, enabling direct vb2 buffer input. The driver is designed to support the V4L2 M2M stateless decoder API. The initial phase includes support for H.264 decoding on Amlogic S805X2 platform. The driver needs to work alongside with a signed firmware. The loading process of the signed fw is as follow. Stage1: Decypt and decompose the full firmware package when the driver is probed. +---------------------+ +---------------------+ | Decoder Driver | | TEE Shared Memory | | (Kernel Space) | | | | +---------------+ | | +---------------+ | | | video_ucode | | | | firmware | | | | .bin | | Copy payload to SHM | | payload | | | | (from fs) | | ---------------------> | | (Secure RAM) | | | +---------------+ | | +---------------+ | +---------------------+ +----------+----------+ | | PTA Invocation v +-------------------------------+ | BL32 | | +-------------------------+ | | | Decrypt Firmware | | | +-----------+-------------+ | | | | | v | | +-------------------------+ | | | Decompose the full | | | | firmware pacakge | | | +-----------+-------------+ | | | | | v | | +-------------------------+ | | | Store decomposed .bin | | | | in Secure Memory | | | +-------------------------+ | +-------------------------------+ Stage2: When a decode job is scheduled, load decrypted fw via secure monitor. +---------------------+ | V4L2 M2M Framework | | +---------------+ | | | device_run | | | +------+--------+ | +---------+-----------+ | v +---------------------+ +---------------------+ | Decoder Driver | | Secure Monitor | | (Kernel Space) | | (bl32) | | +---------------+ | SMC Call | +---------------+ | | | Select Codec | | ---------------> | | Select & Load | | | | Specific FW | | | | firmware.bin | | | +---------------+ | | | to AMRISC | | +---------------------+ | +-------+-------+ | +----------+----------+ | v +---------------------+ | AMRISC Core | | +---------------+ | | | Running fw on | | | | AMRISC | | | +---------------+ | +---------------------+ The driver is capable of: - Supporting stateless H.264 decoding up to a resolution 1920x1088(on the S805X2 platform). - Supporting I/P/B frame handling. - Supporting vb2 mmap and dma-buf modes. - Supporting frame-based decode mode. (Note that some H.264 bitstreams require DPB reordering to generate reference lists, the stateless decoder driver cannot access reordered reference lists in this mode, requiring the driver to perform reference list reordering itself) - Supporting NV12/NV21 output. - Supporting Annex B start codes. This driver is tested with Gstreamer. Example: gst-launch-1.0 filesrc location=/tmp/video_640x360_mp4_hevc_450kbps_no_b.mp4 ! parsebin ! v4l2slh264dec ! filesink location=/tmp/output.yuv Retry the compliance test based on kernel 7.1.0: v4l2-compliance 1.30.1, 64 bits, 64-bit time_t Compliance test for aml-vdec-drv device /dev/video0: Driver Info: Driver name : aml-vdec-drv Card type : platform:aml-vdec-drv Bus info : platform:fe320000.video-codec Driver version : 7.1.0 Capabilities : 0x84204000 Video Memory-to-Memory Multiplanar Streaming Extended Pix Format Device Capabilities Device Caps : 0x04204000 Video Memory-to-Memory Multiplanar Streaming Extended Pix Format Detected Stateless Decoder Media Driver Info: Driver name : aml-vdec-drv Model : aml-vdec-drv Serial : Bus info : platform:fe320000.video-codec Media version : 7.1.0 Hardware revision: 0x00000000 (0) Driver version : 7.1.0 Interface Info: ID : 0x0300000c Type : V4L Video Entity Info: ID : 0x00000001 (1) Name : aml_dev_drv-source Function : V4L2 I/O Pad 0x01000002 : 0: Source Link 0x02000008: to remote pad 0x1000004 of entity 'aml_dev_drv-proc' (Video Decoder): Data, Enabled, Immutable Required ioctls: test MC information (see 'Media Driver Info' above): OK test VIDIOC_QUERYCAP: OK test invalid ioctls: OK Allow for multiple opens: test second /dev/video0 open: OK test VIDIOC_QUERYCAP: OK test VIDIOC_G/S_PRIORITY: OK test for unlimited opens: OK Debug ioctls: test VIDIOC_DBG_G/S_REGISTER: OK (Not Supported) test VIDIOC_LOG_STATUS: OK (Not Supported) Input ioctls: test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported) test VIDIOC_G/S_FREQUENCY: OK (Not Supported) test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported) test VIDIOC_ENUMAUDIO: OK (Not Supported) test VIDIOC_G/S/ENUMINPUT: OK (Not Supported) test VIDIOC_G/S_AUDIO: OK (Not Supported) Inputs: 0 Audio Inputs: 0 Tuners: 0 Output ioctls: test VIDIOC_G/S_MODULATOR: OK (Not Supported) test VIDIOC_G/S_FREQUENCY: OK (Not Supported) test VIDIOC_ENUMAUDOUT: OK (Not Supported) test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported) test VIDIOC_G/S_AUDOUT: OK (Not Supported) Outputs: 0 Audio Outputs: 0 Modulators: 0 Input/Output configuration ioctls: test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported) test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported) test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported) test VIDIOC_G/S_EDID: OK (Not Supported) Control ioctls: test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK test VIDIOC_QUERYCTRL: OK test VIDIOC_G/S_CTRL: OK test VIDIOC_G/S/TRY_EXT_CTRLS: OK test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK test VIDIOC_G/S_JPEGCOMP: OK (Not Supported) Standard Controls: 6 Private Controls: 0 Standard Compound Controls: 4 Private Compound Controls: 0 Format ioctls: test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK test VIDIOC_G/S_PARM: OK (Not Supported) test VIDIOC_G_FBUF: OK (Not Supported) test VIDIOC_G_FMT: OK test VIDIOC_TRY_FMT: OK test VIDIOC_S_FMT: OK test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported) test Cropping: OK (Not Supported) test Composing: OK (Not Supported) test Scaling: OK (Not Supported) Codec ioctls: test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported) test VIDIOC_G_ENC_INDEX: OK (Not Supported) test VIDIOC_(TRY_)DECODER_CMD: OK Buffer ioctls: test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK test CREATE_BUFS maximum buffers: OK test VIDIOC_REMOVE_BUFS: OK test VIDIOC_EXPBUF: OK test Requests: OK test blocking wait: OK Total for aml-vdec-drv device /dev/video0: 49, Succeeded: 49, Failed: 0, Warnings: 0 Fluster test result of JVT-AVC_V1. Result: Ran 77/135 tests successfully - 52 test vectors failed due to interlaced or mbaff clips: The Amlogic stateless decoder driver only support bitstreams with frame_mbs_only_flags == 1. Test Vectors: cabac_mot_fld0_full cabac_mot_mbaff0_full cabac_mot_picaff0_full CABREF3_Sand_D CAFI1_SVA_C CAMA1_Sony_C CAMA1_TOSHIBA_B cama1_vtc_c cama2_vtc_b CAMA3_Sand_E cama3_vtc_b CAMACI3_Sony_C CAMANL1_TOSHIBA_B CAMANL2_TOSHIBA_B CAMANL3_Sand_E CAMASL3_Sony_B CAMP_MOT_MBAFF_L30 CAMP_MOT_MBAFF_L31 CANLMA2_Sony_C CANLMA3_Sony_C CAPA1_TOSHIBA_B CAPAMA3_Sand_F cavlc_mot_fld0_full_B cavlc_mot_mbaff0_full_B cavlc_mot_picaff0_full_B CVCANLMA2_Sony_C CVFI1_Sony_D CVFI1_SVA_C CVFI2_Sony_H CVFI2_SVA_C CVMA1_Sony_D CVMA1_TOSHIBA_B CVMANL1_TOSHIBA_B CVMANL2_TOSHIBA_B CVMAPAQP3_Sony_E CVMAQP2_Sony_G CVMAQP3_Sony_D CVMP_MOT_FLD_L30_B CVNLFI1_Sony_C CVNLFI2_Sony_H CVPA1_TOSHIBA_B FI1_Sony_E MR6_BT_B MR7_BT_B MR8_BT_B MR9_BT_B Sharp_MP_Field_1_B Sharp_MP_Field_2_B Sharp_MP_Field_3_B Sharp_MP_PAFF_1r2 Sharp_MP_PAFF_2r CVMP_MOT_FRM_L31_B - 3 test vectors failed due to unsupported bitstream. num_slice_group_minus1 greater than zero is not supported by the hardware. Test Vectors: FM1_BT_B FM1_FT_E FM2_SVA_C - 2 test vectors failed because SP_SLICE type is not supported by the hardware. Test Vectors: SP1_BT_A sp2_bt_b One remain failure is CVFC1_Sony_C, which contains crop information. The md5sum of every decoded YUV indicates that original output from the decoder was correct. The YUV was cropped by gstreamer. The correct cropping method for this bitstream should be to crop 30*2 rows of pixels from both the top and bottom of the image, and 13*2 columns of pixels from both the left and right sides.However, gstreamer cropped 13*4 columns of pixels from the right side and 30*4 rows of pixels from the bottom. We are trying to find out the cause of this. Other failuers mentioned in V1 and V2 were resolved. Changes in v5: - Rename the compatible and the clock item accroding to Krzysztof's feedback. - Use tee & meson_sm helpers to decrypt load the signed decoder firmware. Add the meson_sm describsion and reference to dt-binding and dts. - Link to v4: https://lore.kernel.org/r/20260213-b4-s4-vdec-upstream-v4-0-c7112d00d662 at amlogic.com Changes in v4: - Use %pad to print dma_addr_t type instead of using %llx. - Add initial values to some local variables. - Link to v3: https://lore.kernel.org/r/20260121-b4-s4-vdec-upstream-v3-0-4496aec3d79e at amlogic.com Changes in v3: - Fixed the DT check error: arch/arm64/boot/dts/amlogic/meson-s4-s805x2-aq222.dtb: video-codec at fe320000 (amlogic,s4-vcodec-dec): 'amlogic,canvas' does not match any of the regexes: '^pinctrl-[0-9]+$' from schema $id: http://devicetree.org/schemas/media/amlogic,vcodec-dec.yaml - Added DOS reset lines to dtsi and dt-binding. - Fixed the issue where some B-frames were not decoded correctly(The fluster failures mentioned in patch V1 and V2 were mostly caused by this). - Fixed the issue where canvas_index leaks occurred during the decoding of some bitstreams. - Rework the src/dst format storage. Use v4l2_pix_format_mplane to store formats that related to bitstreams into the context. Add the reset format function to reset all the formats to default value. - Store decoding parameters related to chip platforms, such as maximum width/height and alignment requirement, organized by chip platform. - Link to v2: https://lore.kernel.org/r/20251124-b4-s4-vdec-upstream-v2-0-bdbbce3f11a6 at amlogic.com Changes in v2: - Fixed incorrect generation of the reference lists for some B-frames. - Rename or get rid of some properties in DTS and dt-binding. - Remove some useless code or helper functions, (eg. clk helper functions, reg I/O macros, and some superfluous print messages) replace these functions with existing ones. - Replace all the printk messages with dev_err/dev_info/dev_dbg - Use the helper functions from the existing meson-canvas driver. - Use clk_bulk_data to map clocks from DTS. - Retry the V4L2 Compliance test on 6.18-rc6, fix a newly introduced bug. - Link to v1: https://lore.kernel.org/r/20251027-b4-s4-vdec-upstream-v1-0-620401813b5d at amlogic.com Signed-off-by: Zhentao Guo --- Zhentao Guo (6): firmware: meson: sm: Add video firmware loading SMC call firmware: meson: sm: video firmware loading via secure monitor media: dt-bindings: Add Amlogic V4L2 video decoder decoder: Add V4L2 stateless H.264 decoder driver arm64: dts: amlogic: Add video decoder driver support for S4 SOCs arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC .../devicetree/bindings/media/amlogic,s4-vdec.yaml | 103 + MAINTAINERS | 7 + arch/arm64/boot/dts/amlogic/meson-s4.dtsi | 34 + arch/arm64/configs/defconfig | 1 + drivers/firmware/meson/meson_sm.c | 1 + drivers/media/platform/amlogic/Kconfig | 1 + drivers/media/platform/amlogic/Makefile | 1 + drivers/media/platform/amlogic/vdec/Kconfig | 18 + drivers/media/platform/amlogic/vdec/Makefile | 4 + drivers/media/platform/amlogic/vdec/TODO | 7 + drivers/media/platform/amlogic/vdec/aml_vdec.c | 736 +++++++ drivers/media/platform/amlogic/vdec/aml_vdec.h | 33 + drivers/media/platform/amlogic/vdec/aml_vdec_drv.c | 239 +++ drivers/media/platform/amlogic/vdec/aml_vdec_drv.h | 172 ++ drivers/media/platform/amlogic/vdec/aml_vdec_hw.c | 538 +++++ drivers/media/platform/amlogic/vdec/aml_vdec_hw.h | 159 ++ .../platform/amlogic/vdec/aml_vdec_platform.c | 81 + .../platform/amlogic/vdec/aml_vdec_platform.h | 46 + .../media/platform/amlogic/vdec/aml_vdec_tee_fw.c | 240 +++ .../media/platform/amlogic/vdec/aml_vdec_tee_fw.h | 27 + drivers/media/platform/amlogic/vdec/h264.c | 2128 ++++++++++++++++++++ drivers/media/platform/amlogic/vdec/h264.h | 299 +++ drivers/media/platform/amlogic/vdec/reg_defines.h | 177 ++ include/linux/firmware/meson/meson_sm.h | 1 + 24 files changed, 5053 insertions(+) --- base-commit: d387b06f7c15b4639244ad66b4b0900c6a02b430 change-id: 20251027-b4-s4-vdec-upstream-0603c1a4c84a Best regards, -- Zhentao Guo From devnull+zhentao.guo.amlogic.com at kernel.org Tue May 26 01:40:17 2026 From: devnull+zhentao.guo.amlogic.com at kernel.org (Zhentao Guo via B4 Relay) Date: Tue, 26 May 2026 16:40:17 +0800 Subject: [PATCH RFC RESEND v5 1/6] firmware: meson: sm: Add video firmware loading SMC call In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> Message-ID: <20260526-b4-s4-vdec-upstream-v5-1-33bc817f93f4@amlogic.com> From: Zhentao Guo Add SM_LOAD_VIDEO_FW at SMC ID 0xb200000f in the command table to load video firmware. Signed-off-by: Zhentao Guo --- drivers/firmware/meson/meson_sm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c index 3ab67aaa9e5d..5da6c65d684a 100644 --- a/drivers/firmware/meson/meson_sm.c +++ b/drivers/firmware/meson/meson_sm.c @@ -47,6 +47,7 @@ static const struct meson_sm_chip gxbb_chip = { CMD(SM_GET_CHIP_ID, 0x82000044), CMD(SM_A1_PWRC_SET, 0x82000093), CMD(SM_A1_PWRC_GET, 0x82000095), + CMD(SM_LOAD_VIDEO_FW, 0xb200000f), { /* sentinel */ }, }, }; -- 2.42.0 From devnull+zhentao.guo.amlogic.com at kernel.org Tue May 26 01:40:22 2026 From: devnull+zhentao.guo.amlogic.com at kernel.org (Zhentao Guo via B4 Relay) Date: Tue, 26 May 2026 16:40:22 +0800 Subject: [PATCH RFC RESEND v5 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> Message-ID: <20260526-b4-s4-vdec-upstream-v5-6-33bc817f93f4@amlogic.com> From: Zhentao Guo Enable the Amlogic V4L2 stateless video decoder driver as a module in the arm64 defconfig. This driver is needed for stateless video decoding support on Amlogic SoCs. Signed-off-by: Zhentao Guo --- arch/arm64/configs/defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 4567f4b34f29..14caac24d200 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -913,6 +913,7 @@ CONFIG_V4L_PLATFORM_DRIVERS=y CONFIG_SDR_PLATFORM_DRIVERS=y CONFIG_V4L_MEM2MEM_DRIVERS=y CONFIG_VIDEO_AMPHION_VPU=m +CONFIG_VIDEO_AMLOGIC_VDEC=m CONFIG_VIDEO_CADENCE_CSI2RX=m CONFIG_VIDEO_WAVE_VPU=m CONFIG_VIDEO_E5010_JPEG_ENC=m -- 2.42.0 From devnull+zhentao.guo.amlogic.com at kernel.org Tue May 26 01:40:18 2026 From: devnull+zhentao.guo.amlogic.com at kernel.org (Zhentao Guo via B4 Relay) Date: Tue, 26 May 2026 16:40:18 +0800 Subject: [PATCH RFC RESEND v5 2/6] firmware: meson: sm: video firmware loading via secure monitor In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> Message-ID: <20260526-b4-s4-vdec-upstream-v5-2-33bc817f93f4@amlogic.com> From: Zhentao Guo Add SM_LOAD_VIDEO_FW to the secure monitor command enum to allow decoder drivers to load firmware through the meson_sm interface. Signed-off-by: Zhentao Guo --- include/linux/firmware/meson/meson_sm.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/firmware/meson/meson_sm.h index 8eaf8922ab02..f40867a000f1 100644 --- a/include/linux/firmware/meson/meson_sm.h +++ b/include/linux/firmware/meson/meson_sm.h @@ -14,6 +14,7 @@ enum { SM_GET_CHIP_ID, SM_A1_PWRC_SET, SM_A1_PWRC_GET, + SM_LOAD_VIDEO_FW, }; struct meson_sm_firmware; -- 2.42.0 From devnull+zhentao.guo.amlogic.com at kernel.org Tue May 26 01:40:19 2026 From: devnull+zhentao.guo.amlogic.com at kernel.org (Zhentao Guo via B4 Relay) Date: Tue, 26 May 2026 16:40:19 +0800 Subject: [PATCH RFC RESEND v5 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> Message-ID: <20260526-b4-s4-vdec-upstream-v5-3-33bc817f93f4@amlogic.com> From: Zhentao Guo Describe the initial support for the V4L2 stateless video decoder driver used with the Amlogic S4 (S805X2) platform. Signed-off-by: Zhentao Guo --- .../devicetree/bindings/media/amlogic,s4-vdec.yaml | 103 +++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml b/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml new file mode 100644 index 000000000000..a0f33f6c35a1 --- /dev/null +++ b/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2025 Amlogic, Inc. All rights reserved +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/amlogic,s4-vdec.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Amlogic Video Decode Accelerator + +maintainers: + - Zhentao Guo + +description: + The Video Decoder Accelerator present on Amlogic SOCs. + It supports stateless h264 decoding. + +properties: + compatible: + const: amlogic,s4-vdec + + reg: + maxItems: 2 + + reg-names: + items: + - const: dos + - const: dmc + + interrupts: + maxItems: 3 + + clocks: + maxItems: 3 + + clock-names: + items: + - const: dos + - const: vdec + - const: hevcf + + power-domains: + maxItems: 2 + + power-domain-names: + items: + - const: vdec + - const: hevc + + resets: + maxItems: 1 + + amlogic,canvas: + description: should point to a canvas provider node + $ref: /schemas/types.yaml#/definitions/phandle + + secure-monitor: + description: phandle to the secure-monitor node + $ref: /schemas/types.yaml#/definitions/phandle + +required: + - compatible + - reg + - reg-names + - interrupts + - clocks + - clock-names + - power-domains + - power-domain-names + - amlogic,canvas + - secure-monitor + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + video-codec at fe320000 { + compatible = "amlogic,s4-vdec"; + reg = <0xfe320000 0x10000>, + <0xfe036000 0x20>; + amlogic,canvas = <&canvas>; + reg-names = "dos", + "dmc"; + interrupts = , + , + ; + clocks = <&clkc_periphs CLKID_DOS>, + <&clkc_periphs CLKID_VDEC_SEL>, + <&clkc_periphs CLKID_HEVCF_SEL>; + clock-names = "dos", + "vdec", + "hevcf"; + power-domains = <&pwrc PWRC_S4_DOS_VDEC_ID>, + <&pwrc PWRC_S4_DOS_HEVC_ID>; + power-domain-names = "vdec", + "hevc"; + resets = <&reset RESET_DOS>; + secure-monitor = <&sm>; + }; -- 2.42.0 From devnull+zhentao.guo.amlogic.com at kernel.org Tue May 26 01:40:20 2026 From: devnull+zhentao.guo.amlogic.com at kernel.org (Zhentao Guo via B4 Relay) Date: Tue, 26 May 2026 16:40:20 +0800 Subject: [PATCH RFC RESEND v5 4/6] decoder: Add V4L2 stateless H.264 decoder driver In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4@amlogic.com> Message-ID: <20260526-b4-s4-vdec-upstream-v5-4-33bc817f93f4@amlogic.com> From: Zhentao Guo Add initial support for V4L2 stateless video decoder driver on Amlogic S4(S805X2) platform. In phase 1, it supports 8bit H.264 bitstreams decoding. Currently only progressive streams are supported. Signed-off-by: Zhentao Guo --- MAINTAINERS | 7 + drivers/media/platform/amlogic/Kconfig | 1 + drivers/media/platform/amlogic/Makefile | 1 + drivers/media/platform/amlogic/vdec/Kconfig | 18 + drivers/media/platform/amlogic/vdec/Makefile | 4 + drivers/media/platform/amlogic/vdec/TODO | 7 + drivers/media/platform/amlogic/vdec/aml_vdec.c | 736 +++++++ drivers/media/platform/amlogic/vdec/aml_vdec.h | 33 + drivers/media/platform/amlogic/vdec/aml_vdec_drv.c | 239 +++ drivers/media/platform/amlogic/vdec/aml_vdec_drv.h | 172 ++ drivers/media/platform/amlogic/vdec/aml_vdec_hw.c | 538 +++++ drivers/media/platform/amlogic/vdec/aml_vdec_hw.h | 159 ++ .../platform/amlogic/vdec/aml_vdec_platform.c | 81 + .../platform/amlogic/vdec/aml_vdec_platform.h | 46 + .../media/platform/amlogic/vdec/aml_vdec_tee_fw.c | 240 +++ .../media/platform/amlogic/vdec/aml_vdec_tee_fw.h | 27 + drivers/media/platform/amlogic/vdec/h264.c | 2128 ++++++++++++++++++++ drivers/media/platform/amlogic/vdec/h264.h | 299 +++ drivers/media/platform/amlogic/vdec/reg_defines.h | 177 ++ 19 files changed, 4913 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index fe28ba288999..8d35821ff4c3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1406,6 +1406,13 @@ S: Maintained F: Documentation/devicetree/bindings/spi/amlogic,a4-spisg.yaml F: drivers/spi/spi-amlogic-spisg.c +AMLOGIC VDEC DRIVER +M: Zhentao Guo +L: linux-media at vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/amlogic,s4-vcodec-dec.yaml +F: drivers/media/platform/amlogic/vdec/ + AMPHENOL CHIPCAP 2 DRIVER M: Javier Carrasco L: linux-hwmon at vger.kernel.org diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig index 458acf3d5fa8..7c541ac0d0c3 100644 --- a/drivers/media/platform/amlogic/Kconfig +++ b/drivers/media/platform/amlogic/Kconfig @@ -4,3 +4,4 @@ comment "Amlogic media platform drivers" source "drivers/media/platform/amlogic/c3/Kconfig" source "drivers/media/platform/amlogic/meson-ge2d/Kconfig" +source "drivers/media/platform/amlogic/vdec/Kconfig" diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile index c744afcd1b9e..7409de674c0b 100644 --- a/drivers/media/platform/amlogic/Makefile +++ b/drivers/media/platform/amlogic/Makefile @@ -2,3 +2,4 @@ obj-y += c3/ obj-y += meson-ge2d/ +obj-y += vdec/ diff --git a/drivers/media/platform/amlogic/vdec/Kconfig b/drivers/media/platform/amlogic/vdec/Kconfig new file mode 100644 index 000000000000..d392967c7743 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR MIT) + +config VIDEO_AMLOGIC_VDEC + tristate "Amlogic Video Decoder Driver" + depends on ARCH_MESON || COMPILE_TEST + depends on VIDEO_DEV + depends on V4L_MEM2MEM_DRIVERS + depends on TEE + select VIDEOBUF2_DMA_CONTIG + select V4L2_H264 + select V4L2_MEM2MEM_DEV + select MESON_CANVAS + select MESON_SM + help + This is a v4l2 driver for Amlogic video decoder driver. + This driver is designed to support V4L2 M2M STATELESS + interface. + To compile this driver as a module choose m here. diff --git a/drivers/media/platform/amlogic/vdec/Makefile b/drivers/media/platform/amlogic/vdec/Makefile new file mode 100644 index 000000000000..f752716cbf9e --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +aml-vdec-drv-objs := aml_vdec.o aml_vdec_drv.o aml_vdec_hw.o aml_vdec_platform.o h264.o aml_vdec_tee_fw.o\ + +obj-$(CONFIG_VIDEO_AMLOGIC_VDEC) += aml-vdec-drv.o diff --git a/drivers/media/platform/amlogic/vdec/TODO b/drivers/media/platform/amlogic/vdec/TODO new file mode 100644 index 000000000000..54c60145770e --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/TODO @@ -0,0 +1,7 @@ +TODO list for Amlogic V4L2 stateless decoder driver: + +1. Support decoding for HEVC, VP9, AV1, and MPEG-2. +2. Support more SoCs, including the new T7/S7 series and legacy SoCs (e.g., GXL, SM1, G12B). +3. Support 10-bit decoding and P010 output. + Note: P010 output requires hardware support. +4. Support interlaced stream decoding for H.264, HEVC, and MPEG-2. diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec.c b/drivers/media/platform/amlogic/vdec/aml_vdec.c new file mode 100644 index 000000000000..d4dcd0180d2d --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec.c @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include +#include + +#include "aml_vdec.h" +#include "aml_vdec_hw.h" +#include "aml_vdec_platform.h" +#include "aml_vdec_tee_fw.h" + +#define VCODEC_DRV_NAME "aml-vdec-drv" + +static const struct aml_vdec_v4l2_ctrl controls[] = { + { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_STATELESS_H264_DECODE_PARAMS, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_STATELESS_H264_SPS, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_STATELESS_H264_PPS, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_STATELESS_H264_SCALING_MATRIX, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_STATELESS_H264_DECODE_MODE, + .min = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + .def = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + .max = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_STATELESS_H264_START_CODE, + .min = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + .def = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + .max = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + }, + }, { + .codec_type = CODEC_TYPE_H264, + .cfg = { + .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, + .min = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, + .max = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, + .def = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN, + }, + } +}; + +static const struct aml_dec_type dec_type_name[] = { + { + .codec_type = CODEC_TYPE_H264, + .name = "H264", + }, +}; + +static const char *dec_type_to_name(unsigned int type) +{ + int i; + int size = ARRAY_SIZE(dec_type_name); + + for (i = 0; i < size; i++) { + if (dec_type_name[i].codec_type == type) + return dec_type_name[i].name; + } + + return "ERR"; +} + +int aml_vdec_ctrls_setup(struct aml_vdec_ctx *ctx) +{ + int i; + int ctrls_size = ARRAY_SIZE(controls); + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, ctrls_size); + for (i = 0; i < ctrls_size; i++) { + v4l2_ctrl_new_custom(&ctx->ctrl_handler, &controls[i].cfg, NULL); + if (ctx->ctrl_handler.error) { + dev_info(&ctx->dev->plat_dev->dev, "add ctrl for (%d) failed%d\n", + controls[i].cfg.id, ctx->ctrl_handler.error); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return ctx->ctrl_handler.error; + } + } + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + return v4l2_ctrl_handler_setup(&ctx->ctrl_handler); +} + +static void m2mops_vdec_device_run(void *m2m_priv) +{ + struct aml_vdec_ctx *ctx = m2m_priv; + struct aml_vdec_dev *dev = ctx->dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct media_request *src_req; + int ret = 0; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + dev_dbg(&dev->plat_dev->dev, "device run : src buf : %d dst buf %d\n", + src_buf->vb2_buf.index, dst_buf->vb2_buf.index); + if (WARN_ON_ONCE(!ctx->codec_ops->run)) + goto err_cancel_job; + + src_req = src_buf->vb2_buf.req_obj.req; + if (src_req) + v4l2_ctrl_request_setup(src_req, &ctx->ctrl_handler); + dos_enable(dev->dec_hw); + /* incase of bus hang in stop_streaming */ + ctx->dos_clk_en = 1; + + if (ctx->curr_dec_type == CODEC_TYPE_H264) + aml_vdec_reset_core(dev->dec_hw); + + if (load_firmware(dev->dec_hw, ctx->curr_dec_type) < 0) + goto err_cancel_job; + + ret = ctx->codec_ops->run(ctx); + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf); + if (src_req) + v4l2_ctrl_request_complete(src_req, &ctx->ctrl_handler); + if (ret < 0) + goto err_cancel_job; + + return; + +err_cancel_job: + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx, + VB2_BUF_STATE_ERROR); +} + +const struct v4l2_m2m_ops aml_vdec_m2m_ops = { + .device_run = m2mops_vdec_device_run, +}; + +static int vidioc_vdec_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, VCODEC_DRV_NAME, sizeof(cap->driver)); + strscpy(cap->card, "platform:" VCODEC_DRV_NAME, sizeof(cap->card)); + + return 0; +} + +static int vidioc_vdec_enum_fmt(struct aml_vdec_ctx *ctx, + struct v4l2_fmtdesc *f, bool is_output) +{ + struct aml_vdec_dev *dev = ctx->dev; + const struct aml_video_fmt *fmt; + int i = 0, j = 0; + + for (; i < dev->pvdec_data->num_fmts; i++) { + fmt = &dev->pvdec_data->dec_fmt[i]; + if (is_output && fmt->type != AML_FMT_DEC) + continue; + if (!is_output && fmt->type != AML_FMT_FRAME) + continue; + + if (j == f->index) { + f->pixelformat = fmt->fourcc; + strscpy(f->description, fmt->name, + sizeof(f->description)); + return 0; + } + ++j; + } + return -EINVAL; +} + +static const struct aml_video_fmt *aml_vdec_get_video_fmt(struct aml_vdec_dev + *dev, u32 format) +{ + const struct aml_video_fmt *fmt; + unsigned int k; + + for (k = 0; k < dev->pvdec_data->num_fmts; k++) { + fmt = &dev->pvdec_data->dec_fmt[k]; + if (fmt->fourcc == format) + return fmt; + } + + return NULL; +} + +static int aml_vdec_init_dec_inst(struct aml_vdec_ctx *ctx) +{ + struct aml_vdec_dev *dev = ctx->dev; + struct aml_video_fmt *fmt_out = &ctx->dec_fmt[AML_FMT_SRC]; + int ret = -1; + + ctx->codec_ops = &dev->pvdec_data->codec_ops[fmt_out->codec_type]; + if (ctx->codec_ops->init) { + ret = ctx->codec_ops->init(ctx); + if (ret < 0) + return ret; + } + ctx->curr_dec_type = fmt_out->codec_type; + dev_info(&dev->plat_dev->dev, "%s set curr_dec_type %s\n", + __func__, dec_type_to_name(ctx->curr_dec_type)); + + return ret; +} + +static void set_pic_info(struct aml_vdec_ctx *ctx, + struct v4l2_pix_format_mplane *pix_mp, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) { + ctx->pic_info.output_pix_fmt = pix_mp->pixelformat; + ctx->pic_info.coded_width = ALIGN(pix_mp->width, 64); + ctx->pic_info.coded_height = ALIGN(pix_mp->height, 64); + ctx->pic_info.fb_size[0] = + ctx->pic_info.coded_width * ctx->pic_info.coded_height; + ctx->pic_info.fb_size[1] = ctx->pic_info.fb_size[0] / 2; + ctx->pic_info.plane_num = 1; + } +} + +static int vidioc_vdec_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + const struct aml_video_fmt *fmt; + struct aml_vdec_dev *dev = video_drvdata(file); + + if (fsize->index != 0) + return -EINVAL; + + fmt = aml_vdec_get_video_fmt(dev, fsize->pixel_format); + if (!fmt) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise = fmt->stepwise; + + return 0; +} + +static int vdec_try_fmt_mp(struct aml_vdec_ctx *ctx, enum v4l2_buf_type type, + struct v4l2_pix_format_mplane *pix_mp) +{ + struct aml_video_fmt *dec_fmt; + int i, align; + + if (V4L2_TYPE_IS_OUTPUT(type)) + dec_fmt = &ctx->dec_fmt[AML_FMT_SRC]; + else + dec_fmt = &ctx->dec_fmt[AML_FMT_DST]; + + pix_mp->field = V4L2_FIELD_NONE; + + if (V4L2_TYPE_IS_OUTPUT(type)) { + pix_mp->num_planes = dec_fmt->num_planes; + pix_mp->pixelformat = dec_fmt->fourcc; + if (!pix_mp->plane_fmt[0].sizeimage) + pix_mp->plane_fmt[0].sizeimage = + (pix_mp->height * pix_mp->width * 3) / 2; + } + + align = ctx->dev->pvdec_data->dec_fmt->align; + pix_mp->height = ALIGN(pix_mp->height, align); + pix_mp->width = ALIGN(pix_mp->width, align); + + v4l2_apply_frmsize_constraints(&pix_mp->width, &pix_mp->height, + &dec_fmt->stepwise); + dev_dbg(&ctx->dev->plat_dev->dev, + "%s type %d four_cc %d pix_mp->width %d pix_mp->height %d\n", + __func__, type, dec_fmt->fourcc, pix_mp->width, pix_mp->height); + + v4l2_fill_pixfmt_mp(pix_mp, dec_fmt->fourcc, pix_mp->width, + pix_mp->height); + + for (i = 0; i < pix_mp->num_planes; i++) + memset(&pix_mp->plane_fmt[i].reserved[0], 0x0, + sizeof(pix_mp->plane_fmt[0].reserved)); + + memset(pix_mp->reserved, 0x0, sizeof(pix_mp->reserved)); + pix_mp->flags = 0; + + dev_dbg(&ctx->dev->plat_dev->dev, + "%s type %d fmt %d num_plane %d sizeimage0 %d sizeimage1 %d\n", + __func__, type, pix_mp->pixelformat, pix_mp->num_planes, + pix_mp->plane_fmt[0].sizeimage, pix_mp->plane_fmt[1].sizeimage); + + return 0; +} + +static int vdec_s_fmt_output(struct aml_vdec_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + const struct aml_video_fmt *out_fmt; + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (vb2_is_busy(vq) && + pix_mp->pixelformat != ctx->pix_fmt[AML_FMT_SRC].pixelformat) + return -EBUSY; + + out_fmt = aml_vdec_get_video_fmt(ctx->dev, pix_mp->pixelformat); + if (out_fmt) + ctx->dec_fmt[AML_FMT_SRC] = *out_fmt; + else + dev_dbg(&ctx->dev->plat_dev->dev, + "%s fmt %d not supported, use default\n", __func__, + pix_mp->pixelformat); + + ret = vdec_try_fmt_mp(ctx, f->type, pix_mp); + set_pic_info(ctx, pix_mp, f->type); + + ctx->pix_fmt[AML_FMT_SRC] = *pix_mp; + ctx->pix_fmt[AML_FMT_DST] = *pix_mp; + + return ret; +} + +static int vdec_s_fmt_capture(struct aml_vdec_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + const struct aml_video_fmt *cap_fmt; + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (vb2_is_busy(vq)) + return -EBUSY; + + cap_fmt = aml_vdec_get_video_fmt(ctx->dev, pix_mp->pixelformat); + if (cap_fmt) + ctx->dec_fmt[AML_FMT_DST] = *cap_fmt; + else + dev_dbg(&ctx->dev->plat_dev->dev, + "%s fmt %d not supported, use default\n", __func__, + pix_mp->pixelformat); + + ret = vdec_try_fmt_mp(ctx, f->type, pix_mp); + + ctx->pix_fmt[AML_FMT_DST] = *pix_mp; + + return ret; +} + +static void reset_output_fmts(struct aml_vdec_ctx *ctx) +{ + struct aml_vdec_dev *dev = ctx->dev; + const struct aml_video_fmt *out_fmt; + struct v4l2_pix_format_mplane fmt; + + /* reset default output fmt to V4L2_PIX_FMT_H264_SLICE */ + out_fmt = aml_vdec_get_video_fmt(dev, V4L2_PIX_FMT_H264_SLICE); + if (!out_fmt) + return; + + ctx->dec_fmt[AML_FMT_SRC] = *out_fmt; + + memset(&fmt, 0, sizeof(struct v4l2_pix_format_mplane)); + + fmt.height = out_fmt->stepwise.min_height; + fmt.width = out_fmt->stepwise.min_width; + /* reset bytesperline to 0 for output fmts */ + fmt.plane_fmt[0].bytesperline = 0; + fmt.colorspace = V4L2_COLORSPACE_DEFAULT; + fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt.quantization = V4L2_QUANTIZATION_DEFAULT; + fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT; + vdec_try_fmt_mp(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, &fmt); + + ctx->pix_fmt[AML_FMT_SRC] = fmt; +} + +static void reset_capture_fmts(struct aml_vdec_ctx *ctx) +{ + struct aml_vdec_dev *dev = ctx->dev; + const struct aml_video_fmt *cap_fmt; + struct v4l2_pix_format_mplane fmt; + + /* reset default output fmt to V4L2_PIX_FMT_NV12 */ + cap_fmt = aml_vdec_get_video_fmt(dev, V4L2_PIX_FMT_NV12); + if (!cap_fmt) + return; + + ctx->dec_fmt[AML_FMT_DST] = *cap_fmt; + + memset(&fmt, 0, sizeof(struct v4l2_pix_format_mplane)); + + fmt.height = cap_fmt->stepwise.min_height; + fmt.width = cap_fmt->stepwise.min_width; + fmt.colorspace = V4L2_COLORSPACE_DEFAULT; + fmt.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt.quantization = V4L2_QUANTIZATION_DEFAULT; + fmt.xfer_func = V4L2_XFER_FUNC_DEFAULT; + vdec_try_fmt_mp(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, &fmt); + + ctx->pix_fmt[AML_FMT_DST] = fmt; +} + +void aml_vdec_reset_fmts(struct aml_vdec_ctx *ctx) +{ + ctx->m2m_ctx->q_lock = &ctx->v4l2_intf_lock; + reset_output_fmts(ctx); + reset_capture_fmts(ctx); +} + +static int vdec_g_fmt(struct aml_vdec_ctx *ctx, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + + if (V4L2_TYPE_IS_OUTPUT(f->type)) + *pix_mp = ctx->pix_fmt[AML_FMT_SRC]; + else + *pix_mp = ctx->pix_fmt[AML_FMT_DST]; + + dev_dbg(&ctx->dev->plat_dev->dev, + "%s fmt %d num planes %d\n", __func__, pix_mp->pixelformat, + pix_mp->num_planes); + + return 0; +} + +static int vidioc_try_fmt_cap_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vdec_try_fmt_mp(fh_to_dec_ctx(file), f->type, &f->fmt.pix_mp); +} + +static int vidioc_try_fmt_out_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vdec_try_fmt_mp(fh_to_dec_ctx(file), f->type, &f->fmt.pix_mp); +} + +static int vidioc_vdec_s_fmt_out_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + return vdec_s_fmt_output(ctx, f); +} + +static int vidioc_vdec_s_fmt_cap_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + return vdec_s_fmt_capture(ctx, f); +} + +static int vidioc_vdec_g_fmt_out_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + return vdec_g_fmt(ctx, f); +} + +static int vidioc_vdec_g_fmt_cap_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + return vdec_g_fmt(ctx, f); +} + +static int vidioc_vdec_enum_fmt_out_mplane(struct file *file, + void *priv, struct v4l2_fmtdesc *f) +{ + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + return vidioc_vdec_enum_fmt(ctx, f, 1); +} + +static int vidioc_vdec_enum_fmt_cap_mplane(struct file *file, + void *priv, struct v4l2_fmtdesc *f) +{ + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + return vidioc_vdec_enum_fmt(ctx, f, 0); +} + +const struct v4l2_ioctl_ops aml_vdec_ioctl_ops = { + .vidioc_querycap = vidioc_vdec_querycap, + .vidioc_enum_framesizes = vidioc_vdec_enum_framesizes, + + .vidioc_enum_fmt_vid_cap = vidioc_vdec_enum_fmt_cap_mplane, + .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_cap_mplane, + .vidioc_s_fmt_vid_cap_mplane = vidioc_vdec_s_fmt_cap_mplane, + .vidioc_g_fmt_vid_cap_mplane = vidioc_vdec_g_fmt_cap_mplane, + + .vidioc_enum_fmt_vid_out = vidioc_vdec_enum_fmt_out_mplane, + .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt_out_mplane, + .vidioc_s_fmt_vid_out_mplane = vidioc_vdec_s_fmt_out_mplane, + .vidioc_g_fmt_vid_out_mplane = vidioc_vdec_g_fmt_out_mplane, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_decoder_cmd = v4l2_m2m_ioctl_stateless_decoder_cmd, + .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_stateless_try_decoder_cmd, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, +}; + +static void aml_vdec_release_instance(struct aml_vdec_ctx *ctx) +{ + if (ctx->codec_ops && ctx->codec_ops->exit) + ctx->codec_ops->exit(ctx); +} + +static int vb2ops_vdec_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(vq); + struct v4l2_pix_format_mplane *pix_fmt; + unsigned int i; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + pix_fmt = &ctx->pix_fmt[AML_FMT_SRC]; + else + pix_fmt = &ctx->pix_fmt[AML_FMT_DST]; + + if (*nplanes) { + if (*nplanes != pix_fmt->num_planes) + return -EINVAL; + + for (i = 0; i < *nplanes; i++) { + if (sizes[i] < pix_fmt->plane_fmt[i].sizeimage) { + dev_err(&ctx->dev->plat_dev->dev, + "not supported sizeimage\n"); + return -EINVAL; + } + alloc_devs[i] = &ctx->dev->plat_dev->dev; + } + } else { + if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + *nplanes = pix_fmt->num_planes; + else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + *nplanes = 1; + + for (i = 0; i < *nplanes; i++) { + alloc_devs[i] = &ctx->dev->plat_dev->dev; + sizes[i] = pix_fmt->plane_fmt[i].sizeimage; + } + } + + if (*nplanes) { + dev_dbg(&ctx->dev->plat_dev->dev, + "type: %d, plane: %d, buf cnt: %d, size: [Y: %u, C: %u]\n", + vq->type, *nplanes, *nbuffers, sizes[0], sizes[1]); + return 0; + } + + return -EINVAL; +} + +static int vb2ops_vdec_buf_prepare(struct vb2_buffer *vb) +{ + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_pix_format_mplane *pix_fmt; + unsigned int sizeimage = 0; + int i; + + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) + pix_fmt = &ctx->pix_fmt[AML_FMT_SRC]; + else + pix_fmt = &ctx->pix_fmt[AML_FMT_DST]; + + for (i = 0; i < pix_fmt->num_planes; i++) { + sizeimage = pix_fmt->plane_fmt[i].sizeimage; + if (vb2_plane_size(vb, i) < sizeimage) + return -EINVAL; + + if (V4L2_TYPE_IS_CAPTURE(vb->type)) { + vb2_set_plane_payload(vb, i, + pix_fmt->plane_fmt[i].sizeimage); + dev_dbg(&ctx->dev->plat_dev->dev, + "%s type: %d set plane: %d, sizeimage: %d\n", + __func__, vb->vb2_queue->type, i, + pix_fmt->plane_fmt[i].sizeimage); + } + } + + return 0; +} + +static int vb2_ops_vdec_buf_init(struct vb2_buffer *vb) +{ + return 0; +} + +static void vb2_ops_vdec_buf_queue(struct vb2_buffer *vb) +{ + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vb2_v4l2 = to_vb2_v4l2_buffer(vb); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vb2_v4l2); +} + +static void vb2_ops_vdec_buf_finish(struct vb2_buffer *vb) +{ +} + +static int vb2ops_vdec_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(q); + + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + ctx->is_output_streamon = 1; + if (aml_vdec_init_dec_inst(ctx) < 0) + return -EINVAL; + } else { + ctx->is_cap_streamon = 1; + } + + return 0; +} + +static void vb2ops_vdec_stop_streaming(struct vb2_queue *q) +{ + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *src_buf = NULL, *dst_buf = NULL; + + aml_vdec_release_instance(ctx); + + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + while ((src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx))) + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + ctx->is_output_streamon = 0; + } else { + while ((dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx))) + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + ctx->is_cap_streamon = 0; + } +} + +static int vb2ops_vdec_out_buf_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + return 0; +} + +static void vb2ops_vdec_buf_request_complete(struct vb2_buffer *vb) +{ + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->ctrl_handler); +} + +static const struct vb2_ops aml_vdec_vb2_ops = { + .queue_setup = vb2ops_vdec_queue_setup, + .start_streaming = vb2ops_vdec_start_streaming, + .stop_streaming = vb2ops_vdec_stop_streaming, + + .buf_init = vb2_ops_vdec_buf_init, + .buf_prepare = vb2ops_vdec_buf_prepare, + .buf_out_validate = vb2ops_vdec_out_buf_validate, + .buf_queue = vb2_ops_vdec_buf_queue, + .buf_finish = vb2_ops_vdec_buf_finish, + .buf_request_complete = vb2ops_vdec_buf_request_complete, +}; + +int aml_vdec_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv; + struct aml_vdec_dev *dev = ctx->dev; + int ret = 0; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->drv_priv = ctx; + src_vq->ops = &aml_vdec_vb2_ops; + src_vq->lock = &ctx->v4l2_intf_lock; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->supports_requests = true; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + ret = vb2_queue_init(src_vq); + if (ret) { + v4l2_info(&dev->v4l2_dev, + "Failed to initialize videobuf2 queue(output)"); + return ret; + } + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->ops = &aml_vdec_vb2_ops; + dst_vq->lock = &ctx->v4l2_intf_lock; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + ret = vb2_queue_init(dst_vq); + if (ret) { + v4l2_info(&dev->v4l2_dev, + "Failed to initialize videobuf2 queue(capture)"); + vb2_queue_release(src_vq); + } + + return ret; +} diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec.h b/drivers/media/platform/amlogic/vdec/aml_vdec.h new file mode 100644 index 000000000000..32f7fa245f7e --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#ifndef _AML_VDEC_H_ +#define _AML_VDEC_H_ + +#include "aml_vdec_drv.h" + +/** + * struct aml_vdec_v4l2_ctrl - helper type to declare supported ctrls + * @codec_type: codec id this control belong to (CODEC_TYPE_H264, etc.) + * @cfg: control configuration + */ +struct aml_vdec_v4l2_ctrl { + unsigned int codec_type; + struct v4l2_ctrl_config cfg; +}; + +struct aml_dec_type { + unsigned int codec_type; + const char *name; +}; + +extern const struct v4l2_m2m_ops aml_vdec_m2m_ops; +extern const struct v4l2_ioctl_ops aml_vdec_ioctl_ops; + +int aml_vdec_ctrls_setup(struct aml_vdec_ctx *ctx); +int aml_vdec_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq); +void aml_vdec_reset_fmts(struct aml_vdec_ctx *ctx); +#endif diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c new file mode 100644 index 000000000000..d63cbd4f9e26 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include + +#include "aml_vdec.h" +#include "aml_vdec_hw.h" +#include "aml_vdec_platform.h" + +#define AML_VDEC_DRV_NAME "aml-vdec-drv" + +static int fops_vcodec_open(struct file *file) +{ + struct aml_vdec_dev *dec_dev = video_drvdata(file); + struct aml_vdec_ctx *ctx = NULL; + int ret = 0; + + ctx = kzalloc_obj(*ctx, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mutex_lock(&dec_dev->dev_mutex); + dec_dev->dec_ctx = ctx; + ctx->dev = dec_dev; + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh, file); + dec_dev->filp = file; + mutex_init(&ctx->v4l2_intf_lock); + init_waitqueue_head(&ctx->queue); + ctx->int_cond = 0; + + ctx->m2m_ctx = v4l2_m2m_ctx_init(dec_dev->m2m_dev_dec, ctx, + &aml_vdec_queue_init); + if (IS_ERR(ctx->m2m_ctx)) { + ret = PTR_ERR((__force void *)ctx->m2m_ctx); + v4l2_err(&dec_dev->v4l2_dev, "Failed to v4l2_m2m_ctx_init() (%d)", ret); + goto err_m2m_ctx_init; + } + + ctx->fh.m2m_ctx = ctx->m2m_ctx; + ret = aml_vdec_ctrls_setup(ctx); + if (ret) { + v4l2_err(&dec_dev->v4l2_dev, "Failed to init all ctrls (%d)", ret); + goto err_ctrls_setup; + } + + aml_vdec_reset_fmts(ctx); + mutex_unlock(&dec_dev->dev_mutex); + + return ret; + +err_ctrls_setup: + v4l2_m2m_ctx_release(ctx->m2m_ctx); +err_m2m_ctx_init: + v4l2_fh_del(&ctx->fh, file); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + mutex_unlock(&dec_dev->dev_mutex); + + return ret; +} + +static int fops_vcodec_release(struct file *file) +{ + struct aml_vdec_dev *dec_dev = video_drvdata(file); + struct aml_vdec_ctx *ctx = fh_to_dec_ctx(file); + + mutex_lock(&dec_dev->dev_mutex); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_m2m_ctx_release(ctx->m2m_ctx); + v4l2_fh_del(&ctx->fh, file); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + mutex_unlock(&dec_dev->dev_mutex); + + return 0; +} + +static const struct v4l2_file_operations aml_vdec_fops = { + .owner = THIS_MODULE, + .open = fops_vcodec_open, + .release = fops_vcodec_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device dec_dev = { + .name = "aml_dev_drv", + .fops = &aml_vdec_fops, + .ioctl_ops = &aml_vdec_ioctl_ops, + .release = video_device_release, + .vfl_dir = VFL_DIR_M2M, + .device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING, +}; + +static const struct media_device_ops aml_m2m_media_ops = { + .req_validate = vb2_request_validate, + .req_queue = v4l2_m2m_request_queue, +}; + +static int aml_vdec_drv_probe(struct platform_device *pdev) +{ + struct aml_vdec_dev *dev; + struct video_device *vfd_dec; + struct aml_vdec_hw *hw; + int ret = 0; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->plat_dev = pdev; + mutex_init(&dev->dev_mutex); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + return dev_err_probe(&pdev->dev, ret, "v4l2_device_register err\n"); + + vfd_dec = video_device_alloc(); + if (!vfd_dec) { + v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto err_device_alloc; + } + *vfd_dec = dec_dev; + vfd_dec->v4l2_dev = &dev->v4l2_dev; + vfd_dec->lock = &dev->dev_mutex; + video_set_drvdata(vfd_dec, dev); + dev->vfd = vfd_dec; + platform_set_drvdata(pdev, dev); + + hw = devm_kzalloc(&pdev->dev, sizeof(*hw), GFP_KERNEL); + if (!hw) { + ret = -ENOMEM; + goto err_dec_mem_init; + } + dev->dec_hw = hw; + + dev->pvdec_data = of_device_get_match_data(&pdev->dev); + ret = dev->pvdec_data->req_hw_resource(dev); + if (ret < 0) + goto err_hw_init; + + dev->m2m_dev_dec = v4l2_m2m_init(&aml_vdec_m2m_ops); + if (IS_ERR(dev->m2m_dev_dec)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem dec device\n"); + ret = PTR_ERR((__force void *)dev->m2m_dev_dec); + goto err_hw_init; + } + + ret = video_register_device(vfd_dec, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device"); + goto err_vid_dev_register; + } + + dev->mdev.dev = &pdev->dev; + strscpy(dev->mdev.model, AML_VDEC_DRV_NAME, sizeof(dev->mdev.model)); + media_device_init(&dev->mdev); + dev->mdev.ops = &aml_m2m_media_ops; + dev->v4l2_dev.mdev = &dev->mdev; + + ret = v4l2_m2m_register_media_controller(dev->m2m_dev_dec, vfd_dec, + MEDIA_ENT_F_PROC_VIDEO_DECODER); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n"); + goto error_m2m_mc_register; + } + + ret = media_device_register(&dev->mdev); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register media device"); + goto err_media_dev_register; + } + vdec_enable(dev->dec_hw); + return 0; + +err_media_dev_register: + v4l2_m2m_unregister_media_controller(dev->m2m_dev_dec); +error_m2m_mc_register: + media_device_cleanup(&dev->mdev); +err_vid_dev_register: + v4l2_m2m_release(dev->m2m_dev_dec); +err_hw_init: + dev->dec_hw = NULL; +err_dec_mem_init: + video_device_release(vfd_dec); +err_device_alloc: + v4l2_device_unregister(&dev->v4l2_dev); + return ret; +} + +static void aml_vdec_drv_remove(struct platform_device *pdev) +{ + struct aml_vdec_dev *dev = platform_get_drvdata(pdev); + + vdec_disable(dev->dec_hw); + + if (media_devnode_is_registered(dev->mdev.devnode)) { + media_device_unregister(&dev->mdev); + media_device_cleanup(&dev->mdev); + } + + if (dev->m2m_dev_dec) + v4l2_m2m_release(dev->m2m_dev_dec); + if (dev->vfd) + video_unregister_device(dev->vfd); + if (dev->dec_hw) { + dev->pvdec_data->destroy_hw_resource(dev); + dev->dec_hw = NULL; + } + v4l2_device_unregister(&dev->v4l2_dev); +} + +static const struct of_device_id aml_vdec_match[] = { + {.compatible = "amlogic,s4-vdec", .data = &aml_vdec_s4_pdata}, + {}, +}; + +static struct platform_driver aml_vcodec_dec_driver = { + .probe = aml_vdec_drv_probe, + .remove = aml_vdec_drv_remove, + .driver = { + .name = AML_VDEC_DRV_NAME, + .of_match_table = aml_vdec_match, + }, +}; + +module_platform_driver(aml_vcodec_dec_driver); + +MODULE_DESCRIPTION("Amlogic V4L2 decoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_drv.h b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.h new file mode 100644 index 000000000000..fa86233ea5b3 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#ifndef _AML_VDEC_DRV_H_ +#define _AML_VDEC_DRV_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define AML_VCODEC_MAX_PLANES 3 +#define AML_VDEC_MIN_W 64U +#define AML_VDEC_MIN_H 64U +#define AML_VDEC_1080P_MAX_H 1088U +#define AML_VDEC_1080P_MAX_W 1920U + +struct aml_vdec_ctx; +/** + * enum aml_fmt_type - Type of format type + */ +enum aml_fmt_type { + AML_FMT_DEC = 0, + AML_FMT_FRAME = 1, +}; + +/** + * enum aml_codec_type - Type of codec format + */ +enum aml_codec_type { + CODEC_TYPE_H264 = 0, + CODEC_TYPE_FRAME, +}; + +/** + * enum aml_queue_type - Type of queue : cap or output + */ +enum aml_queue_type { + AML_FMT_SRC = 0, + AML_FMT_DST = 1, +}; + +/** + * struct aml_video_fmt - aml video decoder fmt information + * @fourcc: FourCC code of the format. See V4L2_PIX_FMT_*. + * @align: Describe the align width/height required by hardware. + * @is_10_bit_support: If the curr platform support p010 output. + * @type: Curr queue type: capture or output. + * @codec_type: Codec mode related. See aml_codec_type. + * @num_planes: Num planes of the format. + * @name: Name of the format. + * @stepwise: Supported range of frame sizes (only for bitstream formats). + */ +struct aml_video_fmt { + u32 fourcc; + int align; + int is_10_bit_support; + enum aml_fmt_type type; + enum aml_codec_type codec_type; + u32 num_planes; + const u8 *name; + struct v4l2_frmsize_stepwise stepwise; +}; + +/** + * struct aml_vdec_dev - driver data + * @plat_dev: Platform device for the current driver. + * @v4l2_dev: V4L2 device to register video devices for. + * @m2m_dev_dec: Mem2mem device associated to this device. + * @vfd: Video_device associated to this device. + * @mdev: Media_device associated to this device. + * @dec_ctx: Decoder context. See struct aml_vdec_ctx. + * @dec_hw: Decoder hardware resources. See struct aml_vdec_hw. + * @pvdec_data: Decoder platform data. See struct aml_dev_platform_data. + * @dev_mutex: video_device lock. + * @filp: v4l2 file handle pointer. + */ +struct aml_vdec_dev { + struct platform_device *plat_dev; + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m_dev_dec; + struct video_device *vfd; + struct media_device mdev; + + struct aml_vdec_ctx *dec_ctx; + struct aml_vdec_hw *dec_hw; + const struct aml_dev_platform_data *pvdec_data; + + struct mutex dev_mutex; + struct file *filp; +}; + +/** + * struct dec_pic_info - pic information description + * @cap_pix_fmt: Pixel format for capture queue. + * @output_pix_fmt: Pixel format for output queue. + * @coded_width: Width for decode. + * @coded_height: Height for decode. + * @fb_size: Frame buffer size for Y or UV. + * @plane_num: Num for planes for curr format. + */ +struct dec_pic_info { + u32 cap_pix_fmt; + u32 output_pix_fmt; + u32 coded_width; + u32 coded_height; + u32 fb_size[2]; + u32 plane_num; +}; + +/** + * struct aml_vdec_ctx - driver instance context + * @dev: pointer to the aml_vdec_dev of the device. + * @fh: struct v4l2 fh. + * @m2m_ctx: pointer to v4l2_m2m_ctx context. + * @ctrl_handler: V4L2 ctrl handler. + * @v4l2_intf_lock: Mutex lock for v4l2 interface. + * @codec_ops: Codec operation functions. See struct aml_codec_ops. + * @int_cond: Variable used by the waitqueue. + * @queue: Waitqueue to wait for the current decode context finish. + * @pix_fmt: To store the V4L2 pixel format. + * @dec_fmt: To describe the decoding format supported by hardware platform. + * @is_cap_streamon: indicates if the current capture stream is on. + * @is_output_streamon: indicates if the current output stream is on. + * @dos_clk_en: indicates if dos clk is enabled. + * @pic_info: Pic information for curr decoder context. See struct dec_pic_info. + * @curr_dec_type: Current decoder type. (CODEC_TYPE_H264, etc.) + * @codec_priv: Pointer to current decoder instance. + */ +struct aml_vdec_ctx { + struct aml_vdec_dev *dev; + struct v4l2_fh fh; + struct v4l2_m2m_ctx *m2m_ctx; + struct v4l2_ctrl_handler ctrl_handler; + struct mutex v4l2_intf_lock; + + const struct aml_codec_ops *codec_ops; + int int_cond; + wait_queue_head_t queue; + struct v4l2_pix_format_mplane pix_fmt[2]; + struct aml_video_fmt dec_fmt[2]; + + bool is_cap_streamon; + bool is_output_streamon; + bool dos_clk_en; + + struct dec_pic_info pic_info; + u32 curr_dec_type; + void *codec_priv; +}; + +static inline struct aml_vdec_ctx *fh_to_dec_ctx(struct file *file) +{ + struct v4l2_fh *file_fh = file_to_v4l2_fh(file); + + return container_of(file_fh, struct aml_vdec_ctx, fh); +} + +static inline struct aml_vdec_ctx *ctrl_to_dec_ctx(struct v4l2_ctrl_handler *ctrl) +{ + return container_of(ctrl, struct aml_vdec_ctx, ctrl_handler); +} + +#endif diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c new file mode 100644 index 000000000000..79ba2a68bde4 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c @@ -0,0 +1,538 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aml_vdec_drv.h" +#include "aml_vdec_hw.h" +#include "aml_vdec_platform.h" +#include "aml_vdec_tee_fw.h" + +#define MHz (1000000) +#define MC_SIZE (4096 * 16) + +static struct pm_pd_s vdec_domain_data[] = { + [VDEC_1] = {.name = "vdec", }, + [VDEC_HEVC] = {.name = "hevc", }, +}; + +u32 read_dos_reg(struct aml_vdec_hw *hw, u32 addr) +{ + u32 ret_val; + + regmap_read(hw->map[DOS_BUS], addr, &ret_val); + + return ret_val; +} + +static void dos_reg_write_bits(struct aml_vdec_hw *hw, u32 reg, u32 val, int start, int len) +{ + u32 mask = (((1L << (len)) - 1) << (start)); + + regmap_update_bits(hw->map[DOS_BUS], reg, mask, val); +} + +void dos_enable(struct aml_vdec_hw *hw) +{ + dos_reg_write_bits(hw, DOS_GCLK_EN0, 0x3ff, 0, 10); + + regmap_write(hw->map[DOS_BUS], GCLK_EN, 0x3ff); + + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 31), 0); +} + +void aml_vdec_reset_core(struct aml_vdec_hw *hw) +{ + unsigned int mask = 0; + + mask = (1 << 21); + + regmap_update_bits(hw->map[DMC_BUS], 0x0, mask, 0); + usleep_range(60, 70); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, + (1 << 3) | (1 << 4) | (1 << 5) | (1 << 7) | + (1 << 8) | (1 << 9)); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0); + regmap_update_bits(hw->map[DOS_BUS], VDEC_ASSIST_MMC_CTRL1, 1 << 3, 0); + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_MUX_CTRL, 1 << 31, 0); + regmap_write(hw->map[DOS_BUS], MDEC_EXTIF_CFG1, 0); + + regmap_update_bits(hw->map[DMC_BUS], 0x0, mask, mask); +} + +void aml_start_vdec_hw(struct aml_vdec_hw *hw) +{ + u32 reg_read_val; + + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val); + + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, (1 << 12) | (1 << 11)); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0); + + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_read_val); + + regmap_write(hw->map[DOS_BUS], MPSR, 0x0001); +} + +void aml_stop_vdec_hw(struct aml_vdec_hw *hw) +{ + u32 reg_val = 0; + int ret; + + regmap_write(hw->map[DOS_BUS], MPSR, 0); + regmap_write(hw->map[DOS_BUS], CPSR, 0); + + ret = read_poll_timeout_atomic(read_dos_reg, reg_val, + !(reg_val & 0x8000), + 10, 100000, true, + hw, IMEM_DMA_CTRL); + + ret = read_poll_timeout_atomic(read_dos_reg, reg_val, + !(reg_val & 0x8000), + 10, 100000, true, + hw, LMEM_DMA_CTRL); + + ret = read_poll_timeout_atomic(read_dos_reg, reg_val, + !(reg_val & 0xfff), + 10, 800000, true, + hw, WRRSP_LMEM); + if (ret) + dev_err(hw->dev, "%s, ctrl %x, rsp %x, pc %x status %x,%x\n", + __func__, read_dos_reg(hw, LMEM_DMA_CTRL), + read_dos_reg(hw, WRRSP_LMEM), read_dos_reg(hw, MPC_E), + read_dos_reg(hw, AV_SCRATCH_J), read_dos_reg(hw, AV_SCRATCH_9)); + + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val); + + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, (1 << 12) | (1 << 11)); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0); + + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val); + regmap_read(hw->map[DOS_BUS], DOS_SW_RESET0, ®_val); +} + +static int vdec_clock_gate_init(struct aml_vdec_hw *hw) +{ + hw->gates[CLK_DOS].id = "dos"; + hw->gates[CLK_VDEC].id = "vdec"; + hw->gates[CLK_HEVCF].id = "hevcf"; + + return devm_clk_bulk_get(hw->dev, CLK_MAX, hw->gates); +} + +static struct clk_bulk_data *vdec_get_clk_by_name(struct aml_vdec_hw *hw, + const char *name) +{ + int i; + + for (i = 0; i < CLK_MAX; i++) { + if (!strcmp(name, hw->gates[i].id)) { + if (hw->gates[i].clk) + return &hw->gates[i]; + } + } + return NULL; +} + +static int pm_vdec_power_domain_init(struct aml_vdec_hw *hw) +{ + int i, err; + const struct power_manager_s *pm = hw->pm; + struct pm_pd_s *pd = pm->pd_data; + + mutex_init(&hw->pm_mutex); + + for (i = 0; i < VDEC_MAX; i++) { + pd[i].dev = dev_pm_domain_attach_by_name(hw->dev, pd[i].name); + if (IS_ERR_OR_NULL(pd[i].dev)) { + err = PTR_ERR(pd[i].dev); + dev_dbg(hw->dev, "Get %s failed, pm-domain: %d\n", + pd[i].name, err); + continue; + } + + pd[i].link = device_link_add(hw->dev, pd[i].dev, + DL_FLAG_PM_RUNTIME | + DL_FLAG_STATELESS); + if (IS_ERR_OR_NULL(pd[i].link)) { + dev_err(hw->dev, "Adding %s device link failed!\n", pd[i].name); + return -ENODEV; + } + + dev_dbg(hw->dev, "power domain: name: %s, dev: %p, link: %p\n", + pd[i].name, pd[i].dev, pd[i].link); + } + + return 0; +} + +static void pm_vdec_power_domain_release(struct aml_vdec_hw *hw) +{ + int i; + const struct power_manager_s *pm = hw->pm; + struct pm_pd_s *pd = pm->pd_data; + + for (i = 0; i < VDEC_MAX; i++) { + if (!IS_ERR_OR_NULL(pd[i].link)) + device_link_del(pd[i].link); + + if (!IS_ERR_OR_NULL(pd[i].dev)) + dev_pm_domain_detach(pd[i].dev, true); + } +} + +static void dos_local_config(struct aml_vdec_hw *hw, bool is_on, int id) +{ + if (is_on) { + usleep_range(20, 100); + + switch (id) { + case VDEC_1: + regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_VDEC, 0); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0xfffffffc); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_VDEC, 0); + break; + case VDEC_HEVC: + regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_HEVC, 0); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET3, 0xffffffff); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET3, 0); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_HEVC, 0); + break; + default: + dev_info(hw->dev, "%s on, not found id %d\n", __func__, id); + break; + } + } else { + switch (id) { + case VDEC_1: + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0xfffffffc); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_VDEC, 0xffffffffUL); + break; + case VDEC_HEVC: + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET3, 0xffffffff); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET3, 0); + usleep_range(20, 100); + regmap_write(hw->map[DOS_BUS], DOS_MEM_PD_HEVC, 0xffffffffUL); + break; + default: + dev_info(hw->dev, "%s off, not found id %d\n", __func__, id); + break; + } + } + + dev_dbg(hw->dev, "%s end, id %d, is_on %d\n", __func__, id, is_on); +} + +static void pm_vdec_power_domain_power_on(struct aml_vdec_hw *hw, int id) +{ + const struct power_manager_s *pm = hw->pm; + struct device *dev = pm->pd_data[id].dev; + struct clk_bulk_data *gate_node = NULL; + + if (id == VDEC_1) + gate_node = vdec_get_clk_by_name(hw, "vdec"); + else if (id == VDEC_HEVC) + gate_node = vdec_get_clk_by_name(hw, "hevcf"); + + if (gate_node) { + clk_prepare_enable(gate_node->clk); + if (id == VDEC_1) { + clk_set_rate(gate_node->clk, 499999992); + dev_dbg(hw->dev, "after set, vdec clock is %lu Hz\n", + clk_get_rate(gate_node->clk)); + } + dev_dbg(hw->dev, "the %-15s clock on\n", gate_node->id); + } else { + dev_info(hw->dev, "clk %d, unreachable\n", id); + } + + if (dev) { + pm_runtime_get_sync(dev); + dev_dbg(dev, "dev: %p link %p the %-15s power on\n", + dev, pm->pd_data[id].link, pm->pd_data[id].name); + } + + dos_local_config(hw, 1, id); +} + +static void pm_vdec_power_domain_power_off(struct aml_vdec_hw *hw, int id) +{ + const struct power_manager_s *pm = hw->pm; + struct device *dev = pm->pd_data[id].dev; + struct clk_bulk_data *gate_node = NULL; + + dos_local_config(hw, 0, id); + + if (dev) { + pm_runtime_put_sync(dev); + dev_dbg(dev, "dev: %p link %p the %-15s power off\n", + dev, pm->pd_data[id].link, pm->pd_data[id].name); + } + + if (id == VDEC_1) + gate_node = vdec_get_clk_by_name(hw, "vdec"); + else if (id == VDEC_HEVC) + gate_node = vdec_get_clk_by_name(hw, "hevcf"); + + if (gate_node) { + clk_disable_unprepare(gate_node->clk); + dev_dbg(hw->dev, "the %-15s clock off\n", gate_node->id); + } else { + dev_info(hw->dev, "clk %d, unreachable\n", id); + } +} + +static bool pm_vdec_power_domain_power_state(struct aml_vdec_hw *hw, int id) +{ + if (hw->pm->pd_data[id].dev) + return pm_runtime_active(hw->pm->pd_data[id].dev); + else + return false; +} + +static void vdec_poweron(struct aml_vdec_hw *hw, enum vdec_type_e core) +{ + if (core >= VDEC_MAX) + return; + + mutex_lock(&hw->pm_mutex); + if (!hw->pm->pd_data[core].dev) + goto out; + + hw->pm->pd_data[core].ref_count++; + if (hw->pm->pd_data[core].ref_count > 1) + goto out; + + if (hw->pm->power_state(hw, core)) + goto out; + + hw->pm->power_on(hw, core); + +out: + mutex_unlock(&hw->pm_mutex); +} + +static void vdec_poweroff(struct aml_vdec_hw *hw, enum vdec_type_e core) +{ + if (core >= VDEC_MAX) + return; + + mutex_lock(&hw->pm_mutex); + if (hw->pm->pd_data[core].ref_count == 0) + goto out; + + hw->pm->pd_data[core].ref_count--; + if (hw->pm->pd_data[core].ref_count > 0) + goto out; + + hw->pm->power_off(hw, core); + +out: + mutex_unlock(&hw->pm_mutex); +} + +int vdec_enable(struct aml_vdec_hw *hw) +{ + vdec_poweron(hw, VDEC_1); + + return 0; +} + +int vdec_disable(struct aml_vdec_hw *hw) +{ + vdec_poweroff(hw, VDEC_1); + + return 0; +} + +static const struct power_manager_s pm[] = { + [AML_PM_PD] = { + .pd_data = vdec_domain_data, + .init = pm_vdec_power_domain_init, + .release = pm_vdec_power_domain_release, + .power_on = pm_vdec_power_domain_power_on, + .power_off = pm_vdec_power_domain_power_off, + .power_state = pm_vdec_power_domain_power_state, + }, +}; + +static irqreturn_t vdec_irq_handler(int irq, void *priv) +{ + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + struct aml_vdec_hw *hw = dev->dec_hw; + irqreturn_t ret = IRQ_HANDLED; + + if (hw->hw_ops.irq_handler) + ret = hw->hw_ops.irq_handler(irq, priv); + + return ret; +} + +static irqreturn_t vdec_threaded_isr_handler(int irq, void *priv) +{ + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + struct aml_vdec_hw *hw = dev->dec_hw; + irqreturn_t ret = IRQ_HANDLED; + + if (hw->hw_ops.irq_threaded_func) + ret = hw->hw_ops.irq_threaded_func(irq, priv); + + return ret; +} + +struct aml_vdec_hw *vdec_get_hw(void *priv) +{ + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + + return dev->dec_hw; +} + +static const struct regmap_config dos_regmap_conf = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x10000, +}; + +static const struct regmap_config dmc_regmap_conf = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x20, +}; + +int dev_request_hw_resources(void *priv) +{ + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + struct aml_vdec_hw *hw; + struct platform_device *pdev; + struct device_node *sm_np; + void __iomem *reg_base[MAX_REG_BUS]; + struct resource res; + int i; + int ret = -1; + + if (!dev || !dev->dec_hw) + return -1; + + pdev = dev->plat_dev; + hw = dev->dec_hw; + hw->dev = &pdev->dev; + + hw->dec_irq = platform_get_irq(pdev, VDEC_IRQ_1); + if (hw->dec_irq < 0) { + dev_err(&pdev->dev, "get irq failed\n"); + return hw->dec_irq; + } + ret = devm_request_threaded_irq(&pdev->dev, hw->dec_irq, vdec_irq_handler, + vdec_threaded_isr_handler, IRQF_ONESHOT, + "vdec-1", dev); + if (ret) { + dev_err(&pdev->dev, "failed to install irq %d (%d)", + hw->dec_irq, ret); + return -1; + } + + for (i = 0; i < MAX_REG_BUS; i++) { + if (of_address_to_resource(pdev->dev.of_node, i, &res)) { + dev_err(&pdev->dev, "of_address_to_resource %d failed\n", i); + return -EINVAL; + } + reg_base[i] = devm_ioremap_resource(&pdev->dev, &res); + + if (IS_ERR(reg_base[i])) + return PTR_ERR(reg_base[i]); + + if (i == DOS_BUS) { + hw->map[i] = devm_regmap_init_mmio(&pdev->dev, reg_base[i], + &dos_regmap_conf); + } else if (i == DMC_BUS) { + hw->map[i] = devm_regmap_init_mmio(&pdev->dev, reg_base[i], + &dmc_regmap_conf); + } + + if (IS_ERR(hw->map[i])) + return PTR_ERR(hw->map[i]); + + dev_dbg(&pdev->dev, "%s, res start %llx, end %llx, iomap: %p\n", + __func__, (unsigned long long)res.start, + (unsigned long long)res.end, reg_base[i]); + } + hw->canvas = meson_canvas_get(&pdev->dev); + if (IS_ERR(hw->canvas)) + return PTR_ERR(hw->canvas); + + sm_np = of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0); + if (IS_ERR(sm_np)) + return PTR_ERR(hw->canvas); + + hw->sec_fw = meson_sm_get(sm_np); + of_node_put(sm_np); + if (IS_ERR(hw->sec_fw)) + return PTR_ERR(hw->sec_fw); + + hw->pm = &pm[dev->pvdec_data->power_type]; + if (hw->pm->init) { + ret = hw->pm->init(hw); + if (ret < 0) { + dev_err(&pdev->dev, "power mgr init failed!\n"); + return ret; + } + } + + ret = vdec_clock_gate_init(hw); + if (ret) { + dev_err(&pdev->dev, "clk bulk init failed!\n"); + return ret; + } + + ret = aml_tee_fw_preload(hw); + if (ret) + return ret; + + dev_dbg(&pdev->dev, "##Amlogic hw resource init OK##\n"); + + return 0; +} + +void dev_destroy_hw_resources(void *priv) +{ + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + struct aml_vdec_hw *hw; + + if (!dev || !dev->dec_hw) + return; + + hw = dev->dec_hw; + + if (hw->pm->release) + hw->pm->release(hw); + + dev_dbg(hw->dev, "##Amlogic hw resource release OK##\n"); +} diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_hw.h b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.h new file mode 100644 index 000000000000..443f48226239 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#ifndef _AML_VDEC_HW_H_ +#define _AML_VDEC_HW_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "reg_defines.h" + +#define VDEC_FIFO_ALIGN 8 +#define VLD_PADDING_SIZE 1024 + +/** + * struct aml_vdec_hw_ops - codec mode specific operations for hw + * @load_firmware_ex: Load firmware for current dec specific. + * @irq_handler: mandatory call when the ISR triggers + * @irq_threaded_func: mandatory call for the threaded ISR + * @canvas_alloc: Alloc canvas for curr frame + * @canvas_free: Release canvas. + * @config_canvas: Config for curr frame, such as w/h, Y/UV start addr etc. + */ +struct aml_vdec_hw_ops { + int (*load_firmware_ex)(void *priv, const u8 *data, u32 len); + irqreturn_t (*irq_handler)(int irq, void *priv); + irqreturn_t (*irq_threaded_func)(int irq, void *priv); + int (*canvas_alloc)(u8 *canvas_index); + void (*canvas_free)(u8 canvas_index); + void (*config_canvas)(u8 canvas_index, + ulong addr, u32 width, u32 height, + u32 wrap, u32 blkmode, u32 endian); +}; + +/** + * enum vdec_type_e - Type of decoder hardware. + */ +enum vdec_type_e { + VDEC_1 = 0, + VDEC_HEVC, + VDEC_MAX, +}; + +/** + * enum vdec_irq_num - Definition of the irq. + */ +enum vdec_irq_num { + VDEC_IRQ_0 = 0, + VDEC_IRQ_1, + VDEC_IRQ_2, + VDEC_IRQ_MAX, +}; + +/** + * enum vdec_type_e - Type of decoder clock. + */ +enum clk_type_e { + CLK_DOS = 0, + CLK_VDEC, + CLK_HEVCF, + CLK_MAX, +}; + +/** + * enum aml_power_type_e - Type of decoder power. + */ +enum aml_power_type_e { + AML_PM_PD = 0, +}; + +/** + * enum mm_bus_e - Type of decoder hardware bus. + */ +enum mm_bus_e { + DOS_BUS = 0, + DMC_BUS, + MAX_REG_BUS +}; + +/** + * struct pm_pd_s - power domain definition + * @name: Power domain name. + * @dev: Pointer to device structure. + * @mutex: Pointer to device_link structure. + * @ref_count: Curr power domain instance ref count. + */ +struct pm_pd_s { + u8 *name; + struct device *dev; + struct device_link *link; + int ref_count; +}; + +/** + * struct aml_vdec_hw - decoder hardware resources definition + * @pdev: Pointer to struct platform_device. + * @dev: Pointer to struct device. + * @regs: Reg base for dos/dmc hardware. + * @pm_mutex: Mutex for pm->pd_data. + * @pm: Pointer to struct power_manager_s. + * @hw_ops: Hardware resource operation functions. See struct aml_vdec_hw_ops. + * @gates: Clk instance used by curr decoder context. + * @dec_irq: Irq registered. + * @curr_ctx: Pointer to curr decoder context. + */ +struct aml_vdec_hw { + struct platform_device *pdev; + struct device *dev; + struct regmap *map[MAX_REG_BUS]; + struct mutex pm_mutex; + struct meson_canvas *canvas; + struct meson_sm_firmware *sec_fw; + const struct power_manager_s *pm; + struct aml_vdec_hw_ops hw_ops; + struct clk_bulk_data gates[CLK_MAX]; + int dec_irq; + void *curr_ctx; +}; + +/** + * struct power_manager_s - Power manager & opertion function + * @pd_data: Pointer to struct pm_pd_s + * @init: Power manager init. + * @release: Power manager release. + * @power_on: Power on for decoder hw. + * @power_off: Power off for decoder hw. + * @power_state: Query the power state. + */ +struct power_manager_s { + struct pm_pd_s *pd_data; + int (*init)(struct aml_vdec_hw *hw); + void (*release)(struct aml_vdec_hw *hw); + void (*power_on)(struct aml_vdec_hw *hw, int id); + void (*power_off)(struct aml_vdec_hw *hw, int id); + bool (*power_state)(struct aml_vdec_hw *hw, int id); +}; + +int dev_request_hw_resources(void *priv); +void dev_destroy_hw_resources(void *priv); +struct aml_vdec_hw *vdec_get_hw(void *priv); +u32 read_dos_reg(struct aml_vdec_hw *hw, u32 reg_addr); +int vdec_enable(struct aml_vdec_hw *hw); +int vdec_disable(struct aml_vdec_hw *hw); +void dos_enable(struct aml_vdec_hw *hw); +void aml_start_vdec_hw(struct aml_vdec_hw *hw); +void aml_stop_vdec_hw(struct aml_vdec_hw *hw); +void aml_vdec_reset_core(struct aml_vdec_hw *hw); + +#endif diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_platform.c b/drivers/media/platform/amlogic/vdec/aml_vdec_platform.c new file mode 100644 index 000000000000..60d20efb1d74 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_platform.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#include "aml_vdec_platform.h" +#include "aml_vdec_hw.h" +#include "h264.h" + +static struct aml_video_fmt aml_s4_video_formats[] = { + { + .name = "H.264", + .fourcc = V4L2_PIX_FMT_H264_SLICE, + .type = AML_FMT_DEC, + .align = 64, + .is_10_bit_support = 0, + .codec_type = CODEC_TYPE_H264, + .num_planes = 1, + .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2, + AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2}, + }, + { + .name = "NV21M", + .fourcc = V4L2_PIX_FMT_NV21M, + .type = AML_FMT_FRAME, + .align = 64, + .codec_type = CODEC_TYPE_FRAME, + .num_planes = 2, + .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2, + AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2}, + }, + { + .name = "NV21", + .fourcc = V4L2_PIX_FMT_NV21, + .type = AML_FMT_FRAME, + .align = 64, + .codec_type = CODEC_TYPE_FRAME, + .num_planes = 1, + .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2, + AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2}, + }, + { + .name = "NV12M", + .fourcc = V4L2_PIX_FMT_NV12M, + .type = AML_FMT_FRAME, + .align = 64, + .codec_type = CODEC_TYPE_FRAME, + .num_planes = 2, + .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2, + AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2}, + + }, + { + .name = "NV12", + .fourcc = V4L2_PIX_FMT_NV12, + .type = AML_FMT_FRAME, + .align = 64, + .codec_type = CODEC_TYPE_FRAME, + .num_planes = 1, + .stepwise = {AML_VDEC_MIN_W, AML_VDEC_1080P_MAX_W, 2, + AML_VDEC_MIN_H, AML_VDEC_1080P_MAX_H, 2}, + }, +}; + +const struct aml_codec_ops aml_S4_dec_ops[] = { + [CODEC_TYPE_H264] = { + .init = aml_h264_init, + .exit = aml_h264_exit, + .run = aml_h264_dec_run, + }, +}; + +const struct aml_dev_platform_data aml_vdec_s4_pdata = { + .codec_ops = aml_S4_dec_ops, + .dec_fmt = aml_s4_video_formats, + .num_fmts = ARRAY_SIZE(aml_s4_video_formats), + .power_type = AML_PM_PD, + .req_hw_resource = dev_request_hw_resources, + .destroy_hw_resource = dev_destroy_hw_resources, +}; + diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_platform.h b/drivers/media/platform/amlogic/vdec/aml_vdec_platform.h new file mode 100644 index 000000000000..a167abfe7b51 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_platform.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#ifndef AML_VDEC_PLATFORM_H_ +#define AML_VDEC_PLATFORM_H_ + +#include +#include "aml_vdec_drv.h" + +/** + * struct aml_codec_ops - codec mode specific operations + * @init: Used for decoder initialization. + * @exit: If needed, can be used to undo the .init phase. + * @run: Start a single decoding job. Called from atomic context. + * Caller should ensure that a pair of buffers is ready and the + * hardware is powered on and clk is enabled. Returns zero if OK, + * a negative value in error cases. + */ +struct aml_codec_ops { + int (*init)(void *priv); + void (*exit)(void *priv); + int (*run)(void *priv); +}; + +/** + * struct aml_dev_platform_data - compatible data for each chip. + * @dec_fmt: Support dec format. + * @codec_ops: Codec operation function. + * @req_hw_resource: Operation function to request the hardware resource. + * @destroy_hw_resource: Operation function to release the hardware resource. + * @power_type: Type of power that the current chip need. See aml_power_type_e. + */ +struct aml_dev_platform_data { + const struct aml_codec_ops *codec_ops; + const struct aml_video_fmt *dec_fmt; + int num_fmts; + int (*req_hw_resource)(void *priv); + void (*destroy_hw_resource)(void *priv); + int power_type; +}; + +extern const struct aml_dev_platform_data aml_vdec_s4_pdata; + +#endif diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c new file mode 100644 index 000000000000..ad4156249f55 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ +#include +#include + +#include "aml_vdec_tee_fw.h" +#include "aml_vdec_drv.h" + +#define VIDEO_DEC_H264_MULTI 15 + +#define CORE_VDEC_LEGENCY 0 + +#define FIRMWARE_PATH "video_ucode.bin" +#define ONCE_SENT_SIZE (1024 * 128) +#define UCODE_HEADER_SIZE (1024 * 32) + +#define TEEC_SUCCESS 0x0 +#define TEEC_ERROR_BUSY 0xffff000d +#define FIRMWARE_CMD_PROCESS 0 + +#define TEE_SMC_FUNCID_LOAD_VIDEO_FW 15 +#define TEE_SMC_LOAD_VIDEO_FW \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_TRUSTED_OS, TEE_SMC_FUNCID_LOAD_VIDEO_FW) + +#define PTA_LOAD_FW UUID_INIT(0x526fc4fc, 0x7ee6, 0x4a12, \ + 0x96, 0xe3, 0x83, 0xda, 0x95, 0x65, 0xbc, 0xe8) + +#define TEE_PARAM_NUM 4 + +static struct aml_tee_fw firmware[] = { + [CODEC_TYPE_H264] = { + .fw_format = VIDEO_DEC_H264_MULTI, + .core = CORE_VDEC_LEGENCY, + .is_swap = 1, + }, +}; + +static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) +{ + return (ver->impl_id == TEE_IMPL_ID_OPTEE); +} + +static void prepare_tee_grgs(size_t firmware_size, struct tee_param *param0, + struct tee_param *param1) +{ + memset(param0, 0, TEE_PARAM_NUM * sizeof(*param0)); + memset(param1, 0, TEE_PARAM_NUM * sizeof(*param1)); + + param0[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; + param0[0].u.value.a = firmware_size; + + param0[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; + + param0[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + param0[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + + param1[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; + param1[0].u.memref.size = firmware_size; + + param1[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + param1[2].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; + param1[3].attr = TEE_IOCTL_PARAM_ATTR_TYPE_NONE; +} + +static int tee_pta_invoke_cmd(struct aml_vdec_hw *hw, struct tee_context *ctx, + uuid_t uuid, u32 cmd, void *firmware_data, + struct tee_param *param_init, + struct tee_param *param_invoke) +{ + int ret = 0; + struct tee_ioctl_open_session_arg sess_arg = { 0 }; + struct tee_ioctl_invoke_arg inv_arg = { 0 }; + u32 sent_size = 0; + u32 fw_size = 0; + struct tee_shm *shm = NULL; + void *shm_va = NULL; + + fw_size = param_invoke[0].u.memref.size; + + shm = tee_shm_alloc_kernel_buf(ctx, ONCE_SENT_SIZE); + if (IS_ERR(shm)) { + dev_info(hw->dev, "Failed to allocate shared memory size %d\n", + ONCE_SENT_SIZE); + ret = PTR_ERR(shm); + goto out; + } + + shm_va = tee_shm_get_va(shm, 0); + if (IS_ERR(shm_va)) { + dev_info(hw->dev, "Failed to get VA for shared memory\n"); + ret = PTR_ERR(shm_va); + goto free_shm; + } + + /* Open session */ + memcpy(sess_arg.uuid, uuid.b, TEE_IOCTL_UUID_LEN); + sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC; + sess_arg.num_params = TEE_PARAM_NUM; + ret = tee_client_open_session(ctx, &sess_arg, param_init); + if (ret < 0 || sess_arg.ret != TEEC_SUCCESS) { + dev_info(hw->dev, + "%s open session failed, cmd = %u, ret = %d, res = 0x%x, origin = 0x%x\n", + __func__, cmd, ret, sess_arg.ret, sess_arg.ret_origin); + ret = sess_arg.ret; + goto free_shm; + } + + inv_arg.func = cmd; + inv_arg.session = sess_arg.session; + inv_arg.num_params = TEE_PARAM_NUM; + + while (sent_size < fw_size) { + memset(shm_va, 0, ONCE_SENT_SIZE); + if (fw_size - sent_size > ONCE_SENT_SIZE) { + memcpy(shm_va, (firmware_data + sent_size), + ONCE_SENT_SIZE); + param_invoke[0].u.memref.size = ONCE_SENT_SIZE; + } else { + memcpy(shm_va, (firmware_data + sent_size), + fw_size - sent_size); + param_invoke[0].u.memref.size = (fw_size - sent_size); + } + param_invoke[0].u.memref.shm = shm; + ret = tee_client_invoke_func(ctx, &inv_arg, param_invoke); + if (ret < 0 || (inv_arg.ret != TEEC_SUCCESS && inv_arg.ret != TEEC_ERROR_BUSY)) { + dev_info(hw->dev, + "%s invoke func failed, cmd = %u, ret= %d, res = 0x%x, origin = 0x%x\n", + __func__, cmd, ret, inv_arg.ret, + inv_arg.ret_origin); + ret = inv_arg.ret; + goto close_session; + } + sent_size += param_invoke[0].u.memref.size; + } +close_session: + tee_client_close_session(ctx, sess_arg.session); +free_shm: + tee_shm_free(shm); +out: + return ret; +} + +int load_firmware(struct aml_vdec_hw *hw, u32 type) +{ + int ret = -1; + struct aml_tee_fw *video_fw; + + if (type >= CODEC_TYPE_FRAME) { + dev_info(hw->dev, "codec type %d invalid\n", type); + return ret; + } + video_fw = &firmware[type]; + + meson_sm_call(hw->sec_fw, SM_LOAD_VIDEO_FW, &ret, + video_fw->fw_format, video_fw->core, + video_fw->is_swap, 0, 0); + if (ret < 0) + dev_err(hw->dev, "loading fw type %d core %d, ret %x\n", + video_fw->fw_format, video_fw->core, ret); + + return ret; +} + +static int get_firmware(const char *path, void **data, size_t *size) +{ + const struct firmware *fw = NULL; + int ret; + void *buf; + + ret = request_firmware(&fw, FIRMWARE_PATH, NULL); + if (ret) + return ret; + + /* get rid of the first 32K bytes plaintext */ + buf = kzalloc((fw->size - UCODE_HEADER_SIZE), GFP_KERNEL); + if (!buf) { + release_firmware(fw); + return -ENOMEM; + } + + memcpy(buf, fw->data + UCODE_HEADER_SIZE, fw->size - UCODE_HEADER_SIZE); + release_firmware(fw); + + *data = buf; + *size = fw->size - UCODE_HEADER_SIZE; + + return 0; +} + +static int pass_firmware_to_tee(struct aml_vdec_hw *hw) +{ + int ret; + struct tee_context *ctx = NULL; + uuid_t uuid = PTA_LOAD_FW; + struct tee_param param_init[TEE_PARAM_NUM]; + struct tee_param param_invoke[TEE_PARAM_NUM]; + void *firmware_data; + size_t firmware_size; + + ret = get_firmware(FIRMWARE_PATH, &firmware_data, &firmware_size); + if (ret) { + dev_info(hw->dev, "Failed get firmware %s from FS\n", + FIRMWARE_PATH); + return ret; + } + + ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL); + if (IS_ERR(ctx)) { + dev_info(hw->dev, "Failed to open TEE context\n"); + ret = PTR_ERR(ctx); + goto free_firmware; + } + + prepare_tee_grgs(firmware_size, param_init, param_invoke); + + ret = tee_pta_invoke_cmd(hw, ctx, uuid, FIRMWARE_CMD_PROCESS, + firmware_data, param_init, param_invoke); + if (ret) + dev_info(hw->dev, "TEE firmware processing failed, ret = %d\n", + ret); + + tee_client_close_context(ctx); +free_firmware: + kfree(firmware_data); + return ret; +} + +int aml_tee_fw_preload(struct aml_vdec_hw *hw) +{ + int ret; + + ret = pass_firmware_to_tee(hw); + if (ret) + dev_err(hw->dev, "Failed to preload firmware via TEE\n"); + + return ret; +} diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.h b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.h new file mode 100644 index 000000000000..04b47e8f8654 --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_tee_fw.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#ifndef AML_VDEC_TEE_FW_H_ +#define AML_VDEC_TEE_FW_H_ + +#include "aml_vdec_hw.h" + +/** + * struct aml_tee_fw - specify the firmware format for each dec type + * @fw_format: Specify firmware format for current decoder. + * @core: Specify which hardware core is needed. + * @is_swap: Specify if the swap memory is needed. + */ +struct aml_tee_fw { + u32 fw_format; + u32 core; + u32 is_swap; +}; + +int aml_tee_fw_preload(struct aml_vdec_hw *hw); +int load_firmware(struct aml_vdec_hw *hw, u32 type); + +#endif + diff --git a/drivers/media/platform/amlogic/vdec/h264.c b/drivers/media/platform/amlogic/vdec/h264.c new file mode 100644 index 000000000000..bd3aef44409f --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/h264.c @@ -0,0 +1,2128 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#include +#include +#include "aml_vdec.h" +#include "aml_vdec_hw.h" +#include "h264.h" + +#define INVALID_POC 0xffffffff + +#define H264_SLICE_HEADER_DONE 0x1 +#define H264_SLICE_DATA_DONE 0x2 + +#define H264_MAX_COL_BUF 32 +#define H264_MAX_CANVAS_POS 26 + +#define DECODER_TIMEOUT_MS 500 + +#define COL_BUFFER_MARGIN 2 +#define COL_SIZE_FOR_ONE_MB 96 + +struct vdec_h264_stateless_ctrl_ref { + const struct v4l2_ctrl_h264_decode_params *decode; + const struct v4l2_ctrl_h264_scaling_matrix *scaling; + const struct v4l2_ctrl_h264_sps *sps; + const struct v4l2_ctrl_h264_pps *pps; +}; + +enum SliceType { + P_SLICE = 0, + B_SLICE = 1, + I_SLICE = 2, + SP_SLICE = 3, + SI_SLICE = 4, + MAX_SLICE_TYPES = 5 +}; + +#define I_Slice 2 +#define P_Slice 5 +#define B_Slice 6 +#define P_Slice_0 0 +#define B_Slice_1 1 +#define I_Slice_7 7 + +/* Used by firmware */ +union param { + struct { + unsigned short data[RPM_END - RPM_BEGIN]; + } l; + struct { + unsigned short dump[DPB_OFFSET]; + unsigned short dpb_base[FRAME_IN_DPB << 3]; + + unsigned short dpb_max_buffer_frame; + unsigned short actual_dpb_size; + + unsigned short colocated_buf_status; + + unsigned short num_forward_short_term_reference_pic; + unsigned short num_short_term_reference_pic; + unsigned short num_reference_pic; + + unsigned short current_dpb_index; + unsigned short current_decoded_frame_num; + unsigned short current_reference_frame_num; + + unsigned short l0_size; + unsigned short l1_size; + /** + * [6:5] : nal_ref_idc + * [4:0] : nal_unit_type + */ + unsigned short NAL_info_mmco; + /** + * [1:0] : 00 - top field, 01 - bottom field, + * 10 - frame, 11 - mbaff frame + */ + unsigned short picture_structure_mmco; + unsigned short frame_num; + unsigned short pic_order_cnt_lsb; + + unsigned short num_ref_idx_l0_active_minus1; + unsigned short num_ref_idx_l1_active_minus1; + + unsigned short PrevPicOrderCntLsb; + unsigned short PreviousFrameNum; + + /* 32 bits variables */ + unsigned short delta_pic_order_cnt_bottom[2]; + unsigned short delta_pic_order_cnt_0[2]; + unsigned short delta_pic_order_cnt_1[2]; + + unsigned short PrevPicOrderCntMsb[2]; + unsigned short PrevFrameNumOffset[2]; + + unsigned short frame_pic_order_cnt[2]; + unsigned short top_field_pic_order_cnt[2]; + unsigned short bottom_field_pic_order_cnt[2]; + + unsigned short colocated_mv_addr_start[2]; + unsigned short colocated_mv_addr_end[2]; + unsigned short colocated_mv_wr_addr[2]; + } dpb; + struct { + unsigned short dump[MMCO_OFFSET]; + + /* array base address for offset_for_ref_frame */ + unsigned short offset_for_ref_frame_base[128]; + + /** + * 0 - Index in DPB + * 1 - Picture Flag + * [2] : 0 - short term reference, + * 1 - long term reference + * [1] : bottom field + * [0] : top field + * 2 - Picture Number (short term or long term) low 16 bits + * 3 - Picture Number (short term or long term) high 16 bits + */ + unsigned short reference_base[128]; + + /* command and parameter, until command is 3 */ + unsigned short l0_reorder_cmd[REORDER_CMD_MAX]; + unsigned short l1_reorder_cmd[REORDER_CMD_MAX]; + + /* command and parameter, until command is 0 */ + unsigned short mmco_cmd[44]; + + unsigned short l0_base[40]; + unsigned short l1_base[40]; + } mmco; + struct { + /* from ucode lmem, do not change this struct */ + } p; +}; + +struct h264_canvas { + u32 canvas_pos; + int poc; +}; + +struct h264_decode_buf_spec { + struct v4l2_h264_dpb_entry *dpb; + u32 canvas_pos; + u32 dpb_index; + int poc; + int col_buf_index; + u8 y_canvas_index; + u8 u_canvas_index; + u8 v_canvas_index; + u8 used; + u8 long_term_flag; + dma_addr_t y_dma_addr; + dma_addr_t c_dma_addr; +}; + +#define REORDERING_COMMAND_MAX_SIZE 33 +struct slice { + int frame_num; + /*modification */ + int slice_type; + int num_ref_idx_l0; + int num_ref_idx_l1; + int first_mb_in_slice; + int ref_pic_list_reordering_flag[2]; + int modification_of_pic_nums_idc[2][REORDERING_COMMAND_MAX_SIZE]; + int abs_diff_pic_num_minus1[2][REORDERING_COMMAND_MAX_SIZE]; + int long_term_pic_idx[2][REORDERING_COMMAND_MAX_SIZE]; + unsigned char dec_ref_pic_marking_buffer_valid; +}; + +struct aml_h264_ctx { + struct aml_vdec_ctx *v4l2_ctx; + u8 init_flag; + u8 new_pic_flag; + u8 mc_cpu_loaded; + u8 param_set; + u8 colocated_buf_num; + u8 reg_iqidct_control_init_flag; + u32 reg_iqidct_control; + u32 reg_vcop_ctrl_reg; + u32 reg_rv_ai_mb_count; + u32 vld_dec_control; + u32 save_avscratch_f; + u32 seq_info; + u32 decode_pic_count; + union param dpb_param; + u32 dec_status; + struct slice mslice; + struct h264_decode_buf_spec curr_spec; + struct h264_decode_buf_spec ref_list0[V4L2_H264_NUM_DPB_ENTRIES + 1]; + struct h264_decode_buf_spec ref_list1[V4L2_H264_NUM_DPB_ENTRIES + 1]; + struct h264_decode_buf_spec ref_list0_unreordered[V4L2_H264_NUM_DPB_ENTRIES + 1]; + struct h264_decode_buf_spec ref_list1_unreordered[V4L2_H264_NUM_DPB_ENTRIES + 1]; + u8 list_size[2]; + u32 canvas_pos_map; + struct h264_canvas ref_canvas[V4L2_H264_NUM_DPB_ENTRIES + 1]; + dma_addr_t lmem_phy_addr; + void *lmem_addr; + dma_addr_t mc_cpu_paddr; + void *mc_cpu_vaddr; + dma_addr_t cma_alloc_addr; + void *cma_alloc_vaddr; + dma_addr_t collated_cma_addr; + dma_addr_t collated_cma_addr_end; + void *collated_cma_vaddr; + dma_addr_t workspace_offset; + void *workspace_vaddr; + u32 col_buf_alloc_size; + u32 one_col_buf_size; + u32 colocated_buf_map; + int colocated_buf_poc[H264_MAX_COL_BUF]; + + u32 frame_width; + u32 frame_height; + u32 mb_width; + u32 mb_height; + u32 mb_total; + u32 max_num_ref_frames; + + struct vdec_h264_stateless_ctrl_ref ctrl_ref; +}; + +static inline int get_flag(u32 flag, u32 mask) +{ + return (flag & mask) ? 1 : 0; +} + +static inline void write_lmem(unsigned short *base, u32 offset, u32 value) +{ + base[offset] = value; +} + +static inline uint32_t spec2canvas(struct h264_decode_buf_spec *buf_spec) +{ + return (buf_spec->v_canvas_index << 16) | + (buf_spec->u_canvas_index << 8) | + (buf_spec->y_canvas_index << 0); +} + +static struct h264_decode_buf_spec *find_spec_by_dpb_index(struct aml_h264_ctx + *h264_ctx, int index, int list) +{ + int i; + int size; + struct h264_decode_buf_spec *ref_list; + + size = h264_ctx->list_size[list]; + if (list == 0) + ref_list = &h264_ctx->ref_list0[0]; + else + ref_list = &h264_ctx->ref_list1[0]; + + for (i = 0; i < size; i++) { + if (index == ref_list[i].dpb_index) + return &ref_list[i]; + } + + return NULL; +} + +static int h264_prepare_input(struct aml_vdec_ctx *ctx) +{ + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + struct vb2_v4l2_buffer *src; + struct vb2_buffer *vb; + dma_addr_t src_dma; + u32 payload_size; + int dummy; + + src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + if (!src) { + dev_info(hw->dev, "no input buffer available!\n"); + return -1; + } + vb = &src->vb2_buf; + payload_size = vb2_get_plane_payload(vb, 0); + src_dma = vb2_dma_contig_plane_dma_addr(vb, 0); + + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, 0); + /* reset VLD fifo for all vdec */ + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, + (1 << 5) | (1 << 4) | (1 << 3)); + regmap_write(hw->map[DOS_BUS], DOS_SW_RESET0, 0); + regmap_write(hw->map[DOS_BUS], POWER_CTL_VLD, 1 << 4); + + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_START_PTR, src_dma); + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_END_PTR, + (src_dma + payload_size)); + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CURR_PTR, + round_down(src_dma, VDEC_FIFO_ALIGN)); + + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, 1); + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, 0); + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_BUF_CNTL, 2); + + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_RP, + round_down(src_dma, VDEC_FIFO_ALIGN)); + dummy = payload_size + VLD_PADDING_SIZE; + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_WP, + round_down((src_dma + dummy), VDEC_FIFO_ALIGN)); + + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_BUF_CNTL, 3); + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_BUF_CNTL, 2); + + regmap_write(hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, + (0x11 << 16) | (1 << 10) | (7 << 3)); + + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_1, 0x0); + regmap_write(hw->map[DOS_BUS], H264_DECODE_INFO, (1 << 13)); + regmap_write(hw->map[DOS_BUS], H264_DECODE_SIZE, payload_size); + regmap_write(hw->map[DOS_BUS], VIFF_BIT_CNT, payload_size * 8); + + return 0; +} + +static void config_sps_params(struct aml_h264_ctx *h264_ctx, + unsigned short *sps_base, + const struct v4l2_ctrl_h264_sps *sps) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + u32 cfg_tmp = 0; + u32 frame_size; + u32 offset = 0; + unsigned short data_tmp[0x100]; + int i, ii; + + memset(sps_base, 0, 0x100); + + h264_ctx->frame_width = (sps->pic_width_in_mbs_minus1 + 1) << 4; + h264_ctx->frame_height = (sps->pic_height_in_map_units_minus1 + 1) << 4; + + data_tmp[offset] = PARAM_BASE_VAL; + offset += 2; + + data_tmp[offset++] = GET_SPS_PROFILE_IDC(sps->profile_idc); + + data_tmp[offset++] = GET_SPS_SEQ_PARAM_SET_ID(sps->seq_parameter_set_id) | + GET_SPS_LEVEL_IDC(sps->level_idc); + + if (sps->profile_idc >= 100) { + data_tmp[offset++] = GET_SPS_CHROMA_FORMAT_IDC(sps->chroma_format_idc); + + data_tmp[offset++] = ((sps->chroma_format_idc ^ 1) << 1); + } + + data_tmp[offset++] = GET_SPS_LOG2_MAX_FRAME_NUM(sps->log2_max_frame_num_minus4); + data_tmp[offset++] = GET_SPS_PIC_ORDER_TYPE(sps->pic_order_cnt_type); + + if (sps->pic_order_cnt_type == 0) { + data_tmp[offset++] = + GET_SPS_PIC_ORDER_CNT_LSB(sps->log2_max_pic_order_cnt_lsb_minus4); + } else if (sps->pic_order_cnt_type == 1) { + data_tmp[offset++] = + get_flag(sps->flags, + V4L2_H264_SPS_FLAG_DELTA_PIC_ORDER_ALWAYS_ZERO); + data_tmp[offset++] = + GET_SPS_OFFSET_FOR_NONREF_PIC_LOW(sps->offset_for_non_ref_pic); + data_tmp[offset++] = + GET_SPS_OFFSET_FOR_NONREF_PIC_HIGH(sps->offset_for_non_ref_pic); + data_tmp[offset++] = + GET_SPS_OFFSET_FOR_TOP_BOT_FIELD_LOW(sps->offset_for_top_to_bottom_field); + data_tmp[offset++] = + GET_SPS_OFFSET_FOR_TOP_BOT_FIELD_HIGH(sps->offset_for_top_to_bottom_field); + data_tmp[offset++] = sps->num_ref_frames_in_pic_order_cnt_cycle; + } + + data_tmp[offset++] = GET_SPS_NUM_REF_FRAMES(sps->max_num_ref_frames) | + GET_SPS_GAPS_ALLOWED_FLAG(get_flag(sps->flags, + V4L2_H264_SPS_FLAG_GAPS_IN_FRAME_NUM_VALUE_ALLOWED)); + + data_tmp[offset++] = GET_SPS_PIC_WIDTH_IN_MBS(sps->pic_width_in_mbs_minus1); + + data_tmp[offset++] = GET_SPS_PIC_HEIGHT_IN_MBS(sps->pic_height_in_map_units_minus1); + data_tmp[offset++] = + GET_SPS_DIRECT_8X8_FLAGS + (get_flag(sps->flags, + V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE)) | + GET_SPS_MB_ADAPTIVE_FRAME_FIELD_FLAGS + (get_flag(sps->flags, + V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD)) | + GET_SPS_FRAME_MBS_ONLY_FLAGS(get_flag(sps->flags, + V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY)); + + for (i = 0; i < 0x100; i += 4) { + for (ii = 0; ii < 4; ii++) + sps_base[i + 3 - ii] = data_tmp[i + ii]; + } + + frame_size = (sps->pic_width_in_mbs_minus1 + 1) * (sps->pic_height_in_map_units_minus1 + 1); + cfg_tmp = (get_flag(sps->flags, V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY) << 31) | + (sps->max_num_ref_frames << 24) | (frame_size << 8) | + (sps->pic_width_in_mbs_minus1 + 1); + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_1, cfg_tmp); + h264_ctx->seq_info = cfg_tmp; + + cfg_tmp = 0; + cfg_tmp = (get_flag(sps->flags, V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE) << 15) | + (sps->chroma_format_idc); + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_2, cfg_tmp); + + cfg_tmp = 0; + cfg_tmp = (sps->max_num_ref_frames << 8) | (sps->level_idc); + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_B, cfg_tmp); + + cfg_tmp = ((sps->level_idc & 0xff) << 7) | + (get_flag(sps->flags, V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY) << 2); + regmap_write(hw->map[DOS_BUS], NAL_SEARCH_CTL, + read_dos_reg(hw, NAL_SEARCH_CTL) | cfg_tmp); + + h264_ctx->mb_width = (sps->pic_width_in_mbs_minus1 + 4) & 0xfffffffc; + h264_ctx->mb_height = (sps->pic_height_in_map_units_minus1 + 4) & 0xfffffffc; + h264_ctx->mb_total = h264_ctx->mb_width * h264_ctx->mb_height; + h264_ctx->max_num_ref_frames = sps->max_num_ref_frames; +} + +static void config_pps_params(struct aml_h264_ctx *h264_ctx, + unsigned short *pps_base, + const struct v4l2_ctrl_h264_pps *pps) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + u32 offset = 0; + unsigned short data_tmp[0x100]; + u32 max_reference_size = V4L2_H264_NUM_DPB_ENTRIES; + u32 max_list_size; + int i, ii; + + memset(pps_base, 0, 0x100); + + data_tmp[offset++] = PARAM_BASE_VAL; + + data_tmp[offset++] = + GET_PPS_PIC_PARAM_SET_ID(pps->pic_parameter_set_id) | + GET_PPS_SEQ_PARAM_SET_ID(pps->seq_parameter_set_id) | + GET_PPS_ENTROPY_CODING_MODE_FLAG + (get_flag(pps->flags, + V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE)) | + GET_PPS_PIC_ORDER_PRESENT_FLAG + (get_flag(pps->flags, + V4L2_H264_PPS_FLAG_BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT)); + + data_tmp[offset++] = + GET_PPS_WEIGHTED_BIPRED_IDC(pps->weighted_bipred_idc) | + GET_PPS_WEIGHTED_PRED_FLAG(get_flag(pps->flags, + V4L2_H264_PPS_FLAG_WEIGHTED_PRED)) | + GET_PPS_NUM_IDX_REF_L1_MINUS1(pps->num_ref_idx_l1_default_active_minus1) | + GET_PPS_NUM_IDX_REF_L0_MINUS1(pps->num_ref_idx_l0_default_active_minus1); + + data_tmp[offset++] = GET_PPS_INIT_QS_MINUS26(pps->pic_init_qs_minus26) | + GET_PPS_INIT_QP_MINUS26(pps->pic_init_qp_minus26); + + data_tmp[offset] = + GET_PPS_CHROMA_QP_INDEX_OFFSET(pps->chroma_qp_index_offset) | + GET_PPS_DEBLOCK_FILTER_CTRL_PRESENT_FLAG + (get_flag(pps->flags, + V4L2_H264_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT)) | + GET_PPS_CONSTRAIN_INTRA_PRED_FLAG + (get_flag(pps->flags, + V4L2_H264_PPS_FLAG_CONSTRAINED_INTRA_PRED)) | + GET_PPS_REDUNDANT_PIC_CNT_PRESENT_FLAG + (get_flag(pps->flags, + V4L2_H264_PPS_FLAG_REDUNDANT_PIC_CNT_PRESENT)); + if (get_flag(pps->flags, V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE | + V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT)) + data_tmp[offset] |= (1 << 11); + offset++; + + data_tmp[offset++] = + GET_PPS_SCALING_MATRIX_PRESENT_FLAG(get_flag + (pps->flags, + V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT)) | + GET_PPS_TRANSFORM_8X8_FLAG(get_flag(pps->flags, + V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE)); + + data_tmp[offset++] = + GET_PPS_GET_SECOND_CHROMA_QP_OFFSET(pps->second_chroma_qp_index_offset); + + max_list_size = (pps->num_ref_idx_l1_default_active_minus1 + 1) + + (pps->num_ref_idx_l0_default_active_minus1 + 1); + + h264_ctx->max_num_ref_frames = max_list_size > h264_ctx->max_num_ref_frames ? + max_list_size : h264_ctx->max_num_ref_frames; + + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_0, + ((h264_ctx->max_num_ref_frames + 1) << 24) | + (max_reference_size << 16) | (max_reference_size << 8)); + + for (i = 0; i < 0x100; i += 4) { + for (ii = 0; ii < 4; ii++) + pps_base[i + 3 - ii] = data_tmp[i + ii]; + } +} + +static void h264_config_params(struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv; + unsigned short *p_sps_base, *p_pps_base; + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + const struct v4l2_ctrl_h264_sps *sps = ctrls->sps; + const struct v4l2_ctrl_h264_pps *pps = ctrls->pps; + + p_sps_base = (unsigned short *)(h264_ctx->workspace_vaddr + + MEM_SPS_BASE + sps->seq_parameter_set_id * 0x400); + p_pps_base = (unsigned short *)(h264_ctx->workspace_vaddr + + MEM_PPS_BASE + pps->pic_parameter_set_id * 0x200); + + dev_dbg(&ctx->dev->plat_dev->dev, "%s sps id %d, pps id %d\n", + __func__, sps->seq_parameter_set_id, pps->pic_parameter_set_id); + + config_sps_params(h264_ctx, p_sps_base, sps); + config_pps_params(h264_ctx, p_pps_base, pps); +} + +static void config_decode_canvas(struct aml_vdec_hw *hw, + struct h264_decode_buf_spec *buf_spec, + u32 mb_width, u32 mb_height) +{ + int canvas_alloc_result = 0; + int blkmode = 0x0; + + canvas_alloc_result = meson_canvas_alloc(hw->canvas, &buf_spec->y_canvas_index); + canvas_alloc_result = meson_canvas_alloc(hw->canvas, &buf_spec->u_canvas_index); + buf_spec->v_canvas_index = buf_spec->u_canvas_index; + + if (!canvas_alloc_result) { + /* config y canvas */ + meson_canvas_config(hw->canvas, + buf_spec->y_canvas_index, buf_spec->y_dma_addr, + mb_width << 4, mb_height << 4, + MESON_CANVAS_WRAP_NONE, MESON_CANVAS_BLKMODE_LINEAR, + MESON_CANVAS_ENDIAN_SWAP64); + regmap_write(hw->map[DOS_BUS], VDEC_ASSIST_CANVAS_BLK32, + (1 << 11) | /* canvas_blk32_wr */ + (blkmode << 10) | /* canvas_blk32 */ + (1 << 8) | /* canvas_index_wr */ + (buf_spec->y_canvas_index << 0) /* canvas index */ + ); + + /* config uv canvas */ + meson_canvas_config(hw->canvas, + buf_spec->u_canvas_index, buf_spec->c_dma_addr, + mb_width << 4, mb_height << 3, + MESON_CANVAS_WRAP_NONE, MESON_CANVAS_BLKMODE_LINEAR, + MESON_CANVAS_ENDIAN_SWAP64); + regmap_write(hw->map[DOS_BUS], VDEC_ASSIST_CANVAS_BLK32, + (1 << 11) | /* canvas_blk32_wr */ + (blkmode << 10) | /* canvas_blk32 */ + (1 << 8) | /* canvas_index_wr */ + (buf_spec->u_canvas_index << 0) /* canvas index */ + ); + + regmap_write(hw->map[DOS_BUS], ANC0_CANVAS_ADDR + (buf_spec->canvas_pos << 2), + spec2canvas(buf_spec)); + } +} + +static int allocate_colocate_buf(struct aml_h264_ctx *h264_ctx, int poc) +{ + int i; + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + + for (i = 0; i < h264_ctx->colocated_buf_num; i++) { + if (((h264_ctx->colocated_buf_map >> i) & 0x1) == 0) { + h264_ctx->colocated_buf_map |= (1 << i); + break; + } + } + + if (i == h264_ctx->colocated_buf_num) + return -1; + + h264_ctx->colocated_buf_poc[i] = poc; + dev_dbg(&ctx->dev->plat_dev->dev, "%s colocated_buf_index %d poc %d\n", + __func__, i, h264_ctx->colocated_buf_poc[i]); + + return i; +} + +static void release_colocate_buf(struct aml_h264_ctx *h264_ctx, int index) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + + if (index >= 0) { + if (index >= h264_ctx->colocated_buf_num) { + dev_dbg + (&ctx->dev->plat_dev->dev, + "%s error, index %d is bigger than buf count %d\n", + __func__, index, h264_ctx->max_num_ref_frames); + } else { + if (h264_ctx->colocated_buf_poc[index] != INVALID_POC && + ((h264_ctx->colocated_buf_map >> index) & 0x1) == 0x1) { + h264_ctx->colocated_buf_map &= (~(1 << index)); + dev_dbg + (&ctx->dev->plat_dev->dev, + "%s colocated_buf_index %d released poc %d\n", + __func__, index, + h264_ctx->colocated_buf_poc[index]); + } + h264_ctx->colocated_buf_poc[index] = INVALID_POC; + } + } +} + +static int get_col_buf_index_by_poc(struct aml_h264_ctx *h264_ctx, int poc) +{ + int idx; + + for (idx = 0; idx < h264_ctx->colocated_buf_num; idx++) { + if (h264_ctx->colocated_buf_poc[idx] == poc) + break; + } + + if (idx == h264_ctx->colocated_buf_num) + idx = -1; + + return idx; +} + +static int alloc_colocate_cma(struct aml_h264_ctx *h264_ctx, + struct aml_vdec_ctx *ctx) +{ + int alloc_size = 0; + int i; + struct aml_vdec_hw *hw; + + if (h264_ctx->collated_cma_vaddr) + return 0; + + hw = vdec_get_hw(ctx->dev); + if (!hw) + return -1; + + /* 96 :col buf size for each mb */ + h264_ctx->one_col_buf_size = h264_ctx->mb_total * 96; + alloc_size = PAGE_ALIGN(h264_ctx->one_col_buf_size * + (h264_ctx->max_num_ref_frames + COL_BUFFER_MARGIN)); + h264_ctx->collated_cma_vaddr = dma_alloc_coherent(hw->dev, alloc_size, + &h264_ctx->collated_cma_addr, GFP_KERNEL); + if (!h264_ctx->collated_cma_vaddr) + return -ENOMEM; + + dev_dbg + (&ctx->dev->plat_dev->dev, + "collated_cma_addr = %pad, one_col_buf_size = %x alloc_size = %x\n", + &h264_ctx->collated_cma_addr, h264_ctx->one_col_buf_size, + alloc_size); + h264_ctx->collated_cma_addr_end = + h264_ctx->collated_cma_addr + alloc_size; + memset(h264_ctx->collated_cma_vaddr, 0, alloc_size); + h264_ctx->col_buf_alloc_size = alloc_size; + h264_ctx->colocated_buf_map = 0; + h264_ctx->colocated_buf_num = h264_ctx->max_num_ref_frames + COL_BUFFER_MARGIN; + + for (i = 0; i < H264_MAX_COL_BUF; i++) + h264_ctx->colocated_buf_poc[i] = INVALID_POC; + + return 0; +} + +static void config_p_reflist(struct aml_h264_ctx *h264_ctx, + struct v4l2_h264_reference *v4l2_p0_reflist, + u32 list_size) +{ + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + struct v4l2_ctrl_h264_decode_params *decode = + (struct v4l2_ctrl_h264_decode_params *)ctrls->decode; + struct v4l2_h264_dpb_entry *dpb = decode->dpb; + u8 index; + int i; + + for (i = 0; i < list_size; i++) { + index = v4l2_p0_reflist[i].index; + h264_ctx->ref_list0[i].used = 1; + h264_ctx->ref_list0[i].dpb = &dpb[index]; + h264_ctx->ref_list0[i].poc = dpb[index].top_field_order_cnt; + h264_ctx->ref_list0[i].long_term_flag = + dpb[index].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM ? true : false; + h264_ctx->ref_list0[i].dpb_index = index; + } + h264_ctx->list_size[0] = list_size; +} + +static void config_b_reflist(struct aml_h264_ctx *h264_ctx, + struct v4l2_h264_reference *v4l2_b0_reflist, + struct v4l2_h264_reference *v4l2_b1_reflist, + u32 list_size) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + struct v4l2_ctrl_h264_decode_params *decode = + (struct v4l2_ctrl_h264_decode_params *)ctrls->decode; + struct v4l2_h264_dpb_entry *dpb = decode->dpb; + u8 index; + int i, j; + + h264_ctx->list_size[0] = list_size; + for (i = 0; i < list_size; i++) { + index = v4l2_b0_reflist[i].index; + h264_ctx->ref_list0[i].used = 1; + h264_ctx->ref_list0[i].dpb = &dpb[index]; + h264_ctx->ref_list0[i].poc = dpb[index].top_field_order_cnt; + h264_ctx->ref_list0[i].long_term_flag = + dpb[index].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM ? true : false; + h264_ctx->ref_list0[i].col_buf_index = + get_col_buf_index_by_poc(h264_ctx, dpb[index].top_field_order_cnt); + h264_ctx->ref_list0[i].dpb_index = index; + } + + h264_ctx->list_size[1] = list_size; + for (j = 0; j < list_size; j++) { + index = v4l2_b1_reflist[j].index; + h264_ctx->ref_list1[j].used = 1; + h264_ctx->ref_list1[j].dpb = &dpb[index]; + h264_ctx->ref_list1[j].poc = dpb[index].top_field_order_cnt; + h264_ctx->ref_list1[j].long_term_flag = + dpb[index].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM ? true : false; + h264_ctx->ref_list1[j].col_buf_index = + get_col_buf_index_by_poc(h264_ctx, dpb[index].top_field_order_cnt); + h264_ctx->ref_list1[j].dpb_index = index; + } + + if ((h264_ctx->list_size[1] + h264_ctx->list_size[0]) < list_size) + dev_info(&ctx->dev->plat_dev->dev, "ref list incorrect list0 %d list0 %d list_size%d\n", + h264_ctx->list_size[0], h264_ctx->list_size[1], list_size); +} + +static int poc_is_in_dpb(int poc, const struct v4l2_h264_dpb_entry *dpb) +{ + int i; + int ret = 0; + + for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) { + if (poc == dpb[i].top_field_order_cnt) { + ret = 1; + break; + } + } + + return ret; +} + +static int get_ref_list_size(struct aml_h264_ctx *h264_ctx, int cur_list) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + unsigned short override_flag = h264_ctx->dpb_param.l.data[REF_IDC_OVERRIDE_FLAG]; + int num_ref_idx_lx_active_minus1; + + if (cur_list == 0) { + num_ref_idx_lx_active_minus1 = + h264_ctx->ctrl_ref.pps->num_ref_idx_l0_default_active_minus1; + if (override_flag) + num_ref_idx_lx_active_minus1 = + h264_ctx->dpb_param.dpb.num_ref_idx_l0_active_minus1; + } else { + num_ref_idx_lx_active_minus1 = + h264_ctx->ctrl_ref.pps->num_ref_idx_l1_default_active_minus1; + } + dev_dbg(&ctx->dev->plat_dev->dev, "%s get list %d size %d\n", + __func__, cur_list, num_ref_idx_lx_active_minus1 + 1); + + return num_ref_idx_lx_active_minus1 + 1; +} + +static int get_refidx_by_picnum(struct aml_h264_ctx *h264_ctx, int pic_num, + int curr_list) +{ + int i; + struct h264_decode_buf_spec *ref_list; + + if (curr_list == 0) + ref_list = &h264_ctx->ref_list0[0]; + else + ref_list = &h264_ctx->ref_list1[0]; + + for (i = 0; ref_list[i].dpb; i++) { + if (pic_num == ref_list[i].dpb->pic_num) + return i; + } + + return -1; +} + +static struct h264_decode_buf_spec *get_st_refpic_by_num(struct aml_h264_ctx *h264_ctx, + int pic_num, int curr_list) +{ + int i; + struct h264_decode_buf_spec *ref_list; + + if (curr_list == 0) + ref_list = &h264_ctx->ref_list0_unreordered[0]; + else + ref_list = &h264_ctx->ref_list1_unreordered[0]; + + for (i = 0; ref_list[i].dpb; i++) { + if (pic_num == ref_list[i].dpb->pic_num && ref_list[i].long_term_flag == 0) + return &ref_list[i]; + } + + return NULL; +} + +static struct h264_decode_buf_spec *get_lt_refpic_by_num(struct aml_h264_ctx *h264_ctx, + int pic_num, int curr_list) +{ + int i; + struct h264_decode_buf_spec *ref_list; + + if (curr_list == 0) + ref_list = &h264_ctx->ref_list0_unreordered[0]; + else + ref_list = &h264_ctx->ref_list1_unreordered[0]; + + for (i = 0; ref_list[i].dpb; i++) { + if (pic_num == ref_list[i].dpb->pic_num && ref_list[i].long_term_flag == 1) + return &ref_list[i]; + } + + return NULL; +} + +static void reorder_short_term(struct slice *curr_slice, int cur_list, + int pic_num_lx, int *ref_idx_lx) +{ + struct aml_h264_ctx *h264_ctx = + container_of(curr_slice, struct aml_h264_ctx, mslice); + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + int c_idx, n_idx; + int num_ref_idx_lx_active; + struct h264_decode_buf_spec *pic_lx = NULL; + struct h264_decode_buf_spec *ref_list_reordered; + + if (cur_list == 0) + ref_list_reordered = &h264_ctx->ref_list0[0]; + else + ref_list_reordered = &h264_ctx->ref_list1[0]; + + num_ref_idx_lx_active = get_ref_list_size(h264_ctx, cur_list); + + /* find short-term ref frame with pic_num is pic_num_lx */ + pic_lx = get_st_refpic_by_num(h264_ctx, pic_num_lx, cur_list); + if (!pic_lx) { + dev_dbg(&ctx->dev->plat_dev->dev, "cannot find st pic_lx for %d\n", pic_num_lx); + return; + } + + if (*ref_idx_lx == get_refidx_by_picnum(h264_ctx, pic_num_lx, cur_list)) { + dev_dbg(&ctx->dev->plat_dev->dev, "no need to move pic lx %d\n", *ref_idx_lx); + *ref_idx_lx = *ref_idx_lx + 1; + return; + } + + for (c_idx = num_ref_idx_lx_active; c_idx > *ref_idx_lx; c_idx--) + memcpy(&ref_list_reordered[c_idx], &ref_list_reordered[c_idx - 1], + sizeof(struct h264_decode_buf_spec)); + + memcpy(&ref_list_reordered[*ref_idx_lx], pic_lx, sizeof(struct h264_decode_buf_spec)); + dev_dbg(&ctx->dev->plat_dev->dev, "%s : RefPicListX[%d ] = pic %p pic_num(%d)\n", __func__, + *ref_idx_lx, pic_lx, ref_list_reordered[*ref_idx_lx].dpb->pic_num); + *ref_idx_lx = *ref_idx_lx + 1; + + n_idx = *ref_idx_lx; + for (c_idx = *ref_idx_lx; c_idx <= num_ref_idx_lx_active; c_idx++) { + if (ref_list_reordered[c_idx].long_term_flag || !ref_list_reordered[c_idx].dpb || + ref_list_reordered[c_idx].dpb->pic_num != pic_num_lx) + memcpy(&ref_list_reordered[n_idx++], &ref_list_reordered[c_idx], + sizeof(struct h264_decode_buf_spec)); + } + + h264_ctx->list_size[cur_list] = num_ref_idx_lx_active; +} + +static void reorder_long_term(struct slice *curr_slice, int cur_list, + int lt_pic_num, int *ref_idx_lx) +{ + struct aml_h264_ctx *h264_ctx = + container_of(curr_slice, struct aml_h264_ctx, mslice); + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + int num_ref_idx_lx_active; + int c_idx, n_idx; + struct h264_decode_buf_spec *ref_list; + struct h264_decode_buf_spec *pic_lt = NULL; + + if (cur_list == 0) + ref_list = &h264_ctx->ref_list0[0]; + else + ref_list = &h264_ctx->ref_list1[0]; + + num_ref_idx_lx_active = get_ref_list_size(h264_ctx, cur_list); + + /* find long-term ref frame with pic_num is lt_pic_num */ + pic_lt = get_lt_refpic_by_num(h264_ctx, lt_pic_num, cur_list); + if (!pic_lt) { + dev_dbg(&ctx->dev->plat_dev->dev, "cannot find lt pic_lx for %d\n", lt_pic_num); + return; + } + + if (*ref_idx_lx == get_refidx_by_picnum(h264_ctx, lt_pic_num, cur_list)) { + dev_dbg(&ctx->dev->plat_dev->dev, "no need to move pic lx %d\n", *ref_idx_lx); + *ref_idx_lx = *ref_idx_lx + 1; + return; + } + + for (c_idx = num_ref_idx_lx_active; c_idx > *ref_idx_lx; c_idx--) + memcpy(&ref_list[c_idx], &ref_list[c_idx - 1], sizeof(struct h264_decode_buf_spec)); + + memcpy(&ref_list[*ref_idx_lx], pic_lt, sizeof(struct h264_decode_buf_spec)); + dev_dbg(&ctx->dev->plat_dev->dev, "%s : RefPicListX[%d ] = pic %p pic_num(%d)\n", __func__, + *ref_idx_lx, pic_lt, ref_list[*ref_idx_lx].dpb->pic_num); + *ref_idx_lx = *ref_idx_lx + 1; + + n_idx = *ref_idx_lx; + /* Pointer dpb is NULL means this is a dummy frame store */ + for (c_idx = *ref_idx_lx; c_idx <= num_ref_idx_lx_active; c_idx++) { + if (!ref_list[c_idx].long_term_flag || !ref_list[c_idx].dpb || + ref_list[c_idx].dpb->pic_num != lt_pic_num) + memcpy(&ref_list[n_idx++], &ref_list[c_idx], + sizeof(struct h264_decode_buf_spec)); + } + + h264_ctx->list_size[cur_list] = num_ref_idx_lx_active; +} + +static void get_modification_cmd(unsigned short *reorder_cmd, + struct slice *curr_slice, int list) +{ + int i, j, val; + + val = curr_slice->ref_pic_list_reordering_flag[list]; + if (val) { + i = 0; + j = 0; + do { + curr_slice->modification_of_pic_nums_idc[list][i] = + reorder_cmd[j++]; + if (j >= REORDER_CMD_MAX) { + curr_slice->modification_of_pic_nums_idc[list][i] = 0; + break; + } + + val = curr_slice->modification_of_pic_nums_idc[list][i]; + if (val == 0 || val == 1) + curr_slice->abs_diff_pic_num_minus1[list][i] = reorder_cmd[j++]; + else if (val == 2) + curr_slice->long_term_pic_idx[list][i] = reorder_cmd[j++]; + + i++; + + if (i >= REORDERING_COMMAND_MAX_SIZE) { + curr_slice->ref_pic_list_reordering_flag[list] = 0; + break; + }; + if (j > REORDER_CMD_MAX) { + curr_slice->ref_pic_list_reordering_flag[list] = 0; + break; + }; + } while (val != 3); + } +} + +static void reorder_pics(struct aml_h264_ctx *h264_ctx, + struct slice *curr_slice, int cur_list) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + int *modification_of_pic_nums_idc = + curr_slice->modification_of_pic_nums_idc[cur_list]; + int *abs_diff_pic_num_minus1 = + curr_slice->abs_diff_pic_num_minus1[cur_list]; + int *long_term_pic_idx = curr_slice->long_term_pic_idx[cur_list]; + int pic_num_lx_nowarp, pic_num_lx_pred, pic_num_lx; + int curr_pic_num = curr_slice->frame_num; + int max_pic_num = + 1 << (4 + h264_ctx->ctrl_ref.sps->log2_max_frame_num_minus4); + int ref_idx_lx = 0; + int nowarp_tmp = 0; + int i; + + pic_num_lx_pred = curr_pic_num; + for (i = 0; i < REORDERING_COMMAND_MAX_SIZE && modification_of_pic_nums_idc[i] != 3; i++) { + if (modification_of_pic_nums_idc[i] > 3) { + dev_info(&ctx->dev->plat_dev->dev, "error, Invalid modification_of_pic_nums_idc command\n"); + break; + } + + if (modification_of_pic_nums_idc[i] < 2) { + if (modification_of_pic_nums_idc[i] == 0) { + nowarp_tmp = pic_num_lx_pred - (abs_diff_pic_num_minus1[i] + 1); + pic_num_lx_nowarp = nowarp_tmp + (nowarp_tmp < 0 ? max_pic_num : 0); + } else if (modification_of_pic_nums_idc[i] == 1) { + nowarp_tmp = pic_num_lx_pred + (abs_diff_pic_num_minus1[i] + 1); + pic_num_lx_nowarp = nowarp_tmp - + (nowarp_tmp > max_pic_num ? max_pic_num : 0); + } + pic_num_lx_pred = pic_num_lx_nowarp; + if (pic_num_lx_nowarp > curr_pic_num) + pic_num_lx = pic_num_lx_nowarp - max_pic_num; + else + pic_num_lx = pic_num_lx_nowarp; + + reorder_short_term(curr_slice, cur_list, pic_num_lx, &ref_idx_lx); + } else { + reorder_long_term(curr_slice, cur_list, long_term_pic_idx[i], &ref_idx_lx); + } + } +} + +static void copy_ref_list(struct aml_h264_ctx *h264_ctx, int curr_list) +{ + if (curr_list == 0) + memcpy(h264_ctx->ref_list0_unreordered, h264_ctx->ref_list0, + sizeof(h264_ctx->ref_list0)); + else + memcpy(h264_ctx->ref_list1_unreordered, h264_ctx->ref_list0, + sizeof(h264_ctx->ref_list1)); +} + +static void h264_reorder_reflists(struct aml_h264_ctx *h264_ctx) +{ + unsigned short *reorder_cmd; + struct slice *curr_slice = &h264_ctx->mslice; + + if (curr_slice->slice_type != I_SLICE && curr_slice->slice_type != SI_SLICE) { + reorder_cmd = &h264_ctx->dpb_param.mmco.l0_reorder_cmd[0]; + /* 3:parsed by ucode, means no reorder needed */ + if (reorder_cmd[0] != 3) + curr_slice->ref_pic_list_reordering_flag[0] = 1; + else + curr_slice->ref_pic_list_reordering_flag[0] = 0; + + get_modification_cmd(reorder_cmd, curr_slice, 0); + } + + if (curr_slice->slice_type == B_SLICE) { + reorder_cmd = &h264_ctx->dpb_param.mmco.l1_reorder_cmd[0]; + /* 3:parsed by ucode, means no reorder needed */ + if (reorder_cmd[0] != 3) + curr_slice->ref_pic_list_reordering_flag[1] = 1; + else + curr_slice->ref_pic_list_reordering_flag[1] = 0; + + get_modification_cmd(reorder_cmd, curr_slice, 1); + } + + if (curr_slice->slice_type != I_SLICE && + curr_slice->slice_type != SI_SLICE && + curr_slice->ref_pic_list_reordering_flag[0] != 0) { + copy_ref_list(h264_ctx, 0); + reorder_pics(h264_ctx, curr_slice, 0); + } + + if (curr_slice->slice_type == B_SLICE && + curr_slice->ref_pic_list_reordering_flag[1] != 0) { + copy_ref_list(h264_ctx, 1); + reorder_pics(h264_ctx, curr_slice, 1); + } +} + +static void h264_config_ref_lists(struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv; + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + struct v4l2_ctrl_h264_decode_params *decode = + (struct v4l2_ctrl_h264_decode_params *)ctrls->decode; + struct v4l2_ctrl_h264_sps *sps = + (struct v4l2_ctrl_h264_sps *)ctrls->sps; + const struct v4l2_h264_dpb_entry *dpb = decode->dpb; + struct v4l2_h264_reflist_builder builder; + struct v4l2_h264_reference v4l2_p0_reflist[V4L2_H264_REF_LIST_LEN]; + struct v4l2_h264_reference v4l2_b0_reflist[V4L2_H264_REF_LIST_LEN]; + struct v4l2_h264_reference v4l2_b1_reflist[V4L2_H264_REF_LIST_LEN]; + struct slice *curr_slice = &h264_ctx->mslice; + + if (decode->flags == V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC) + return; + + v4l2_h264_init_reflist_builder(&builder, decode, sps, dpb); + dev_dbg(&ctx->dev->plat_dev->dev, "%s num_valid = %d", __func__, + builder.num_valid); + + if (curr_slice->slice_type == P_SLICE && + (decode->flags & V4L2_H264_DECODE_PARAM_FLAG_PFRAME)) { + v4l2_h264_build_p_ref_list(&builder, v4l2_p0_reflist); + config_p_reflist(h264_ctx, v4l2_p0_reflist, builder.num_valid); + } else if (curr_slice->slice_type == B_SLICE && + (decode->flags & V4L2_H264_DECODE_PARAM_FLAG_BFRAME)) { + v4l2_h264_build_b_ref_lists(&builder, v4l2_b0_reflist, v4l2_b1_reflist); + config_b_reflist(h264_ctx, v4l2_b0_reflist, v4l2_b1_reflist, + builder.num_valid); + } +} + +static int allocate_canvas_pos(struct aml_h264_ctx *h264_ctx, int poc) +{ + int i; + int ret = -1; + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + + for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) { + if (((h264_ctx->canvas_pos_map >> i) & 0x1) == 0) { + h264_ctx->canvas_pos_map |= (1 << i); + h264_ctx->ref_canvas[i].poc = poc; + h264_ctx->ref_canvas[i].canvas_pos = i; + ret = i; + + dev_dbg(&ctx->dev->plat_dev->dev, + "%s i %d pos_poc %d\n", __func__, i, + h264_ctx->ref_canvas[i].poc); + break; + } + } + + return ret; +} + +static void release_canvas_pos(struct aml_h264_ctx *h264_ctx, int index) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + + if (index >= 0) { + if (index > V4L2_H264_NUM_DPB_ENTRIES) { + dev_dbg(&ctx->dev->plat_dev->dev, + "%s error, index %d is bigger than buf count %d\n", + __func__, index, h264_ctx->max_num_ref_frames); + } else { + if (h264_ctx->ref_canvas[index].poc != INVALID_POC && + ((h264_ctx->canvas_pos_map >> index) & 0x1) == + 0x1) { + h264_ctx->canvas_pos_map &= (~(1 << index)); + dev_dbg(&ctx->dev->plat_dev->dev, + "%s canvas_pos index %d released poc %d, canvas_pos_map 0x%x\n", + __func__, index, h264_ctx->ref_canvas[index].poc, + h264_ctx->canvas_pos_map); + h264_ctx->ref_canvas[index].poc = INVALID_POC; + h264_ctx->ref_canvas[index].canvas_pos = -1; + } + } + } +} + +static int get_canvas_pos_by_poc(struct aml_h264_ctx *h264_ctx, int poc) +{ + int i; + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + int ret_pos = -1; + + for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) { + if (h264_ctx->ref_canvas[i].poc == poc) { + ret_pos = h264_ctx->ref_canvas[i].canvas_pos; + dev_dbg(&ctx->dev->plat_dev->dev, "%s canvas_pos %d\n", + __func__, ret_pos); + return ret_pos; + } + } + + dev_dbg(&ctx->dev->plat_dev->dev, + "%s error, no find canvas pos %d, poc %d\n", __func__, ret_pos, poc); + + return ret_pos; +} + +static void clear_unused_col_buf(struct aml_h264_ctx *h264_ctx, + struct v4l2_ctrl_h264_decode_params *decode) +{ + int i, col_poc; + + /* flush all col buffers when IDR */ + if (decode->flags == V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC) { + /* 32 : max index of co-locate buffer */ + for (i = 0; i < 32; i++) + release_colocate_buf(h264_ctx, i); + for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) + release_canvas_pos(h264_ctx, i); + return; + } + + for (i = 0; i < h264_ctx->colocated_buf_num; i++) { + col_poc = h264_ctx->colocated_buf_poc[i]; + if (col_poc != INVALID_POC && + (poc_is_in_dpb(col_poc, decode->dpb) != 1)) + release_colocate_buf(h264_ctx, i); + } + + for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) { + col_poc = h264_ctx->ref_canvas[i].poc; + if (col_poc != INVALID_POC && + (poc_is_in_dpb(col_poc, decode->dpb) != 1)) + release_canvas_pos(h264_ctx, i); + } +} + +static void h264_config_decode_spec(struct aml_vdec_hw *hw, struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx; + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + struct v4l2_ctrl_h264_decode_params *decode = + (struct v4l2_ctrl_h264_decode_params *)ctrls->decode; + struct h264_decode_buf_spec *buf_spec_l0, *buf_spec_l1; + struct vb2_buffer *vb; + struct vb2_v4l2_buffer *vb2_v4l2; + struct vb2_queue *vq; + int i; + + clear_unused_col_buf(h264_ctx, decode); + + vb2_v4l2 = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + vb = &vb2_v4l2->vb2_buf; + + h264_ctx->curr_spec.y_dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); + if (ctx->pic_info.plane_num > 1) + h264_ctx->curr_spec.c_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 1); + else + h264_ctx->curr_spec.c_dma_addr = + h264_ctx->curr_spec.y_dma_addr + ctx->pic_info.fb_size[0]; + h264_ctx->curr_spec.canvas_pos = + allocate_canvas_pos(h264_ctx, decode->top_field_order_cnt); + if (h264_ctx->curr_spec.canvas_pos < 0) + dev_err(&ctx->dev->plat_dev->dev, "curr_spec.canvas error\n"); + + if (decode->nal_ref_idc) + h264_ctx->curr_spec.col_buf_index = + allocate_colocate_buf(h264_ctx, + decode->top_field_order_cnt); + else + h264_ctx->curr_spec.col_buf_index = -1; + h264_ctx->curr_spec.poc = decode->top_field_order_cnt; + + h264_config_ref_lists(ctx); + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) { + struct v4l2_h264_dpb_entry *dpb = &decode->dpb[i]; + + if (!(dpb->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) + break; + + buf_spec_l0 = find_spec_by_dpb_index(h264_ctx, i, 0); + if (buf_spec_l0) { + buf_spec_l0->canvas_pos = + get_canvas_pos_by_poc(h264_ctx, + dpb->top_field_order_cnt); + if (buf_spec_l0->canvas_pos < 0) { + dev_err(&ctx->dev->plat_dev->dev, + "l0 canvas_pos %d error\n", + buf_spec_l0->canvas_pos); + continue; + } + vb = vb2_find_buffer(vq, dpb->reference_ts); + if (!vb) { + dev_err(&ctx->dev->plat_dev->dev, + "ref pic for ts %llu lost\n", dpb->reference_ts); + continue; + } + + buf_spec_l0->y_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 0); + if (ctx->pic_info.plane_num > 1) + buf_spec_l0->c_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 1); + else + buf_spec_l0->c_dma_addr = + buf_spec_l0->y_dma_addr + + ctx->pic_info.fb_size[0]; + dev_dbg(&ctx->dev->plat_dev->dev, + "config canvas for poc %d canvas %d y_dma_addr %pad c_dma_addr %pad\n", + buf_spec_l0->dpb->top_field_order_cnt, + buf_spec_l0->canvas_pos, + &buf_spec_l0->y_dma_addr, + &buf_spec_l0->c_dma_addr); + } + + buf_spec_l1 = find_spec_by_dpb_index(h264_ctx, i, 1); + if (!buf_spec_l0 && buf_spec_l1) { + buf_spec_l1->canvas_pos = + get_canvas_pos_by_poc(h264_ctx, + dpb->top_field_order_cnt); + if (buf_spec_l1->canvas_pos < 0) { + dev_err(&ctx->dev->plat_dev->dev, + "l1 canvas_pos %d error\n", + buf_spec_l1->canvas_pos); + continue; + } + vb = vb2_find_buffer(vq, dpb->reference_ts); + if (!vb) { + dev_err(&ctx->dev->plat_dev->dev, + "ref pic for ts %llu lost\n", dpb->reference_ts); + continue; + } + + buf_spec_l1->y_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 0); + if (ctx->pic_info.plane_num > 1) + buf_spec_l1->c_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 1); + else + buf_spec_l1->c_dma_addr = + buf_spec_l1->y_dma_addr + + ctx->pic_info.fb_size[0]; + dev_dbg(&ctx->dev->plat_dev->dev, + "config canvas for poc %d canvas %d y_dma_addr %pad c_dma_addr %pad\n", + buf_spec_l1->dpb->top_field_order_cnt, + buf_spec_l1->canvas_pos, + &buf_spec_l1->y_dma_addr, + &buf_spec_l1->c_dma_addr); + } else if (buf_spec_l0 && buf_spec_l1) { + memcpy(buf_spec_l1, buf_spec_l0, + sizeof(struct h264_decode_buf_spec)); + dev_dbg(&ctx->dev->plat_dev->dev, + "config canvas for poc %d canvas %d y_dma_addr %pad c_dma_addr %pad\n", + buf_spec_l1->dpb->top_field_order_cnt, + buf_spec_l1->canvas_pos, + &buf_spec_l1->y_dma_addr, + &buf_spec_l1->c_dma_addr); + } + } +} + +static int get_poc_by_canvas_pos(struct aml_h264_ctx *h264_ctx, int canvas_pos) +{ + int i; + + for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) { + if (h264_ctx->ref_canvas[i].canvas_pos == canvas_pos) + return h264_ctx->ref_canvas[i].poc; + } + return -1; +} + +static struct v4l2_h264_dpb_entry *get_dpb_by_poc(struct v4l2_ctrl_h264_decode_params *decode, + int poc) +{ + int i; + + for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) { + if (decode->dpb[i].top_field_order_cnt == poc) + return &decode->dpb[i]; + } + return NULL; +} + +static int h264_config_decode_buf(struct aml_vdec_hw *hw, + struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx; + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + struct v4l2_ctrl_h264_decode_params *decode = + (struct v4l2_ctrl_h264_decode_params *)ctrls->decode; + unsigned int canvas_adr; + unsigned int ref_cfg; + unsigned int ref_cfg_once = 0; + struct slice *curr_slice = &h264_ctx->mslice; + unsigned int type_cfg = 0x3; /* 0x3: frame type */ + unsigned int colocate_adr_offset = 0; + unsigned int colocate_wr_adr; + unsigned int info0; + unsigned int info1; + unsigned int info2; + int i, j; + int h264_buffer_info_data_write_count = 0; + u8 canvas_pos; + u8 use_mode_8x8_flag; + u32 reg_val; + + regmap_write(hw->map[DOS_BUS], H264_CURRENT_POC_IDX_RESET, 0); + regmap_write(hw->map[DOS_BUS], H264_CURRENT_POC, decode->top_field_order_cnt); + regmap_write(hw->map[DOS_BUS], H264_CURRENT_POC, decode->top_field_order_cnt); + regmap_write(hw->map[DOS_BUS], H264_CURRENT_POC, decode->bottom_field_order_cnt); + regmap_write(hw->map[DOS_BUS], CURR_CANVAS_CTRL, h264_ctx->curr_spec.canvas_pos << 24); + regmap_read(hw->map[DOS_BUS], CURR_CANVAS_CTRL, &canvas_adr); + canvas_adr &= 0xffffff; + dev_dbg(hw->dev, "canvas_pos = %d canvas_adr 0x%x\n", + h264_ctx->curr_spec.canvas_pos, canvas_adr); + + regmap_write(hw->map[DOS_BUS], REC_CANVAS_ADDR, canvas_adr); + regmap_write(hw->map[DOS_BUS], DBKR_CANVAS_ADDR, canvas_adr); + regmap_write(hw->map[DOS_BUS], DBKW_CANVAS_ADDR, canvas_adr); + + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_INDEX, 16); + + for (j = 0; j < (V4L2_H264_NUM_DPB_ENTRIES + 1); j++) { + int poc; + struct v4l2_h264_dpb_entry *dpb = NULL; + + info0 = 0; + info1 = 0; + info2 = 0; + + poc = get_poc_by_canvas_pos(h264_ctx, j); + if (poc == decode->top_field_order_cnt) { + info0 = 0xf480 | 0xf; + info1 = decode->top_field_order_cnt; + info2 = decode->bottom_field_order_cnt; + if (decode->bottom_field_order_cnt < + decode->top_field_order_cnt) + info0 |= 0x100; + } else { + dpb = get_dpb_by_poc(decode, poc); + if (dpb && (dpb->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) { + info0 = 0xf480; + if (dpb->bottom_field_order_cnt < + dpb->top_field_order_cnt) + info0 |= 0x100; + info1 = dpb->top_field_order_cnt; + info2 = dpb->bottom_field_order_cnt; + if (dpb->flags & + V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM) + info0 |= ((1 << 5) | (1 << 4)); + } + } + + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, info0); + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, info1); + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, info2); + } + + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_INDEX, 0); + /* when frame width <= 256, Disable DDR_BYTE64_CACHE */ + if (ctx->pic_info.coded_width <= 256) { + regmap_update_bits(hw->map[DOS_BUS], IQIDCT_CONTROL, (1 << 16), (1 << 16)); + regmap_write(hw->map[DOS_BUS], DCAC_DDR_BYTE64_CTL, + (read_dos_reg(hw, DCAC_DDR_BYTE64_CTL) & (~0xf)) | 0xa); + } else { + regmap_update_bits(hw->map[DOS_BUS], IQIDCT_CONTROL, (1 << 16), 0); + regmap_write(hw->map[DOS_BUS], DCAC_DDR_BYTE64_CTL, + (read_dos_reg(hw, DCAC_DDR_BYTE64_CTL) & (~0xf))); + } + + ref_cfg = 0; + j = 0; + + for (i = 0; i < h264_ctx->list_size[0]; i++) { + canvas_pos = h264_ctx->ref_list0[i].canvas_pos; + /* bit 0:3 canvas_pos bit 5:6 frame struct cfg */ + ref_cfg_once = (canvas_pos & 0x1f) | (type_cfg << 5); + ref_cfg <<= 8; + ref_cfg |= ref_cfg_once; + j++; + + if (j == 4) { + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, + ref_cfg); + dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", + ref_cfg); + h264_buffer_info_data_write_count++; + j = 0; + } + } + + if (j != 0) { + while (j != 4) { + ref_cfg <<= 8; + ref_cfg |= ref_cfg_once; + j++; + } + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, ref_cfg); + dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", ref_cfg); + h264_buffer_info_data_write_count++; + } + ref_cfg = (ref_cfg_once << 24) | (ref_cfg_once << 16) | + (ref_cfg_once << 8) | ref_cfg_once; + for (j = h264_buffer_info_data_write_count; j < 8; j++) { + dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", ref_cfg); + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, ref_cfg); + } + + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_INDEX, 8); + j = 0; + ref_cfg = 0; + + for (i = 0; i < h264_ctx->list_size[1]; i++) { + canvas_pos = h264_ctx->ref_list1[i].canvas_pos; + ref_cfg_once = (canvas_pos & 0x1f) | (type_cfg << 5); + ref_cfg <<= 8; + ref_cfg |= ref_cfg_once; + j++; + + if (j == 4) { + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, ref_cfg); + dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", ref_cfg); + j = 0; + } + } + + if (j != 0) { + while (j != 4) { + ref_cfg <<= 8; + ref_cfg |= ref_cfg_once; + j++; + } + dev_dbg(hw->dev, "H264_BUFFER_INFO_DATA: %x\n", ref_cfg); + regmap_write(hw->map[DOS_BUS], H264_BUFFER_INFO_DATA, ref_cfg); + } + + if (get_flag(ctrls->sps->flags, V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY) && + get_flag(ctrls->sps->flags, V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE)) + use_mode_8x8_flag = 1; + else + use_mode_8x8_flag = 0; + + read_poll_timeout(read_dos_reg, reg_val, + !(reg_val & 0x800), + 10, 0, true, hw, H264_CO_MB_RW_CTL); + + /* col buf for curr frame */ + colocate_adr_offset = COL_SIZE_FOR_ONE_MB; + if (use_mode_8x8_flag) + colocate_adr_offset >>= 2; + colocate_adr_offset *= curr_slice->first_mb_in_slice; + + if (h264_ctx->curr_spec.col_buf_index >= 0 && + h264_ctx->curr_spec.col_buf_index < h264_ctx->colocated_buf_num) { + colocate_wr_adr = h264_ctx->collated_cma_addr + + ((h264_ctx->one_col_buf_size * + h264_ctx->curr_spec.col_buf_index) >> (use_mode_8x8_flag ? 2 : 0)); + if (colocate_adr_offset > h264_ctx->one_col_buf_size || + colocate_wr_adr + h264_ctx->one_col_buf_size > + h264_ctx->collated_cma_addr_end) { + dev_err(hw->dev, + "Error, colocate buf is not enough, index is %d\n", + h264_ctx->curr_spec.col_buf_index); + return -1; + } + regmap_write(hw->map[DOS_BUS], H264_CO_MB_WR_ADDR, + (colocate_wr_adr + colocate_adr_offset)); + dev_dbg(hw->dev, "col buffer addr = 0x%x col_buf_index %d\n", + (colocate_wr_adr + colocate_adr_offset), + h264_ctx->curr_spec.col_buf_index); + } else { + regmap_write(hw->map[DOS_BUS], H264_CO_MB_WR_ADDR, 0xffffffff); + dev_dbg(hw->dev, "col buffer addr = 0xffffffff\n"); + } + + if (h264_ctx->list_size[1] > 0) { + struct h264_decode_buf_spec *colocate_pic = + &h264_ctx->ref_list1[0]; + struct h264_decode_buf_spec *curr_pic = &h264_ctx->curr_spec; + int l10_structure = 2; /* for pic struct == FRAME, default to 2 */ + int cur_colocate_ref_type; + unsigned int colocate_rd_adr; + unsigned int colocate_rd_adr_offset = 0; + unsigned int val; + + cur_colocate_ref_type = + (abs(curr_pic->poc - colocate_pic->dpb->top_field_order_cnt) < + abs(curr_pic->poc - colocate_pic->dpb->bottom_field_order_cnt)) ? 0 : 1; + colocate_rd_adr_offset = COL_SIZE_FOR_ONE_MB; + if (use_mode_8x8_flag) + colocate_rd_adr_offset >>= 2; + + colocate_rd_adr_offset *= curr_slice->first_mb_in_slice; + if (colocate_pic->col_buf_index >= 0 && + colocate_pic->col_buf_index < h264_ctx->colocated_buf_num) { + colocate_rd_adr = h264_ctx->collated_cma_addr + + ((h264_ctx->one_col_buf_size * + colocate_pic->col_buf_index) >> (use_mode_8x8_flag + ? 2 : 0)); + if (colocate_rd_adr + h264_ctx->one_col_buf_size > + h264_ctx->collated_cma_addr_end) { + dev_err(hw->dev, + "Error, colocate rd buf is not enough, index is %d\n", + colocate_pic->col_buf_index); + return -1; + } + val = ((colocate_rd_adr_offset + colocate_rd_adr) >> 3) | + (cur_colocate_ref_type << 29) | + (l10_structure << 30); + regmap_write(hw->map[DOS_BUS], H264_CO_MB_RD_ADDR, val); + } else { + dev_err + (hw->dev, + "Error, reference pic has no colocated buf poc %d\n", + curr_pic->poc); + return -1; + } + } + + return 0; +} + +static void get_canvas_index(struct aml_vdec_hw *hw, struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx; + int i; + struct h264_decode_buf_spec *buf; + + config_decode_canvas(hw, &h264_ctx->curr_spec, + h264_ctx->mb_width, h264_ctx->mb_height); + if (h264_ctx->list_size[0] > 0) { + for (i = 0; i < h264_ctx->list_size[0]; i++) { + buf = &h264_ctx->ref_list0[i]; + config_decode_canvas(hw, buf, h264_ctx->mb_width, + h264_ctx->mb_height); + } + } + + if (h264_ctx->list_size[1] > 0) { + for (i = 0; i < h264_ctx->list_size[1]; i++) { + buf = &h264_ctx->ref_list1[i]; + config_decode_canvas(hw, buf, h264_ctx->mb_width, + h264_ctx->mb_height); + } + } +} + +static void release_canvas_index(struct aml_vdec_hw *hw, + struct h264_decode_buf_spec *buf) +{ + if (buf->y_canvas_index >= 0) { + dev_dbg(hw->dev, "free y_canvas %d\n", buf->y_canvas_index); + meson_canvas_free(hw->canvas, buf->y_canvas_index); + buf->y_canvas_index = -1; + } + + if (buf->u_canvas_index >= 0) { + dev_dbg(hw->dev, "free uv_canvas_index %d\n", + buf->u_canvas_index); + meson_canvas_free(hw->canvas, buf->u_canvas_index); + buf->u_canvas_index = -1; + buf->v_canvas_index = -1; + } +} + +static void h264_release_decode_spec(struct aml_vdec_hw *hw, struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx; + int i; + struct h264_decode_buf_spec *buf; + + release_canvas_index(hw, &h264_ctx->curr_spec); + + if (h264_ctx->list_size[0] > 0) { + for (i = 0; i < h264_ctx->list_size[0]; i++) { + buf = &h264_ctx->ref_list0[i]; + if (buf->used) { + buf->dpb = NULL; + release_canvas_index(hw, buf); + buf->used = 0; + } + } + h264_ctx->list_size[0] = 0; + } + + if (h264_ctx->list_size[1] > 0) { + for (i = 0; i < h264_ctx->list_size[1]; i++) { + buf = &h264_ctx->ref_list1[i]; + if (buf->used) { + buf->dpb = NULL; + release_canvas_index(hw, buf); + buf->used = 0; + } + } + h264_ctx->list_size[1] = 0; + } +} + +static void save_reg_status(struct aml_h264_ctx *h264_ctx) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + + regmap_read(hw->map[DOS_BUS], IQIDCT_CONTROL, &h264_ctx->reg_iqidct_control); + h264_ctx->reg_iqidct_control_init_flag = 1; + regmap_read(hw->map[DOS_BUS], VCOP_CTRL_REG, &h264_ctx->reg_vcop_ctrl_reg); + regmap_read(hw->map[DOS_BUS], RV_AI_MB_COUNT, &h264_ctx->reg_rv_ai_mb_count); + regmap_read(hw->map[DOS_BUS], VLD_DECODE_CONTROL, &h264_ctx->vld_dec_control); +} + +static void h264_get_slice_params(struct aml_h264_ctx *h264_ctx) +{ + struct slice *curr_slice = &h264_ctx->mslice; + + memset(curr_slice, 0, sizeof(struct slice)); + /* parsed by ucode */ + switch (h264_ctx->dpb_param.l.data[SLICE_TYPE]) { + case I_Slice: + curr_slice->slice_type = I_SLICE; + break; + case P_Slice: + curr_slice->slice_type = P_SLICE; + break; + case B_Slice: + curr_slice->slice_type = B_SLICE; + break; + default: + curr_slice->slice_type = MAX_SLICE_TYPES; + break; + } + + curr_slice->first_mb_in_slice = + h264_ctx->dpb_param.l.data[FIRST_MB_IN_SLICE]; + curr_slice->num_ref_idx_l0 = + h264_ctx->dpb_param.dpb.num_ref_idx_l0_active_minus1 + 1; + curr_slice->num_ref_idx_l1 = + h264_ctx->dpb_param.dpb.num_ref_idx_l1_active_minus1 + 1; + curr_slice->frame_num = h264_ctx->ctrl_ref.decode->frame_num; +} + +static irqreturn_t h264_isr(int irq, void *priv) +{ + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + + regmap_write(dev->dec_hw->map[DOS_BUS], VDEC_ASSIST_MBOX1_CLR_REG, 1); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t h264_threaded_isr_func(int irq, void *priv) +{ + u32 dec_status; + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)dev->dec_hw->curr_ctx; + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)dev->dec_ctx; + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + unsigned short *p = (unsigned short *)h264_ctx->lmem_addr; + int i, ii; + + regmap_read(hw->map[DOS_BUS], DPB_STATUS_REG, &dec_status); + h264_ctx->dec_status = dec_status; + dev_dbg + (&dev->plat_dev->dev, + "%s, dec_status 0x%x VIFF_BIT_CNT 0x%x MBY_MBX 0x%x VLD_SHIFT_STATUS 0x%x\n", + __func__, dec_status, read_dos_reg(hw, VIFF_BIT_CNT), + read_dos_reg(hw, MBY_MBX), read_dos_reg(hw, VLD_SHIFT_STATUS)); + + regmap_read(hw->map[DOS_BUS], AV_SCRATCH_F, &h264_ctx->save_avscratch_f); + + switch (dec_status) { + case H264_SLICE_HEADER_DONE: + for (i = 0; i < 0x400; i += 4) + for (ii = 0; ii < 4; ii++) + h264_ctx->dpb_param.l.data[i + ii] = p[i + 3 - ii]; + save_reg_status(h264_ctx); + h264_get_slice_params(h264_ctx); + if (h264_ctx->mslice.first_mb_in_slice != 0) + h264_release_decode_spec(hw, ctx); + + h264_config_decode_spec(hw, ctx); + h264_reorder_reflists(h264_ctx); + get_canvas_index(hw, ctx); + + if (h264_config_decode_buf(hw, ctx) < 0) { + h264_release_decode_spec(hw, ctx); + ctx->int_cond = 1; + wake_up_interruptible(&ctx->queue); + goto irq_handled; + } + if (h264_ctx->new_pic_flag == 1) { + regmap_write(hw->map[DOS_BUS], DPB_STATUS_REG, H264_ACTION_DECODE_NEWPIC); + dev_dbg(&dev->plat_dev->dev, "action decode new pic\n"); + h264_ctx->new_pic_flag = 0; + } else { + regmap_write(hw->map[DOS_BUS], DPB_STATUS_REG, H264_ACTION_DECODE_SLICE); + dev_dbg(&dev->plat_dev->dev, "action decode new slice\n"); + } + break; + case H264_SLICE_DATA_DONE: + h264_release_decode_spec(hw, ctx); + h264_ctx->decode_pic_count++; + ctx->int_cond = 1; + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx, + VB2_BUF_STATE_DONE); + wake_up_interruptible(&ctx->queue); + break; + default: + h264_release_decode_spec(hw, ctx); + ctx->int_cond = 1; + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx, + VB2_BUF_STATE_ERROR); + wake_up_interruptible(&ctx->queue); + break; + } +irq_handled: + return IRQ_HANDLED; +} + +static int h264_restore_hw_ctx(struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv; + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + + regmap_write(hw->map[DOS_BUS], POWER_CTL_VLD, + (read_dos_reg(hw, POWER_CTL_VLD) | (0 << 10) | (1 << 9) | (1 << 6))); + + regmap_write(hw->map[DOS_BUS], PSCALE_CTRL, 0); + + /* clear mailbox interrupt */ + regmap_write(hw->map[DOS_BUS], VDEC_ASSIST_MBOX1_CLR_REG, 1); + + /* enable mailbox interrupt */ + regmap_write(hw->map[DOS_BUS], VDEC_ASSIST_MBOX1_MASK, 1); + + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 17), (1 << 17)); + if (ctx->dec_fmt[AML_FMT_DST].fourcc == V4L2_PIX_FMT_NV21 || + ctx->dec_fmt[AML_FMT_DST].fourcc == V4L2_PIX_FMT_NV21M) + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, + (1 << 16), (1 << 16)); + else + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 16), 0); + + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, + (0xbf << 24), (0xbf << 24)); + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (0xbf << 24), 0); + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_CTRL, (1 << 31), 0); + + regmap_update_bits(hw->map[DOS_BUS], MDEC_PIC_DC_MUX_CTRL, (1 << 31), 0); + regmap_write(hw->map[DOS_BUS], MDEC_EXTIF_CFG1, 0); + regmap_write(hw->map[DOS_BUS], MDEC_PIC_DC_THRESH, 0x404038aa); + + regmap_write(hw->map[DOS_BUS], DPB_STATUS_REG, 0); + + regmap_write(hw->map[DOS_BUS], LMEM_DUMP_ADR, h264_ctx->lmem_phy_addr); + regmap_write(hw->map[DOS_BUS], FRAME_COUNTER_REG, h264_ctx->decode_pic_count); + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_8, h264_ctx->workspace_offset); + + regmap_write(hw->map[DOS_BUS], AV_SCRATCH_F, + ((h264_ctx->save_avscratch_f & 0xffffffc3) | (1 << 4))); + regmap_update_bits(hw->map[DOS_BUS], AV_SCRATCH_F, (1 << 6), 0); + + regmap_write(hw->map[DOS_BUS], MDEC_PIC_DC_THRESH, 0x404038aa); + + if (h264_ctx->reg_iqidct_control_init_flag == 0) + regmap_write(hw->map[DOS_BUS], IQIDCT_CONTROL, 0x200); + + if (h264_ctx->reg_iqidct_control) + regmap_write(hw->map[DOS_BUS], IQIDCT_CONTROL, h264_ctx->reg_iqidct_control); + + if (h264_ctx->reg_vcop_ctrl_reg) + regmap_write(hw->map[DOS_BUS], VCOP_CTRL_REG, h264_ctx->reg_vcop_ctrl_reg); + + if (h264_ctx->vld_dec_control) + regmap_write(hw->map[DOS_BUS], VLD_DECODE_CONTROL, h264_ctx->vld_dec_control); + + dev_dbg + (hw->dev, + "IQIDCT_CONTROL = 0x%x, VCOP_CTRL_REG 0x%x VLD_DECODE_CONTROL 0x%x\n", + read_dos_reg(hw, IQIDCT_CONTROL), read_dos_reg(hw, VCOP_CTRL_REG), + read_dos_reg(hw, VLD_DECODE_CONTROL)); + + return 0; +} + +static void *aml_h264_get_ctrl(struct v4l2_ctrl_handler *hdl, u32 id) +{ + struct v4l2_ctrl *ctrl; + + ctrl = v4l2_ctrl_find(hdl, id); + return ctrl ? ctrl->p_cur.p : NULL; +} + +static int aml_h264_get_stateless_ctrl_ref(struct aml_h264_ctx *h264_ctx) +{ + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; + + ctrls->sps = + (struct v4l2_ctrl_h264_sps *)aml_h264_get_ctrl(&ctx->ctrl_handler, + V4L2_CID_STATELESS_H264_SPS); + if (WARN_ON(!ctrls->sps)) + return -EINVAL; + + ctrls->pps = + (struct v4l2_ctrl_h264_pps *)aml_h264_get_ctrl(&ctx->ctrl_handler, + V4L2_CID_STATELESS_H264_PPS); + if (WARN_ON(!ctrls->pps)) + return -EINVAL; + + ctrls->decode = + (struct v4l2_ctrl_h264_decode_params *)aml_h264_get_ctrl(&ctx->ctrl_handler, + V4L2_CID_STATELESS_H264_DECODE_PARAMS); + if (WARN_ON(!ctrls->decode)) + return -EINVAL; + + ctrls->scaling = + (struct v4l2_ctrl_h264_scaling_matrix *)aml_h264_get_ctrl(&ctx->ctrl_handler, + V4L2_CID_STATELESS_H264_SCALING_MATRIX); + if (WARN_ON(!ctrls->scaling)) + return -EINVAL; + + return 0; +} + +static void copy_mc_cpu_fw(void *mc_cpu_addr, const u8 *data) +{ + /*header */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_HEADER, + data + 0x4000, MC_SWAP_SIZE); + /*data */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_DATA, + data + 0x2000, MC_SWAP_SIZE); + /*mmco */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_MMCO, + data + 0x6000, MC_SWAP_SIZE); + /*list */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_LIST, + data + 0x3000, MC_SWAP_SIZE); + /*slice */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_SLICE, + data + 0x5000, MC_SWAP_SIZE); + /*main */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_MAIN, data, 0x2000); + /*data */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_MAIN + 0x2000, + data + 0x2000, 0x1000); + /*slice */ + memcpy((u8 *)mc_cpu_addr + MC_OFFSET_MAIN + 0x3000, + data + 0x5000, 0x1000); +} + +static int aml_h264_load_fw_ext(void *priv, const u8 *data, u32 len) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)priv; + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)h264_ctx->v4l2_ctx; + struct aml_vdec_hw *dec_hw; + + if (h264_ctx->mc_cpu_loaded) + return 0; + + dec_hw = vdec_get_hw(ctx->dev); + if (!dec_hw) + return -1; + + if (len > MC_TOTAL_SIZE) { + dev_info(dec_hw->dev, "size of mc_cpu_fw id invalid\n"); + return -1; + } + + h264_ctx->mc_cpu_vaddr = dma_alloc_coherent(dec_hw->dev, MC_TOTAL_SIZE, + &h264_ctx->mc_cpu_paddr, + GFP_KERNEL); + if (!h264_ctx->mc_cpu_vaddr) + return -ENOMEM; + + copy_mc_cpu_fw(h264_ctx->mc_cpu_vaddr, data); + + h264_ctx->mc_cpu_loaded = true; + + dev_dbg(dec_hw->dev, "h264 mccpu fw loaded\n"); + + return 0; +} + +int aml_h264_init(void *priv) +{ + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv; + struct aml_vdec_hw *dec_hw; + struct aml_h264_ctx *h264_ctx; + int ret = 0; + + h264_ctx = kzalloc_obj(*h264_ctx, GFP_KERNEL); + if (!h264_ctx) + return -ENOMEM; + + h264_ctx->v4l2_ctx = ctx; + dec_hw = vdec_get_hw(ctx->dev); + if (!dec_hw) + return -1; + + h264_ctx->mc_cpu_loaded = false; + dec_hw->hw_ops.irq_handler = h264_isr; + dec_hw->hw_ops.irq_threaded_func = h264_threaded_isr_func; + dec_hw->hw_ops.load_firmware_ex = aml_h264_load_fw_ext; + + h264_ctx->lmem_addr = dma_alloc_coherent(dec_hw->dev, LMEM_DUMP_SIZE, + &h264_ctx->lmem_phy_addr, + GFP_KERNEL); + if (!h264_ctx->lmem_addr) { + ret = -ENOMEM; + goto err_alloc_lmem; + } + + h264_ctx->cma_alloc_vaddr = + dma_alloc_coherent(dec_hw->dev, V_BUF_ADDR_OFFSET, + &h264_ctx->cma_alloc_addr, GFP_KERNEL); + if (!h264_ctx->cma_alloc_vaddr) { + ret = -ENOMEM; + goto err_alloc_workspace; + } + + h264_ctx->workspace_offset = h264_ctx->cma_alloc_addr + DCAC_READ_MARGIN; + h264_ctx->workspace_vaddr = h264_ctx->cma_alloc_vaddr + DCAC_READ_MARGIN; + + ctx->codec_priv = h264_ctx; + dec_hw->curr_ctx = h264_ctx; + h264_ctx->col_buf_alloc_size = 0; + h264_ctx->init_flag = 0; + h264_ctx->new_pic_flag = 0; + h264_ctx->param_set = 0; + h264_ctx->reg_iqidct_control_init_flag = 0; + h264_ctx->decode_pic_count = 0; + + return 0; + +err_alloc_workspace: + dma_free_coherent(dec_hw->dev, LMEM_DUMP_SIZE, + h264_ctx->lmem_addr, h264_ctx->lmem_phy_addr); +err_alloc_lmem: + kfree(h264_ctx); + + return ret; +} + +void aml_h264_exit(void *priv) +{ + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv; + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv; + struct aml_vdec_hw *dec_hw; + + if (!h264_ctx) { + dev_info(&ctx->dev->plat_dev->dev, + "h264 decoder is already destroyed or not created!\n"); + return; + } + dec_hw = vdec_get_hw(ctx->dev); + h264_ctx->param_set = 0; + + if (ctx->dos_clk_en) + aml_stop_vdec_hw(dec_hw); + + if (h264_ctx->collated_cma_vaddr) { + dma_free_coherent(dec_hw->dev, h264_ctx->col_buf_alloc_size, + h264_ctx->collated_cma_vaddr, + h264_ctx->collated_cma_addr); + h264_ctx->col_buf_alloc_size = 0; + } + + if (h264_ctx->mc_cpu_vaddr) { + dma_free_coherent(dec_hw->dev, MC_TOTAL_SIZE, + h264_ctx->mc_cpu_vaddr, + h264_ctx->mc_cpu_paddr); + h264_ctx->mc_cpu_loaded = false; + } + + if (h264_ctx->lmem_addr) + dma_free_coherent(dec_hw->dev, LMEM_DUMP_SIZE, + h264_ctx->lmem_addr, h264_ctx->lmem_phy_addr); + + if (h264_ctx->cma_alloc_vaddr) + dma_free_coherent(dec_hw->dev, V_BUF_ADDR_OFFSET, + h264_ctx->cma_alloc_vaddr, + h264_ctx->cma_alloc_addr); + + kfree(ctx->codec_priv); + dec_hw->curr_ctx = NULL; + ctx->codec_priv = NULL; +} + +static void config_decode_mode(struct aml_vdec_ctx *ctx) +{ + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv; + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); + + regmap_write(hw->map[DOS_BUS], H264_DECODE_MODE, 0x1); /*decode mode framebase */ + regmap_write(hw->map[DOS_BUS], HEAD_PADDING_REG, 0); + regmap_write(hw->map[DOS_BUS], H264_DECODE_SEQINFO, h264_ctx->seq_info); + regmap_write(hw->map[DOS_BUS], INIT_FLAG_REG, 1); +} + +int aml_h264_dec_run(void *priv) +{ + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv; + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)ctx->codec_priv; + struct aml_vdec_hw *dec_hw = vdec_get_hw(ctx->dev); + int ret = -1; + int i; + + ret = aml_h264_get_stateless_ctrl_ref(h264_ctx); + if (ret < 0) { + dev_err(&ctx->dev->plat_dev->dev, "not ctrl ref for h264 decoder\n"); + return ret; + } + + h264_ctx->new_pic_flag = 1; + h264_config_params(ctx); + + if (h264_prepare_input(ctx) < 0) + return ret; + + if (alloc_colocate_cma(h264_ctx, ctx) < 0) + return ret; + + h264_restore_hw_ctx(ctx); + + config_decode_mode(ctx); + /* enable stream input hardware */ + regmap_update_bits(dec_hw->map[DOS_BUS], VLD_MEM_VIFIFO_CONTROL, 0x6, 0x6); + /* enable hardware timer */ + regmap_write(dec_hw->map[DOS_BUS], NAL_SEARCH_CTL, + read_dos_reg(dec_hw, NAL_SEARCH_CTL) | (1 << 16)); + regmap_write(dec_hw->map[DOS_BUS], MDEC_EXTIF_CFG2, + read_dos_reg(dec_hw, MDEC_EXTIF_CFG2) | 0x20); + regmap_write(dec_hw->map[DOS_BUS], NAL_SEARCH_CTL, + read_dos_reg(dec_hw, NAL_SEARCH_CTL) & (~0x2)); + regmap_update_bits(dec_hw->map[DOS_BUS], VDEC_ASSIST_MMC_CTRL1, + (1 << 3), 0); + + aml_start_vdec_hw(dec_hw); + h264_ctx->init_flag = 1; + + regmap_write(dec_hw->map[DOS_BUS], DPB_STATUS_REG, H264_ACTION_SEARCH_HEAD); + + ret = wait_event_interruptible_timeout(ctx->queue, ctx->int_cond, + msecs_to_jiffies(DECODER_TIMEOUT_MS)); + ctx->int_cond = 0; + if (!ret) { + ret = -1; + dev_err(&ctx->dev->plat_dev->dev, "dec timeout=%u\n", DECODER_TIMEOUT_MS); + for (i = 0; i < 16; i++) { /* 16 : show ucode PC 16 times when timeout */ + dev_info(&ctx->dev->plat_dev->dev, "decoder timeout, pc 0x%x\n", + read_dos_reg(dec_hw, MPC_E)); + usleep_range(10, 20); + } + h264_release_decode_spec(dec_hw, ctx); + } else if (-ERESTARTSYS == ret) { + ret = -1; + h264_release_decode_spec(dec_hw, ctx); + dev_err(&ctx->dev->plat_dev->dev, "dec inter fail\n"); + } + + aml_stop_vdec_hw(dec_hw); + h264_ctx->init_flag = 0; + + return ret; +} diff --git a/drivers/media/platform/amlogic/vdec/h264.h b/drivers/media/platform/amlogic/vdec/h264.h new file mode 100644 index 000000000000..830ab3241a1e --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/h264.h @@ -0,0 +1,299 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ +#ifndef _H264_H_ +#define _H264_H_ + +#define RPM_BEGIN 0x0 +#define FRAME_IN_DPB 24 +#define RPM_END 0x400 +#define DPB_OFFSET 0x100 +#define MMCO_OFFSET 0x200 +#define SPS_OFFSET 0x100 +#define PPS_OFFSET 0x300 +#define PARAM_BASE_VAL 0x414d +#define MEM_MMCO_BASE 0x01c3000 +#define MEM_SPS_BASE 0x01c3c00 +#define MEM_PPS_BASE 0x01cbc00 +#define MC_TOTAL_SIZE ((20 + 16) * SZ_1K) +#define MC_SWAP_SIZE (4 * SZ_1K) +#define LMEM_DUMP_SIZE 4096 +#define V_BUF_ADDR_OFFSET (0x200000 + 0x8000 + 0x20000 + 0x1000) +#define DCAC_READ_MARGIN (64 * 1024) +#define MC_OFFSET_HEADER 0x0000 +#define MC_OFFSET_DATA 0x1000 +#define MC_OFFSET_MMCO 0x2000 +#define MC_OFFSET_LIST 0x3000 +#define MC_OFFSET_SLICE 0x4000 +#define MC_OFFSET_MAIN 0x5000 + +/* Rename the dos regs */ +#define H264_DECODE_INFO M4_CONTROL_REG +#define INIT_FLAG_REG AV_SCRATCH_2 +#define HEAD_PADDING_REG AV_SCRATCH_3 +#define UCODE_WATCHDOG_REG AV_SCRATCH_7 +#define LMEM_DUMP_ADR AV_SCRATCH_L +#define DEBUG_REG1 AV_SCRATCH_M +#define DEBUG_REG2 AV_SCRATCH_N +#define FRAME_COUNTER_REG AV_SCRATCH_I +#define RPM_CMD_REG AV_SCRATCH_A +#define H264_DECODE_SIZE AV_SCRATCH_E +#define H264_DECODE_MODE AV_SCRATCH_4 +#define H264_DECODE_SEQINFO AV_SCRATCH_5 +/** + * NAL_SEARCH_CTL: bit 0, enable itu_t35 + * NAL_SEARCH_CTL: bit 1, enable mmu + * NAL_SEARCH_CTL: bit 2, detect frame_mbs_only_flag whether switch resolution + * NAL_SEARCH_CTL: bit 3, recover the correct sps pps + * NAL_SEARCH_CTL: bit 7-14,level_idc + * NAL_SEARCH_CTL: bit 15,bitstream_restriction_flag + */ +#define NAL_SEARCH_CTL AV_SCRATCH_9 +#define DPB_STATUS_REG AV_SCRATCH_J +#define ERROR_STATUS_REG AV_SCRATCH_9 + +#define H264_BUFFER_INFO_INDEX PMV3_X /* 0xc24 */ +#define H264_BUFFER_INFO_DATA PMV2_X /* 0xc22 */ +#define H264_CURRENT_POC_IDX_RESET LAST_SLICE_MV_ADDR /* 0xc30 */ +#define H264_CURRENT_POC LAST_MVY /* 0xc32 shared with conceal MV */ +#define H264_CO_MB_WR_ADDR VLD_C38 +#define H264_CO_MB_RD_ADDR VLD_C39 +#define H264_CO_MB_RW_CTL VLD_C3D +#define MBY_MBX MB_MOTION_MODE + +#define H264_ACTION_SEARCH_HEAD 0xf0 +#define H264_ACTION_DECODE_SLICE 0xf1 +#define H264_ACTION_CONFIG_DONE 0xf2 +#define H264_ACTION_DECODE_NEWPIC 0xf3 +#define H264_ACTION_DECODE_START 0xff + +/* RPM memory definition */ +#define FIXED_FRAME_RATE_FLAG 0X21 +#define OFFSET_DELIMITER_LO 0x2f +#define OFFSET_DELIMITER_HI 0x30 +#define SLICE_IPONLY_BREAK 0X5C +#define PREV_MAX_REFERENCE_FRAME_NUM 0X5D +#define EOS 0X5E +#define FRAME_PACKING_TYPE 0X5F +#define OLD_POC_PAR_1 0X60 +#define OLD_POC_PAR_2 0X61 +#define PREV_MBX 0X62 +#define PREV_MBY 0X63 +#define ERROR_SKIP_MB_NUM 0X64 +#define ERROR_MB_STATUS 0X65 +#define L0_PIC0_STATUS 0X66 +#define TIMEOUT_COUNTER 0X67 +#define BUFFER_SIZE 0X68 +#define BUFFER_SIZE_HI 0X69 +#define CROPPING_LEFT_RIGHT 0X6A +#define CROPPING_TOP_BOTTOM 0X6B +/** + * sps_flags2: + * bit 3, bitstream_restriction_flag + * bit 2, pic_struct_present_flag + * bit 1, vcl_hrd_parameters_present_flag + * bit 0, nal_hrd_parameters_present_flag + */ +#define SPS_FLAGS2 0x6C +#define NUM_REORDER_FRAMES 0x6D +#define MAX_BUFFER_FRAME 0X6E + +#define NON_CONFORMING_STREAM 0X70 +#define RECOVERY_POINT 0X71 +#define POST_CANVAS 0X72 +#define POST_CANVAS_H 0X73 +#define SKIP_PIC_COUNT 0X74 +#define TARGET_NUM_SCALING_LIST 0X75 +#define FF_POST_ONE_FRAME 0X76 +#define PREVIOUS_BIT_CNT 0X77 +#define MB_NOT_SHIFT_COUNT 0X78 +#define PIC_STATUS 0X79 +#define FRAME_COUNTER 0X7A +#define NEW_SLICE_TYPE 0X7B +#define NEW_PICTURE_STRUCTURE 0X7C +#define NEW_FRAME_NUM 0X7D +#define NEW_IDR_PIC_ID 0X7E +#define IDR_PIC_ID 0X7F + +/* h264 LOCAL */ +#define NAL_UNIT_TYPE 0X80 +#define NAL_REF_IDC 0X81 +#define SLICE_TYPE 0X82 +#define LOG2_MAX_FRAME_NUM 0X83 +#define FRAME_MBS_ONLY_FLAG 0X84 +#define PIC_ORDER_CNT_TYPE 0X85 +#define LOG2_MAX_PIC_ORDER_CNT_LSB 0X86 +#define PIC_ORDER_PRESENT_FLAG 0X87 +#define REDUNDANT_PIC_CNT_PRESENT_FLAG 0X88 +#define PIC_INIT_QP_MINUS26 0X89 +#define DEBLOCKING_FILTER_CONTROL_PRESENT_FLAG 0X8A +#define NUM_SLICE_GROUPS_MINUS1 0X8B +#define MODE_8X8_FLAGS 0X8C +#define ENTROPY_CODING_MODE_FLAG 0X8D +#define SLICE_QUANT 0X8E +#define TOTAL_MB_HEIGHT 0X8F +#define PICTURE_STRUCTURE 0X90 +#define TOP_INTRA_TYPE 0X91 +#define RV_AI_STATUS 0X92 +#define AI_READ_START 0X93 +#define AI_WRITE_START 0X94 +#define AI_CUR_BUFFER 0X95 +#define AI_DMA_BUFFER 0X96 +#define AI_READ_OFFSET 0X97 +#define AI_WRITE_OFFSET 0X98 +#define AI_WRITE_OFFSET_SAVE 0X99 +#define RV_AI_BUFF_START 0X9A +#define I_PIC_MB_COUNT 0X9B +#define AI_WR_DCAC_DMA_CTRL 0X9C +#define SLICE_MB_COUNT 0X9D +#define PICTYPE 0X9E +#define SLICE_GROUP_MAP_TYPE 0X9F +#define MB_TYPE 0XA0 +#define MB_AFF_ADDED_DMA 0XA1 +#define PREVIOUS_MB_TYPE 0XA2 +#define WEIGHTED_PRED_FLAG 0XA3 +#define WEIGHTED_BIPRED_IDC 0XA4 +/* bit 3:2 - PICTURE_STRUCTURE + * bit 1 - MB_ADAPTIVE_FRAME_FIELD_FLAG + * bit 0 - FRAME_MBS_ONLY_FLAG + */ +#define MBFF_INFO 0XA5 +#define TOP_INTRA_TYPE_TOP 0XA6 +#define RV_AI_BUFF_INC 0xA7 +#define DEFAULT_MB_INFO_LO 0xA8 +/* 0 -- no need to read + * 1 -- need to wait Left + * 2 -- need to read Intra + * 3 -- need to read back MV + */ +#define NEED_READ_TOP_INFO 0xA9 +/* 0 -- idle + * 1 -- wait Left + * 2 -- reading top Intra + * 3 -- reading back MV + */ +#define READ_TOP_INFO_STATE 0xAA +#define DCAC_MBX 0xAB +#define TOP_MB_INFO_OFFSET 0xAC +#define TOP_MB_INFO_RD_IDX 0xAD +#define TOP_MB_INFO_WR_IDX 0xAE + +#define VLD_NO_WAIT 0 +#define VLD_WAIT_BUFFER 1 +#define VLD_WAIT_HOST 2 +#define VLD_WAIT_GAP 3 + +#define VLD_WAITING 0xAF + +#define MB_X_NUM 0xB0 +#define MB_HEIGHT 0xB2 +#define MBX 0xB3 +#define TOTAL_MBY 0xB4 +#define INTR_MSK_SAVE 0xB5 +#define NEED_DISABLE_PPE 0xB6 +#define IS_NEW_PICTURE 0XB7 +#define PREV_NAL_REF_IDC 0XB8 +#define PREV_NAL_UNIT_TYPE 0XB9 +#define FRAME_MB_COUNT 0XBA +#define REF_IDC_OVERRIDE_FLAG 0XBB +#define SLICE_GROUP_CHANGE_RATE 0XBC +#define SLICE_GROUP_CHANGE_CYCLE_LEN 0XBD +#define DELAY_LENGTH 0XBE +#define PICTURE_STRUCT 0XBF +#define DCAC_PREVIOUS_MB_TYPE 0xC1 + +#define TIME_STAMP 0XC2 +#define H_TIME_STAMP 0XC3 +#define VPTS_MAP_ADDR 0XC4 +#define H_VPTS_MAP_ADDR 0XC5 +#define PIC_INSERT_FLAG 0XC7 +#define TIME_STAMP_START 0XC8 +#define TIME_STAMP_END 0XDF +#define OFFSET_FOR_NON_REF_PIC 0XE0 +#define OFFSET_FOR_TOP_TO_BOTTOM_FIELD 0XE2 +#define MAX_REFERENCE_FRAME_NUM 0XE4 +#define FRAME_NUM_GAP_ALLOWED 0XE5 +#define NUM_REF_FRAMES_IN_PIC_ORDER_CNT_CYCLE 0XE6 +#define PROFILE_IDC_MMCO 0XE7 +#define LEVEL_IDC_MMCO 0XE8 +#define FRAME_SIZE_IN_MB 0XE9 +#define DELTA_PIC_ORDER_ALWAYS_ZERO_FLAG 0XEA +#define PPS_NUM_REF_IDX_L0_ACTIVE_MINUS1 0XEB +#define PPS_NUM_REF_IDX_L1_ACTIVE_MINUS1 0XEC +#define CURRENT_SPS_ID 0XED +#define CURRENT_PPS_ID 0XEE +/* bit 0 - sequence parameter set may change + * bit 1 - picture parameter set may change + * bit 2 - new dpb just inited + * bit 3 - IDR picture not decoded yet + * bit 5:4 - 0: mb level code loaded 1: picture + * level code loaded 2: slice level code loaded + */ +#define DECODE_STATUS 0XEF +#define FIRST_MB_IN_SLICE 0XF0 +#define PREV_MB_WIDTH 0XF1 +#define PREV_FRAME_SIZE_IN_MB 0XF2 +/* bit 0 - aspect_ratio_info_present_flag + * bit 1 - timing_info_present_flag + * bit 2 - nal_hrd_parameters_present_flag + * bit 3 - vcl_hrd_parameters_present_flag + * bit 4 - pic_struct_present_flag + * bit 5 - bitstream_restriction_flag + */ +#define VUI_STATUS 0XF4 +#define ASPECT_RATIO_IDC 0XF5 +#define ASPECT_RATIO_SAR_WIDTH 0XF6 +#define ASPECT_RATIO_SAR_HEIGHT 0XF7 +#define NUM_UNITS_IN_TICK 0XF8 +#define TIME_SCALE 0XFA +#define CURRENT_PIC_INFO 0XFC +#define DPB_BUFFER_INFO 0XFD +#define REFERENCE_POOL_INFO 0XFE +#define REFERENCE_LIST_INFO 0XFF + +#define REORDER_CMD_MAX 66 + +/* config parameters to DDR lmem */ +#define GET_SPS_PROFILE_IDC(x) (((x) & 0xff) << 8) +#define GET_SPS_LEVEL_IDC(x) ((x) & 0xff) +#define GET_SPS_SEQ_PARAM_SET_ID(x) (((x) & 0x1f) << 8) +#define GET_SPS_CHROMA_FORMAT_IDC(x) ((x) << 8) +#define GET_SPS_NUM_REF_FRAMES(x) ((x) & 0xff) +#define GET_SPS_GAPS_ALLOWED_FLAG(x) ((x) << 8) +#define GET_SPS_LOG2_MAX_FRAME_NUM(x) ((x) + 4) +#define GET_SPS_PIC_ORDER_CNT_LSB(x) ((x) + 4) +#define GET_SPS_PIC_ORDER_TYPE(x) (x) +#define GET_SPS_OFFSET_FOR_NONREF_PIC_HIGH(x) (((x) & 0xffff0000) >> 16) +#define GET_SPS_OFFSET_FOR_NONREF_PIC_LOW(x) ((x) & 0xffff) +#define GET_SPS_OFFSET_FOR_TOP_BOT_FIELD_HIGH(x) (((x) & 0xffff0000) >> 16) +#define GET_SPS_OFFSET_FOR_TOP_BOT_FIELD_LOW(x) ((x) & 0xffff) +#define GET_SPS_PIC_WIDTH_IN_MBS(x) ((x) + 1) +#define GET_SPS_PIC_HEIGHT_IN_MBS(x) ((x) + 1) +#define GET_SPS_DIRECT_8X8_FLAGS(x) (((x) & 0x1) << 2) +#define GET_SPS_MB_ADAPTIVE_FRAME_FIELD_FLAGS(x) (((x) & 0x1) << 1) +#define GET_SPS_FRAME_MBS_ONLY_FLAGS(x) ((x) & 0x1) + +#define GET_PPS_PIC_PARAM_SET_ID(x) ((x) & 0xff) +#define GET_PPS_SEQ_PARAM_SET_ID(x) (((x) & 0x1f) << 8) +#define GET_PPS_ENTROPY_CODING_MODE_FLAG(x) (((x) & 0x1) << 13) +#define GET_PPS_PIC_ORDER_PRESENT_FLAG(x) (((x) & 0x1) << 14) +#define GET_PPS_NUM_IDX_REF_L0_MINUS1(x) ((x) & 0x1f) +#define GET_PPS_NUM_IDX_REF_L1_MINUS1(x) (((x) & 0x1f) << 5) +#define GET_PPS_WEIGHTED_PRED_FLAG(x) (((x) & 0x1) << 10) +#define GET_PPS_WEIGHTED_BIPRED_IDC(x) (((x) & 0x3) << 11) +#define GET_PPS_INIT_QS_MINUS26(x) (((x) & 0xff) << 8) +#define GET_PPS_INIT_QP_MINUS26(x) ((x) & 0xff) +#define GET_PPS_CHROMA_QP_INDEX_OFFSET(x) ((x) & 0xff) +#define GET_PPS_DEBLOCK_FILTER_CTRL_PRESENT_FLAG(x) (((x) & 0x1) << 8) +#define GET_PPS_CONSTRAIN_INTRA_PRED_FLAG(x) (((x) & 0x1) << 9) +#define GET_PPS_REDUNDANT_PIC_CNT_PRESENT_FLAG(x) (((x) & 0x1) << 10) +#define GET_PPS_SCALING_MATRIX_PRESENT_FLAG(x) (((x) & 0x1) << 1) +#define GET_PPS_TRANSFORM_8X8_FLAG(x) ((x) & 0x1) +#define GET_PPS_GET_SECOND_CHROMA_QP_OFFSET(x) (x) + +int aml_h264_init(void *priv); +void aml_h264_exit(void *priv); +int aml_h264_dec_run(void *priv); + +#endif diff --git a/drivers/media/platform/amlogic/vdec/reg_defines.h b/drivers/media/platform/amlogic/vdec/reg_defines.h new file mode 100644 index 000000000000..ea50018a078d --- /dev/null +++ b/drivers/media/platform/amlogic/vdec/reg_defines.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ +/* + * Copyright (C) 2025 Amlogic, Inc. All rights reserved + */ + +#ifndef _REG_DEFINES_H_ +#define _REG_DEFINES_H_ + +#define REG_ALIGN(x) ((x) << 2) + +#define VDEC_ASSIST_MMC_CTRL0 REG_ALIGN(0x0001) +#define VDEC_ASSIST_MMC_CTRL1 REG_ALIGN(0x0002) + +#define VDEC_ASSIST_CANVAS_BLK32 REG_ALIGN(0x0005) + +#define VDEC_ASSIST_MBOX1_CLR_REG REG_ALIGN(0x0075) +#define VDEC_ASSIST_MBOX1_MASK REG_ALIGN(0x0076) + +#define MPSR REG_ALIGN(0x0301) +#define MPC_P REG_ALIGN(0x0306) +#define MPC_D REG_ALIGN(0x0307) +#define MPC_E REG_ALIGN(0x0308) +#define MPC_W REG_ALIGN(0x0309) +#define CPSR REG_ALIGN(0x0321) +#define IMEM_DMA_CTRL REG_ALIGN(0x0340) +#define IMEM_DMA_ADR REG_ALIGN(0x0341) +#define IMEM_DMA_COUNT REG_ALIGN(0x0342) +#define WRRSP_IMEM REG_ALIGN(0x0343) +#define LMEM_DMA_CTRL REG_ALIGN(0x0350) +#define WRRSP_LMEM REG_ALIGN(0x0353) + +#define PSCALE_CTRL REG_ALIGN(0x0911) +#define GCLK_EN REG_ALIGN(0x0983) +#define MDEC_PIC_DC_CTRL REG_ALIGN(0x098e) +#define MDEC_PIC_DC_MUX_CTRL REG_ALIGN(0x098d) +#define ANC0_CANVAS_ADDR REG_ALIGN(0x0990) +#define ANC1_CANVAS_ADDR REG_ALIGN(0x0991) +#define ANC2_CANVAS_ADDR REG_ALIGN(0x0992) +#define ANC3_CANVAS_ADDR REG_ALIGN(0x0993) +#define ANC4_CANVAS_ADDR REG_ALIGN(0x0994) +#define ANC5_CANVAS_ADDR REG_ALIGN(0x0995) +#define ANC6_CANVAS_ADDR REG_ALIGN(0x0996) +#define ANC7_CANVAS_ADDR REG_ALIGN(0x0997) +#define ANC8_CANVAS_ADDR REG_ALIGN(0x0998) +#define ANC9_CANVAS_ADDR REG_ALIGN(0x0999) +#define ANC10_CANVAS_ADDR REG_ALIGN(0x099a) +#define ANC11_CANVAS_ADDR REG_ALIGN(0x099b) +#define ANC12_CANVAS_ADDR REG_ALIGN(0x099c) +#define ANC13_CANVAS_ADDR REG_ALIGN(0x099d) +#define ANC14_CANVAS_ADDR REG_ALIGN(0x099e) +#define ANC15_CANVAS_ADDR REG_ALIGN(0x099f) +#define ANC16_CANVAS_ADDR REG_ALIGN(0x09a0) +#define ANC17_CANVAS_ADDR REG_ALIGN(0x09a1) +#define ANC18_CANVAS_ADDR REG_ALIGN(0x09a2) +#define ANC19_CANVAS_ADDR REG_ALIGN(0x09a3) +#define ANC20_CANVAS_ADDR REG_ALIGN(0x09a4) +#define ANC21_CANVAS_ADDR REG_ALIGN(0x09a5) +#define ANC22_CANVAS_ADDR REG_ALIGN(0x09a6) +#define ANC23_CANVAS_ADDR REG_ALIGN(0x09a7) +#define ANC24_CANVAS_ADDR REG_ALIGN(0x09a8) +#define ANC25_CANVAS_ADDR REG_ALIGN(0x09a9) +#define ANC26_CANVAS_ADDR REG_ALIGN(0x09aa) +#define ANC27_CANVAS_ADDR REG_ALIGN(0x09ab) +#define ANC28_CANVAS_ADDR REG_ALIGN(0x09ac) +#define ANC29_CANVAS_ADDR REG_ALIGN(0x09ad) +#define ANC30_CANVAS_ADDR REG_ALIGN(0x09ae) +#define ANC31_CANVAS_ADDR REG_ALIGN(0x09af) +#define DBKR_CANVAS_ADDR REG_ALIGN(0x09b0) +#define DBKW_CANVAS_ADDR REG_ALIGN(0x09b1) +#define REC_CANVAS_ADDR REG_ALIGN(0x09b2) +#define CURR_CANVAS_CTRL REG_ALIGN(0x09b3) +#define MDEC_PIC_DC_THRESH REG_ALIGN(0x09b8) +#define AV_SCRATCH_0 REG_ALIGN(0x09c0) +#define AV_SCRATCH_1 REG_ALIGN(0x09c1) +#define AV_SCRATCH_2 REG_ALIGN(0x09c2) +#define AV_SCRATCH_3 REG_ALIGN(0x09c3) +#define AV_SCRATCH_4 REG_ALIGN(0x09c4) +#define AV_SCRATCH_5 REG_ALIGN(0x09c5) +#define AV_SCRATCH_6 REG_ALIGN(0x09c6) +#define AV_SCRATCH_7 REG_ALIGN(0x09c7) +#define AV_SCRATCH_8 REG_ALIGN(0x09c8) +#define AV_SCRATCH_9 REG_ALIGN(0x09c9) +#define AV_SCRATCH_A REG_ALIGN(0x09ca) +#define AV_SCRATCH_B REG_ALIGN(0x09cb) +#define AV_SCRATCH_C REG_ALIGN(0x09cc) +#define AV_SCRATCH_D REG_ALIGN(0x09cd) +#define AV_SCRATCH_E REG_ALIGN(0x09ce) +#define AV_SCRATCH_F REG_ALIGN(0x09cf) +#define AV_SCRATCH_G REG_ALIGN(0x09d0) +#define AV_SCRATCH_H REG_ALIGN(0x09d1) +#define AV_SCRATCH_I REG_ALIGN(0x09d2) +#define AV_SCRATCH_J REG_ALIGN(0x09d3) +#define AV_SCRATCH_K REG_ALIGN(0x09d4) +#define AV_SCRATCH_L REG_ALIGN(0x09d5) +#define AV_SCRATCH_M REG_ALIGN(0x09d6) +#define AV_SCRATCH_N REG_ALIGN(0x09d7) +#define WRRSP_VLD REG_ALIGN(0x09da) +#define MDEC_DOUBLEW_CFG0 REG_ALIGN(0x09db) +#define MDEC_DOUBLEW_CFG1 REG_ALIGN(0x09dc) +#define MDEC_DOUBLEW_CFG2 REG_ALIGN(0x09dd) +#define MDEC_DOUBLEW_CFG3 REG_ALIGN(0x09de) +#define MDEC_DOUBLEW_CFG4 REG_ALIGN(0x09df) +#define MDEC_DOUBLEW_CFG5 REG_ALIGN(0x09e0) +#define MDEC_DOUBLEW_CFG6 REG_ALIGN(0x09e1) +#define MDEC_DOUBLEW_CFG7 REG_ALIGN(0x09e2) +#define MDEC_DOUBLEW_STATUS REG_ALIGN(0x09e3) +#define MDEC_EXTIF_CFG0 REG_ALIGN(0x09e4) + +#define MDEC_EXTIF_CFG1 REG_ALIGN(0x09e5) +#define MDEC_EXTIF_CFG2 REG_ALIGN(0x09e6) + +#define POWER_CTL_VLD REG_ALIGN(0x0c08) +#define VLD_DECODE_CONTROL REG_ALIGN(0x0c18) + +#define PMV1_X REG_ALIGN(0x0c20) +#define PMV1_Y REG_ALIGN(0x0c21) +#define PMV2_X REG_ALIGN(0x0c22) +#define PMV2_Y REG_ALIGN(0x0c23) +#define PMV3_X REG_ALIGN(0x0c24) +#define PMV3_Y REG_ALIGN(0x0c25) +#define PMV4_X REG_ALIGN(0x0c26) +#define PMV4_Y REG_ALIGN(0x0c27) +#define M4_TABLE_SELECT REG_ALIGN(0x0c28) +#define M4_CONTROL_REG REG_ALIGN(0x0c29) +#define BLOCK_NUM REG_ALIGN(0x0c2a) +#define PATTERN_CODE REG_ALIGN(0x0c2b) +#define MB_INFO REG_ALIGN(0x0c2c) +#define VLD_DC_PRED REG_ALIGN(0x0c2d) +#define VLD_ERROR_MASK REG_ALIGN(0x0c2e) +#define VLD_DC_PRED_C REG_ALIGN(0x0c2f) +#define LAST_SLICE_MV_ADDR REG_ALIGN(0x0c30) +#define LAST_MVX REG_ALIGN(0x0c31) +#define LAST_MVY REG_ALIGN(0x0c32) + +#define MB_MOTION_MODE REG_ALIGN(0x0c07) +#define VIFF_BIT_CNT REG_ALIGN(0x0c1a) +#define M4_CONTROL_REG REG_ALIGN(0x0c29) +#define VLD_C38 REG_ALIGN(0x0c38) +#define VLD_C39 REG_ALIGN(0x0c39) +#define VLD_SHIFT_STATUS REG_ALIGN(0x0c3b) +#define VLD_C3D REG_ALIGN(0x0c3d) +#define VLD_MEM_VIFIFO_START_PTR REG_ALIGN(0x0c40) +#define VLD_MEM_VIFIFO_CURR_PTR REG_ALIGN(0x0c41) +#define VLD_MEM_VIFIFO_END_PTR REG_ALIGN(0x0c42) +#define VLD_MEM_VIFIFO_BYTES_AVAIL REG_ALIGN(0x0c43) +#define VLD_MEM_VIFIFO_CONTROL REG_ALIGN(0x0c44) +#define VLD_MEM_VIFIFO_WP REG_ALIGN(0x0c45) +#define VLD_MEM_VIFIFO_RP REG_ALIGN(0x0c46) +#define VLD_MEM_VIFIFO_LEVEL REG_ALIGN(0x0c47) +#define VLD_MEM_VIFIFO_BUF_CNTL REG_ALIGN(0x0c48) + +#define VCOP_CTRL_REG REG_ALIGN(0x0e00) +#define RV_AI_MB_COUNT REG_ALIGN(0x0e0c) +#define IQIDCT_CONTROL REG_ALIGN(0x0e0e) +#define DCAC_DDR_BYTE64_CTL REG_ALIGN(0x0e1d) + +#define VDEC2_IMEM_DMA_CTRL REG_ALIGN(0x2340) +#define VDEC2_IMEM_DMA_ADR REG_ALIGN(0x2341) +#define VDEC2_IMEM_DMA_COUNT REG_ALIGN(0x2342) + +#define DOS_SW_RESET0 REG_ALIGN(0x3f00) +#define DOS_GCLK_EN0 REG_ALIGN(0x3f01) +#define DOS_GCLK_EN1 REG_ALIGN(0x3f09) +#define DOS_GCLK_EN3 REG_ALIGN(0x3f35) + +#define DOS_MEM_PD_VDEC REG_ALIGN(0x3f30) +#define DOS_MEM_PD_VDEC2 REG_ALIGN(0x3f31) +#define DOS_MEM_PD_HCODEC REG_ALIGN(0x3f32) +/*add from M8M2*/ +#define DOS_MEM_PD_HEVC REG_ALIGN(0x3f33) + +#define DOS_SW_RESET3 REG_ALIGN(0x3f34) +#define DOS_GCLK_EN3 REG_ALIGN(0x3f35) +#define DOS_HEVC_INT_EN REG_ALIGN(0x3f36) + +#endif + -- 2.42.0 From sashiko-bot at kernel.org Tue May 26 02:01:09 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Tue, 26 May 2026 09:01:09 +0000 Subject: [PATCH RFC RESEND v5 1/6] firmware: meson: sm: Add video firmware loading SMC call In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-1-33bc817f93f4@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-1-33bc817f93f4@amlogic.com> Message-ID: <20260526090109.B34E21F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The identifier `SM_LOAD_VIDEO_FW` is used in the `gxbb_chip.cmd` array but is never defined, resulting in a build failure ('undeclared identifier'). The fix is found later in the series in commit 7da77dd38d2ea56fe7b57e246847b5cd118fd85e ('firmware: meson: sm: video firmware loading via secure monitor'). -- commit 087141e411e53f403ab8bf5aa5ca58a00cc95fd4 Author: Zhentao Guo firmware: meson: sm: Add video firmware loading SMC call This commit adds the SM_LOAD_VIDEO_FW command to the gxbb_chip command table to support loading video firmware. > diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c > index 3ab67aaa9e5da..5da6c65d684a0 100644 > --- a/drivers/firmware/meson/meson_sm.c > +++ b/drivers/firmware/meson/meson_sm.c > @@ -47,6 +47,7 @@ static const struct meson_sm_chip gxbb_chip = { > CMD(SM_GET_CHIP_ID, 0x82000044), > CMD(SM_A1_PWRC_SET, 0x82000093), > CMD(SM_A1_PWRC_GET, 0x82000095), > + CMD(SM_LOAD_VIDEO_FW, 0xb200000f), [Severity: Low] Does this code compile on its own? It appears that SM_LOAD_VIDEO_FW is used here but is not declared yet. While this is resolved later in the series by commit 7da77dd38d2ea56fe7b57e246847b5cd118fd85e (firmware: meson: sm: video firmware loading via secure monitor), each patch in a series needs to compile successfully on its own to maintain bisectability. > { /* sentinel */ }, > }, > }; -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4 at amlogic.com?part=1 From sashiko-bot at kernel.org Tue May 26 02:18:04 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Tue, 26 May 2026 09:18:04 +0000 Subject: [PATCH RFC RESEND v5 2/6] firmware: meson: sm: video firmware loading via secure monitor In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-2-33bc817f93f4@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-2-33bc817f93f4@amlogic.com> Message-ID: <20260526091804.974891F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The patch series is incorrectly ordered, separating the enum declaration of `SM_LOAD_VIDEO_FW` from its usage, which breaks `git bisect` due to a build failure. -- commit 7da77dd38d2ea56fe7b57e246847b5cd118fd85e Author: Zhentao Guo firmware: meson: sm: video firmware loading via secure monitor This commit adds the SM_LOAD_VIDEO_FW command to the secure monitor command enum. This enables decoder drivers to load firmware through the meson_sm interface. > diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/firmware/meson/meson_sm.h > index 8eaf8922ab020..f40867a000f10 100644 > --- a/include/linux/firmware/meson/meson_sm.h > +++ b/include/linux/firmware/meson/meson_sm.h > @@ -14,6 +14,7 @@ enum { > SM_GET_CHIP_ID, > SM_A1_PWRC_SET, > SM_A1_PWRC_GET, > + SM_LOAD_VIDEO_FW, [Severity: Low] Could this patch series ordering break git bisect? The previous commit 087141e411e53 ("firmware: meson: sm: Add video firmware loading SMC call") uses the SM_LOAD_VIDEO_FW command, but the enum value is not declared until this subsequent commit. Checking out the previous commit would result in an undeclared identifier compilation error. Should this declaration be moved before its usage to maintain bisectability? > }; -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4 at amlogic.com?part=2 From sashiko-bot at kernel.org Tue May 26 02:29:12 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Tue, 26 May 2026 09:29:12 +0000 Subject: [PATCH RFC RESEND v5 3/6] media: dt-bindings: Add Amlogic V4L2 video decoder In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-3-33bc817f93f4@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-3-33bc817f93f4@amlogic.com> Message-ID: <20260526092913.858E81F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] The `resets` property is functionally necessary and present in the example, but it is omitted from the `required` list. - [Low] The schema allows multiple interrupts but lacks `interrupt-names`, `items` descriptions, and a `minItems` constraint. -- commit 4ec0f5c7e982a0f22963af0593b18e2e09373c7e Author: Zhentao Guo media: dt-bindings: Add Amlogic V4L2 video decoder This commit describes the initial support for the V4L2 stateless video decoder driver used with the Amlogic S4 (S805X2) platform. > diff --git a/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml b/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml > new file mode 100644 > index 0000000000000..a0f33f6c35a17 > --- /dev/null > +++ b/Documentation/devicetree/bindings/media/amlogic,s4-vdec.yaml [ ... ] > +properties: > + compatible: > + const: amlogic,s4-vdec > + > + reg: > + maxItems: 2 > + > + reg-names: > + items: > + - const: dos > + - const: dmc > + > + interrupts: > + maxItems: 3 [Severity: Low] Should this property include an items list with descriptions or a corresponding interrupt-names property? Without these, there is no documentation on what each of the three interrupts represents or the required order. Is a minItems constraint also needed here so that device trees with fewer than 3 interrupts do not silently pass schema validation? > + > + clocks: > + maxItems: 3 [ ... ] > + secure-monitor: > + description: phandle to the secure-monitor node > + $ref: /schemas/types.yaml#/definitions/phandle > + > +required: > + - compatible > + - reg > + - reg-names > + - interrupts > + - clocks > + - clock-names > + - power-domains > + - power-domain-names > + - amlogic,canvas > + - secure-monitor [Severity: Medium] Should resets be included in this required list? It is defined in the properties block and explicitly used in the device tree example below. Omitting it here might allow incompletely configured device tree nodes missing the reset line to silently pass schema validation, which could lead to driver initialization failures or undefined hardware states at runtime. > + > +additionalProperties: false [ ... ] -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4 at amlogic.com?part=3 From linusw at kernel.org Tue May 26 02:54:31 2026 From: linusw at kernel.org (Linus Walleij) Date: Tue, 26 May 2026 11:54:31 +0200 Subject: [PATCH] pinctrl: meson: amlogic-a4: fix gpio output glitch In-Reply-To: <20260518-fix-set-value-glitch-v1-1-d350732dc934@amlogic.com> References: <20260518-fix-set-value-glitch-v1-1-d350732dc934@amlogic.com> Message-ID: On Mon, May 18, 2026 at 10:26?AM Xianwei Zhao via B4 Relay wrote: > From: Xianwei Zhao > > When the system transitions from bootloader to kernel, the GPIO is > expected to keep driving high. > > However, the Linux kernel first configures the pin direction and then > sets the output value. This may cause a brief low-level glitch on the > GPIO line, which can be problematic for regulator control. > > By configuring the output value before switching the pin direction to > output, the glitch can be avoided. > > This commit fixes the issue by swapping the configuration order. > > Fixes: 6e9be3abb78c ("pinctrl: Add driver support for Amlogic SoCs") > Signed-off-by: Xianwei Zhao Patch applied as non-critical fix based on my gut feeling. Yours, Linus Walleij From jian.hu at amlogic.com Tue May 26 02:58:19 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Tue, 26 May 2026 17:58:19 +0800 Subject: [PATCH 07/10] clk: amlogic: Support POWER_OF_TWO for PLL pre-divider In-Reply-To: <1jqzn65y9l.fsf@starbuckisacylon.baylibre.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-7-41cb4071b7c9@amlogic.com> <1jy0hm6n7e.fsf@starbuckisacylon.baylibre.com> <8d89b669-e72e-4663-9596-999a12922d32@amlogic.com> <1jqzn65y9l.fsf@starbuckisacylon.baylibre.com> Message-ID: <3fda1592-f7d0-4e86-8615-602804673414@amlogic.com> On 5/20/2026 3:35 PM, Jerome Brunet wrote: > [ EXTERNAL EMAIL ] > > On mer. 20 mai 2026 at 13:47, Jian Hu wrote: > >> On 5/14/2026 11:11 PM, Jerome Brunet wrote: >>> [ EXTERNAL EMAIL ] >>> >>> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: >>> >>>> From: Jian Hu >>>> >>>> The A9 PLL pre-divider uses a division factor of 2^n to ensure a clock >>>> duty cycle of 50% after predivision. >>>> >>>> Add flag 'CLK_MESON_PLL_N_POWER_OF_TWO' to indicate that the PLL >>>> pre-divider division factor is 2^n. >>> I understand what you are doing here but I have to ask why this can't be >>> implemented with independent dividers that already supports power of 2 ? >> >> If we use independent dividers, the n member would have to be removed from >> meson_clk_pll_data. >> >> However, n is referenced 35 times in clk-pll.c, which means we would need >> to modify all >> related logic across the file. This would be a relatively large >> change. > Yes > >> >> Moreover, for all Amlogic chips, the n divider is an indispensable part of >> the DCO clock. > There is hardly a justification here > >> The difference between SoC generations is as follows: >> Previous SoCs PLL: n = 1, 2, 3, 4... (linear divider) >> A9 SoC PLL: n = 2^0, 2^1, 2^2, 2^3, 2^4... (power-of-two >> divider) > Yes that was fairly obvious > >> Therefore, splitting out the n divider from the DCO clock might not be a >> good design choice. > I'm not sure I agree and you've only stated your point of view without > providing any technical justification here. > > From the datasheets of the different SoC we have, the documented > limitation is always the DCO output rate range. Nothing related to n (or > m, or the mult-range for that matter). This is a legacy problem, we > started with monolithic driver and slowly simplified it. > > As far as I can see now, reworking the PLL driver to be a simple > multiplier driver with range output rate constraint could actually be > simpler than the current code. I would also make simpler to accomodate > differences such as the one presented here. > > Unless you can provide technical reasons why going in this direction > would be incorrect, that's where I'd prefer to go. > >> [...] >> >> Best regards, >> >> Jian > -- > Jerome I agree that having an independent N divider would simplify the PLL rate calculation. A separate pre-divider for N is technically possible, but there are some hardware constraints that need to be considered: N = 1 is the preferred operating mode except a few fixed-frequency PLLs. Larger N values reduce the PLL phase detector frequency, which may negatively impact jitter performance and overall PLL stability. Because of this, we cannot guarantee stable system operation when arbitrary larger N values are used. Some PLLs require non-1 N values to generate specific fixed output frequencies because the target rate cannot be achieved with N = 1 while keeping the PLL while keeping the PLL within its valid operating range. So N is designed to have other values ??to satisfy this requirement. For example, the AXG PCIe PLL uses N = 3 to generate the required 100 MHz output frequency, since the target frequency cannot be achieved with N = 1. Additionally, is the refactored pre-divider N implemented as a separate patchset, independent from the A9 PLL changes? Best regards, Jian From sashiko-bot at kernel.org Tue May 26 03:04:42 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Tue, 26 May 2026 10:04:42 +0000 Subject: [PATCH RFC RESEND v5 4/6] decoder: Add V4L2 stateless H.264 decoder driver In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-4-33bc817f93f4@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-4-33bc817f93f4@amlogic.com> Message-ID: <20260526100443.497C21F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 22 potential issue(s) to consider: - [Critical] Global context pointers are unconditionally overwritten, breaking isolation for multiple instances and leading to UAF. - [High] Accessing V4L2 M2M buffers and request objects after they have been completed by the ISR. - [High] Missing `v4l2_m2m_buf_done_and_job_finish()` on error path causes a permanent stall of the V4L2 M2M context. - [High] Probe error paths fail to clean up PM domains and device links. - [High] Setting the OUTPUT queue format mistakenly overwrites the CAPTURE queue's format with a compressed stream format. - [High] Multi-planar raw capture formats result in out-of-bounds DMA access due to improperly hardcoded plane counts. - [High] Redundant hardware canvas allocations and error path failures leak and exhaust the global hardware canvas pool. - [High] Tautological comparisons on unsigned variables cause bounds-check bypasses and spurious resource frees. - [High] Missing validation of V4L2 SPS/PPS controls leads to multiple heap buffer overflows, integer overflows, and OOB DMA. - [High] Teardown in `aml_vdec_drv_remove` frees the driver context while file descriptors can remain open. - [High] Failing `start_streaming` does not return queued buffers to the VB2 framework. - [High] Double free and premature free of `ctx->codec_priv` via `vb2ops_vdec_stop_streaming`. - [High] Bypassing PM runtime acquisition leads to unbalanced clock/PM references. - [High] Lockless buffer access and unchecked NULL dereference in IRQ handler. - [High] The DOS clock is never enabled before accessing DOS_BUS registers. - [Medium] Incorrect error checking of `of_parse_phandle` leads to NULL pointer dereference and wrong error return. - [Medium] Unmet dependencies when using `select` with architecture-specific symbols in a `COMPILE_TEST` driver. - [Medium] Missing check for pm_runtime_get_sync return value allows access to unpowered hardware. - [Medium] The V4L2 control handler memory is leaked if `v4l2_ctrl_handler_setup` fails. - [Medium] Video device node is exposed to userspace before hardware initialization is complete. - [Medium] Missing minimum length validation for loaded firmware data. - [Medium] Missing `dma_rmb()` barrier before reading DMA memory updated by hardware. -- commit 05d0db2be715fa3e4cc8fc756c957d431e6f5b89 Author: Zhentao Guo decoder: Add V4L2 stateless H.264 decoder driver Add initial support for V4L2 stateless video decoder driver on Amlogic S4(S805X2) platform. In phase 1, it supports 8bit H.264 bitstreams decoding. Currently only progressive streams are supported. > diff --git a/drivers/media/platform/amlogic/vdec/Kconfig b/drivers/media/platform/amlogic/vdec/Kconfig > new file mode 100644 > index 0000000000000..d392967c77436 > --- /dev/null > +++ b/drivers/media/platform/amlogic/vdec/Kconfig > @@ -0,0 +1,18 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR MIT) > + > +config VIDEO_AMLOGIC_VDEC > + tristate "Amlogic Video Decoder Driver" > + depends on ARCH_MESON || COMPILE_TEST > + depends on VIDEO_DEV > + depends on V4L_MEM2MEM_DRIVERS > + depends on TEE > + select VIDEOBUF2_DMA_CONTIG > + select V4L2_H264 > + select V4L2_MEM2MEM_DEV > + select MESON_CANVAS > + select MESON_SM [Severity: Medium] Will selecting architecture-specific symbols like MESON_CANVAS and MESON_SM cause unmet dependency warnings on unsupported architectures when COMPILE_TEST is enabled? [ ... ] > diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec.c b/drivers/media/platform/amlogic/vdec/aml_vdec.c > new file mode 100644 > index 0000000000000..d4dcd0180d2d0 > --- /dev/null > +++ b/drivers/media/platform/amlogic/vdec/aml_vdec.c [ ... ] > +int aml_vdec_ctrls_setup(struct aml_vdec_ctx *ctx) > +{ > + int i; > + int ctrls_size = ARRAY_SIZE(controls); > + > + v4l2_ctrl_handler_init(&ctx->ctrl_handler, ctrls_size); > + for (i = 0; i < ctrls_size; i++) { > + v4l2_ctrl_new_custom(&ctx->ctrl_handler, &controls[i].cfg, NULL); > + if (ctx->ctrl_handler.error) { > + dev_info(&ctx->dev->plat_dev->dev, "add ctrl for (%d) failed%d\n", > + controls[i].cfg.id, ctx->ctrl_handler.error); > + v4l2_ctrl_handler_free(&ctx->ctrl_handler); > + return ctx->ctrl_handler.error; > + } > + } > + ctx->fh.ctrl_handler = &ctx->ctrl_handler; > + return v4l2_ctrl_handler_setup(&ctx->ctrl_handler); [Severity: Medium] If v4l2_ctrl_handler_setup() returns an error, does this code leak the memory allocated for the control handler? The caller doesn't seem to free it on this failure path. > +} > + > +static void m2mops_vdec_device_run(void *m2m_priv) > +{ > + struct aml_vdec_ctx *ctx = m2m_priv; > + struct aml_vdec_dev *dev = ctx->dev; > + struct vb2_v4l2_buffer *src_buf, *dst_buf; > + struct media_request *src_req; > + int ret = 0; > + > + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); > + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); > + dev_dbg(&dev->plat_dev->dev, "device run : src buf : %d dst buf %d\n", > + src_buf->vb2_buf.index, dst_buf->vb2_buf.index); > + if (WARN_ON_ONCE(!ctx->codec_ops->run)) > + goto err_cancel_job; > + > + src_req = src_buf->vb2_buf.req_obj.req; > + if (src_req) > + v4l2_ctrl_request_setup(src_req, &ctx->ctrl_handler); > + dos_enable(dev->dec_hw); > + /* incase of bus hang in stop_streaming */ > + ctx->dos_clk_en = 1; > + > + if (ctx->curr_dec_type == CODEC_TYPE_H264) > + aml_vdec_reset_core(dev->dec_hw); > + > + if (load_firmware(dev->dec_hw, ctx->curr_dec_type) < 0) > + goto err_cancel_job; > + > + ret = ctx->codec_ops->run(ctx); > + > + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf); > + if (src_req) > + v4l2_ctrl_request_complete(src_req, &ctx->ctrl_handler); [Severity: High] Since ctx->codec_ops->run() blocks on wait_event_interruptible_timeout, and the ISR completes the job by calling v4l2_m2m_buf_done_and_job_finish(), could src_buf, dst_buf, and src_req already be returned to userspace and potentially freed by the time this thread wakes up to access them? > + if (ret < 0) > + goto err_cancel_job; > + > + return; > + > +err_cancel_job: > + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx, > + VB2_BUF_STATE_ERROR); > +} [ ... ] > +static void set_pic_info(struct aml_vdec_ctx *ctx, > + struct v4l2_pix_format_mplane *pix_mp, > + enum v4l2_buf_type type) > +{ > + if (V4L2_TYPE_IS_OUTPUT(type)) { > + ctx->pic_info.output_pix_fmt = pix_mp->pixelformat; > + ctx->pic_info.coded_width = ALIGN(pix_mp->width, 64); > + ctx->pic_info.coded_height = ALIGN(pix_mp->height, 64); > + ctx->pic_info.fb_size[0] = > + ctx->pic_info.coded_width * ctx->pic_info.coded_height; > + ctx->pic_info.fb_size[1] = ctx->pic_info.fb_size[0] / 2; > + ctx->pic_info.plane_num = 1; [Severity: High] If a multi-planar format like NV12M is negotiated, shouldn't plane_num be set to match the actual planes used? By rigidly setting plane_num to 1 here, could the driver end up computing out-of-bounds DMA addresses for the C plane later? > + } > +} [ ... ] > +static int vdec_s_fmt_output(struct aml_vdec_ctx *ctx, struct v4l2_format *f) > +{ > + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; > + const struct aml_video_fmt *out_fmt; > + struct vb2_queue *vq; > + int ret; > + > + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); > + if (vb2_is_busy(vq) && > + pix_mp->pixelformat != ctx->pix_fmt[AML_FMT_SRC].pixelformat) > + return -EBUSY; > + > + out_fmt = aml_vdec_get_video_fmt(ctx->dev, pix_mp->pixelformat); > + if (out_fmt) > + ctx->dec_fmt[AML_FMT_SRC] = *out_fmt; > + else > + dev_dbg(&ctx->dev->plat_dev->dev, > + "%s fmt %d not supported, use default\n", __func__, > + pix_mp->pixelformat); > + > + ret = vdec_try_fmt_mp(ctx, f->type, pix_mp); > + set_pic_info(ctx, pix_mp, f->type); > + > + ctx->pix_fmt[AML_FMT_SRC] = *pix_mp; > + ctx->pix_fmt[AML_FMT_DST] = *pix_mp; [Severity: High] Does overwriting ctx->pix_fmt[AML_FMT_DST] here corrupt the capture queue's pixel format by erroneously assigning it the output queue's compressed bitstream format? > + > + return ret; > +} [ ... ] > +static int vb2ops_vdec_start_streaming(struct vb2_queue *q, unsigned int count) > +{ > + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(q); > + > + if (V4L2_TYPE_IS_OUTPUT(q->type)) { > + ctx->is_output_streamon = 1; > + if (aml_vdec_init_dec_inst(ctx) < 0) > + return -EINVAL; [Severity: High] If aml_vdec_init_dec_inst() fails and returns an error, doesn't the driver need to return all queued buffers to userspace with state VB2_BUF_STATE_QUEUED to avoid permanently leaking them? > + } else { > + ctx->is_cap_streamon = 1; > + } > + > + return 0; > +} > + > +static void vb2ops_vdec_stop_streaming(struct vb2_queue *q) > +{ > + struct aml_vdec_ctx *ctx = vb2_get_drv_priv(q); > + struct vb2_v4l2_buffer *src_buf = NULL, *dst_buf = NULL; > + > + aml_vdec_release_instance(ctx); [Severity: High] Since aml_vdec_release_instance() calls aml_h264_exit() which frees ctx->codec_priv, what happens if userspace stops only the capture queue while the output queue is actively streaming? Will this cause a use-after-free on ctx->codec_priv for ongoing decode jobs or incoming IRQs? > + > + if (V4L2_TYPE_IS_OUTPUT(q->type)) { > + while ((src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx))) > + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); > + ctx->is_output_streamon = 0; > + } else { > + while ((dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx))) > + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); > + ctx->is_cap_streamon = 0; > + } > +} [ ... ] > diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c > new file mode 100644 > index 0000000000000..d63cbd4f9e26d > --- /dev/null > +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_drv.c [ ... ] > +static int fops_vcodec_open(struct file *file) > +{ > + struct aml_vdec_dev *dec_dev = video_drvdata(file); > + struct aml_vdec_ctx *ctx = NULL; > + int ret = 0; > + > + ctx = kzalloc_obj(*ctx, GFP_KERNEL); > + if (!ctx) > + return -ENOMEM; > + > + mutex_lock(&dec_dev->dev_mutex); > + dec_dev->dec_ctx = ctx; [Severity: Critical] Is it safe to assign the newly allocated context to the global dec_dev->dec_ctx? If multiple file handles are opened concurrently, won't this overwrite the pointer and cause the ISR to route events only to the most recently opened instance? > + ctx->dev = dec_dev; > + v4l2_fh_init(&ctx->fh, video_devdata(file)); > + file->private_data = &ctx->fh; > + v4l2_fh_add(&ctx->fh, file); > + dec_dev->filp = file; > + mutex_init(&ctx->v4l2_intf_lock); > + init_waitqueue_head(&ctx->queue); > + ctx->int_cond = 0; [ ... ] > +static int aml_vdec_drv_probe(struct platform_device *pdev) > +{ > + struct aml_vdec_dev *dev; > + struct video_device *vfd_dec; > + struct aml_vdec_hw *hw; > + int ret = 0; > + > + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); > + if (!dev) > + return -ENOMEM; > + > + dev->plat_dev = pdev; > + mutex_init(&dev->dev_mutex); > + > + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); > + if (ret) > + return dev_err_probe(&pdev->dev, ret, "v4l2_device_register err\n"); > + > + vfd_dec = video_device_alloc(); > + if (!vfd_dec) { > + v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n"); > + ret = -ENOMEM; > + goto err_device_alloc; > + } > + *vfd_dec = dec_dev; > + vfd_dec->v4l2_dev = &dev->v4l2_dev; > + vfd_dec->lock = &dev->dev_mutex; > + video_set_drvdata(vfd_dec, dev); > + dev->vfd = vfd_dec; > + platform_set_drvdata(pdev, dev); > + > + hw = devm_kzalloc(&pdev->dev, sizeof(*hw), GFP_KERNEL); > + if (!hw) { > + ret = -ENOMEM; > + goto err_dec_mem_init; > + } > + dev->dec_hw = hw; > + > + dev->pvdec_data = of_device_get_match_data(&pdev->dev); > + ret = dev->pvdec_data->req_hw_resource(dev); > + if (ret < 0) > + goto err_hw_init; > + > + dev->m2m_dev_dec = v4l2_m2m_init(&aml_vdec_m2m_ops); > + if (IS_ERR(dev->m2m_dev_dec)) { > + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem dec device\n"); > + ret = PTR_ERR((__force void *)dev->m2m_dev_dec); > + goto err_hw_init; > + } > + > + ret = video_register_device(vfd_dec, VFL_TYPE_VIDEO, -1); > + if (ret) { > + v4l2_err(&dev->v4l2_dev, "Failed to register video device"); > + goto err_vid_dev_register; > + } > + > + dev->mdev.dev = &pdev->dev; > + strscpy(dev->mdev.model, AML_VDEC_DRV_NAME, sizeof(dev->mdev.model)); > + media_device_init(&dev->mdev); > + dev->mdev.ops = &aml_m2m_media_ops; > + dev->v4l2_dev.mdev = &dev->mdev; > + > + ret = v4l2_m2m_register_media_controller(dev->m2m_dev_dec, vfd_dec, > + MEDIA_ENT_F_PROC_VIDEO_DECODER); > + if (ret) { > + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem media controller\n"); > + goto error_m2m_mc_register; > + } > + > + ret = media_device_register(&dev->mdev); > + if (ret) { > + v4l2_err(&dev->v4l2_dev, "Failed to register media device"); > + goto err_media_dev_register; > + } > + vdec_enable(dev->dec_hw); [Severity: Medium] By calling video_register_device() before vdec_enable(), isn't the device node exposed to userspace before the hardware is powered up and initialized? Could this allow userspace to issue ioctls on unpowered hardware? > + return 0; > + > +err_media_dev_register: > + v4l2_m2m_unregister_media_controller(dev->m2m_dev_dec); > +error_m2m_mc_register: > + media_device_cleanup(&dev->mdev); > +err_vid_dev_register: > + v4l2_m2m_release(dev->m2m_dev_dec); > +err_hw_init: > + dev->dec_hw = NULL; [Severity: High] Does this error path forget to call dev->pvdec_data->destroy_hw_resource(dev)? Failing to clean up hardware resources here might permanently leak attached PM domains and device links. > +err_dec_mem_init: > + video_device_release(vfd_dec); > +err_device_alloc: > + v4l2_device_unregister(&dev->v4l2_dev); > + return ret; > +} > + > +static void aml_vdec_drv_remove(struct platform_device *pdev) > +{ > + struct aml_vdec_dev *dev = platform_get_drvdata(pdev); > + > + vdec_disable(dev->dec_hw); > + > + if (media_devnode_is_registered(dev->mdev.devnode)) { > + media_device_unregister(&dev->mdev); > + media_device_cleanup(&dev->mdev); > + } > + > + if (dev->m2m_dev_dec) > + v4l2_m2m_release(dev->m2m_dev_dec); > + if (dev->vfd) > + video_unregister_device(dev->vfd); > + if (dev->dec_hw) { > + dev->pvdec_data->destroy_hw_resource(dev); > + dev->dec_hw = NULL; > + } > + v4l2_device_unregister(&dev->v4l2_dev); > +} [Severity: High] If a process keeps the video file descriptor open when the device is unbound, devm_kzalloc will free the dev structure after this remove function completes. When the file descriptor is eventually closed, fops_vcodec_release will execute and attempt to access dev->dev_mutex. Will this lead to a use-after-free on the dev structure? > diff --git a/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c > new file mode 100644 > index 0000000000000..79ba2a68bde41 > --- /dev/null > +++ b/drivers/media/platform/amlogic/vdec/aml_vdec_hw.c [ ... ] > +static int vdec_clock_gate_init(struct aml_vdec_hw *hw) > +{ > + hw->gates[CLK_DOS].id = "dos"; > + hw->gates[CLK_VDEC].id = "vdec"; > + hw->gates[CLK_HEVCF].id = "hevcf"; > + > + return devm_clk_bulk_get(hw->dev, CLK_MAX, hw->gates); > +} [ ... ] > +static void pm_vdec_power_domain_power_on(struct aml_vdec_hw *hw, int id) > +{ > + const struct power_manager_s *pm = hw->pm; > + struct device *dev = pm->pd_data[id].dev; > + struct clk_bulk_data *gate_node = NULL; > + > + if (id == VDEC_1) > + gate_node = vdec_get_clk_by_name(hw, "vdec"); > + else if (id == VDEC_HEVC) > + gate_node = vdec_get_clk_by_name(hw, "hevcf"); > + > + if (gate_node) { > + clk_prepare_enable(gate_node->clk); [Severity: High] While the "vdec" and "hevcf" clocks are enabled here, isn't the "dos" clock omitted entirely? If the "dos" clock remains gated, could accessing DOS_BUS registers result in a synchronous external abort? > + if (id == VDEC_1) { > + clk_set_rate(gate_node->clk, 499999992); > + dev_dbg(hw->dev, "after set, vdec clock is %lu Hz\n", > + clk_get_rate(gate_node->clk)); > + } > + dev_dbg(hw->dev, "the %-15s clock on\n", gate_node->id); > + } else { > + dev_info(hw->dev, "clk %d, unreachable\n", id); > + } > + > + if (dev) { > + pm_runtime_get_sync(dev); > + dev_dbg(dev, "dev: %p link %p the %-15s power on\n", > + dev, pm->pd_data[id].link, pm->pd_data[id].name); > + } > + > + dos_local_config(hw, 1, id); [Severity: Medium] Should the return value of pm_runtime_get_sync() be checked before calling dos_local_config()? If the power domain fails to power up, accessing the hardware registers could lead to a bus hang. > +} [ ... ] > +static void vdec_poweron(struct aml_vdec_hw *hw, enum vdec_type_e core) > +{ > + if (core >= VDEC_MAX) > + return; > + > + mutex_lock(&hw->pm_mutex); > + if (!hw->pm->pd_data[core].dev) > + goto out; > + > + hw->pm->pd_data[core].ref_count++; > + if (hw->pm->pd_data[core].ref_count > 1) > + goto out; > + > + if (hw->pm->power_state(hw, core)) > + goto out; [Severity: High] If the power domain is already active, this code jumps past power_on() but still increments ref_count. Won't the subsequent teardown decrement ref_count and call power_off() unconditionally, causing unbalanced clk_disable_unprepare() and pm_runtime_put_sync() calls? > + > + hw->pm->power_on(hw, core); > + > +out: > + mutex_unlock(&hw->pm_mutex); > +} [ ... ] > +int dev_request_hw_resources(void *priv) > +{ [ ... ] > + hw->canvas = meson_canvas_get(&pdev->dev); > + if (IS_ERR(hw->canvas)) > + return PTR_ERR(hw->canvas); > + > + sm_np = of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0); > + if (IS_ERR(sm_np)) > + return PTR_ERR(hw->canvas); [Severity: Medium] Since of_parse_phandle() returns NULL on failure rather than an ERR_PTR, doesn't the IS_ERR check fail to catch a missing node? Passing NULL to meson_sm_get() later might cause issues. Also, why is PTR_ERR(hw->canvas) returned here instead of an appropriate error code? > + > + hw->sec_fw = meson_sm_get(sm_np); > + of_node_put(sm_np); > + if (IS_ERR(hw->sec_fw)) > + return PTR_ERR(hw->sec_fw); [ ... ] > diff --git a/drivers/media/platform/amlogic/vdec/h264.c b/drivers/media/platform/amlogic/vdec/h264.c > new file mode 100644 > index 0000000000000..bd3aef44409f3 > --- /dev/null > +++ b/drivers/media/platform/amlogic/vdec/h264.c [ ... ] > +static void config_sps_params(struct aml_h264_ctx *h264_ctx, > + unsigned short *sps_base, > + const struct v4l2_ctrl_h264_sps *sps) > +{ > + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; > + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); > + u32 cfg_tmp = 0; > + u32 frame_size; > + u32 offset = 0; > + unsigned short data_tmp[0x100]; > + int i, ii; > + > + memset(sps_base, 0, 0x100); > + > + h264_ctx->frame_width = (sps->pic_width_in_mbs_minus1 + 1) << 4; > + h264_ctx->frame_height = (sps->pic_height_in_map_units_minus1 + 1) << 4; [Severity: High] Does this code validate userspace-provided SPS controls? For example, if max_num_ref_frames is artificially large, could it overflow colocated arrays? Likewise, could an overly large pic_width_in_mbs_minus1 cause integer overflows when calculating allocation sizes? > + > + data_tmp[offset] = PARAM_BASE_VAL; > + offset += 2; [ ... ] > +static int allocate_canvas_pos(struct aml_h264_ctx *h264_ctx, int poc) > +{ > + int i; > + int ret = -1; > + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; > + > + for (i = 0; i < (V4L2_H264_NUM_DPB_ENTRIES + 1); i++) { > + if (((h264_ctx->canvas_pos_map >> i) & 0x1) == 0) { > + h264_ctx->canvas_pos_map |= (1 << i); > + h264_ctx->ref_canvas[i].poc = poc; > + h264_ctx->ref_canvas[i].canvas_pos = i; > + ret = i; > + > + dev_dbg(&ctx->dev->plat_dev->dev, > + "%s i %d pos_poc %d\n", __func__, i, > + h264_ctx->ref_canvas[i].poc); > + break; > + } > + } > + > + return ret; > +} > + > +static void release_canvas_pos(struct aml_h264_ctx *h264_ctx, int index) > +{ > + struct aml_vdec_ctx *ctx = h264_ctx->v4l2_ctx; > + > + if (index >= 0) { > + if (index > V4L2_H264_NUM_DPB_ENTRIES) { > + dev_dbg(&ctx->dev->plat_dev->dev, > + "%s error, index %d is bigger than buf count %d\n", > + __func__, index, h264_ctx->max_num_ref_frames); > + } else { > + if (h264_ctx->ref_canvas[index].poc != INVALID_POC && > + ((h264_ctx->canvas_pos_map >> index) & 0x1) == > + 0x1) { > + h264_ctx->canvas_pos_map &= (~(1 << index)); > + dev_dbg(&ctx->dev->plat_dev->dev, > + "%s canvas_pos index %d released poc %d, canvas_pos_map 0x%x\n", > + __func__, index, h264_ctx->ref_canvas[index].poc, > + h264_ctx->canvas_pos_map); > + h264_ctx->ref_canvas[index].poc = INVALID_POC; > + h264_ctx->ref_canvas[index].canvas_pos = -1; > + } > + } > + } > +} [ ... ] > +static void h264_config_decode_spec(struct aml_vdec_hw *hw, struct aml_vdec_ctx *ctx) > +{ > + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx; > + struct vdec_h264_stateless_ctrl_ref *ctrls = &h264_ctx->ctrl_ref; > + struct v4l2_ctrl_h264_decode_params *decode = > + (struct v4l2_ctrl_h264_decode_params *)ctrls->decode; > + struct h264_decode_buf_spec *buf_spec_l0, *buf_spec_l1; > + struct vb2_buffer *vb; > + struct vb2_v4l2_buffer *vb2_v4l2; > + struct vb2_queue *vq; > + int i; > + > + clear_unused_col_buf(h264_ctx, decode); > + > + vb2_v4l2 = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); > + vb = &vb2_v4l2->vb2_buf; [Severity: High] If the destination queue is empty when this ISR runs, won't v4l2_m2m_next_dst_buf return NULL? This would result in an immediate NULL pointer dereference. > + > + h264_ctx->curr_spec.y_dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); > + if (ctx->pic_info.plane_num > 1) > + h264_ctx->curr_spec.c_dma_addr = > + vb2_dma_contig_plane_dma_addr(vb, 1); > + else > + h264_ctx->curr_spec.c_dma_addr = > + h264_ctx->curr_spec.y_dma_addr + ctx->pic_info.fb_size[0]; > + h264_ctx->curr_spec.canvas_pos = > + allocate_canvas_pos(h264_ctx, decode->top_field_order_cnt); > + if (h264_ctx->curr_spec.canvas_pos < 0) [Severity: High] Since canvas_pos is a u32 (inside struct h264_decode_buf_spec), isn't this comparison (< 0) always false? This causes the code to proceed with an invalid canvas position if allocate_canvas_pos() fails. > + dev_err(&ctx->dev->plat_dev->dev, "curr_spec.canvas error\n"); > + > + if (decode->nal_ref_idc) > + h264_ctx->curr_spec.col_buf_index = > + allocate_colocate_buf(h264_ctx, > + decode->top_field_order_cnt); [ ... ] > +static void get_canvas_index(struct aml_vdec_hw *hw, struct aml_vdec_ctx *ctx) > +{ > + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)hw->curr_ctx; > + int i; > + struct h264_decode_buf_spec *buf; > + > + config_decode_canvas(hw, &h264_ctx->curr_spec, > + h264_ctx->mb_width, h264_ctx->mb_height); > + if (h264_ctx->list_size[0] > 0) { > + for (i = 0; i < h264_ctx->list_size[0]; i++) { > + buf = &h264_ctx->ref_list0[i]; > + config_decode_canvas(hw, buf, h264_ctx->mb_width, > + h264_ctx->mb_height); > + } > + } > + > + if (h264_ctx->list_size[1] > 0) { > + for (i = 0; i < h264_ctx->list_size[1]; i++) { > + buf = &h264_ctx->ref_list1[i]; > + config_decode_canvas(hw, buf, h264_ctx->mb_width, > + h264_ctx->mb_height); > + } > + } [Severity: High] If a reference frame is shared between list0 and list1, will config_decode_canvas() be called twice for the same buffer, causing a redundant canvas allocation and leaking the hardware canvas pool? > +} > + > +static void release_canvas_index(struct aml_vdec_hw *hw, > + struct h264_decode_buf_spec *buf) > +{ > + if (buf->y_canvas_index >= 0) { [Severity: High] Because y_canvas_index is declared as u8, won't this check always be true? If it's previously set to -1, it wraps to 255, causing the driver to spuriously free canvas 255. > + dev_dbg(hw->dev, "free y_canvas %d\n", buf->y_canvas_index); > + meson_canvas_free(hw->canvas, buf->y_canvas_index); > + buf->y_canvas_index = -1; > + } > + > + if (buf->u_canvas_index >= 0) { > + dev_dbg(hw->dev, "free uv_canvas_index %d\n", > + buf->u_canvas_index); > + meson_canvas_free(hw->canvas, buf->u_canvas_index); > + buf->u_canvas_index = -1; > + buf->v_canvas_index = -1; > + } > +} [ ... ] > +static irqreturn_t h264_threaded_isr_func(int irq, void *priv) > +{ > + u32 dec_status; > + struct aml_vdec_dev *dev = (struct aml_vdec_dev *)priv; > + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)dev->dec_hw->curr_ctx; > + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)dev->dec_ctx; > + struct aml_vdec_hw *hw = vdec_get_hw(ctx->dev); > + unsigned short *p = (unsigned short *)h264_ctx->lmem_addr; > + int i, ii; > + > + regmap_read(hw->map[DOS_BUS], DPB_STATUS_REG, &dec_status); > + h264_ctx->dec_status = dec_status; > + dev_dbg > + (&dev->plat_dev->dev, > + "%s, dec_status 0x%x VIFF_BIT_CNT 0x%x MBY_MBX 0x%x VLD_SHIFT_STATUS 0x%x\n", > + __func__, dec_status, read_dos_reg(hw, VIFF_BIT_CNT), > + read_dos_reg(hw, MBY_MBX), read_dos_reg(hw, VLD_SHIFT_STATUS)); > + > + regmap_read(hw->map[DOS_BUS], AV_SCRATCH_F, &h264_ctx->save_avscratch_f); > + > + switch (dec_status) { > + case H264_SLICE_HEADER_DONE: > + for (i = 0; i < 0x400; i += 4) > + for (ii = 0; ii < 4; ii++) > + h264_ctx->dpb_param.l.data[i + ii] = p[i + 3 - ii]; [Severity: Medium] Since lmem_addr holds DMA memory populated by the hardware, should a dma_rmb() barrier be issued before reading from `p` to ensure the CPU doesn't observe stale cached data? > + save_reg_status(h264_ctx); > + h264_get_slice_params(h264_ctx); > + if (h264_ctx->mslice.first_mb_in_slice != 0) > + h264_release_decode_spec(hw, ctx); > + > + h264_config_decode_spec(hw, ctx); > + h264_reorder_reflists(h264_ctx); > + get_canvas_index(hw, ctx); > + > + if (h264_config_decode_buf(hw, ctx) < 0) { > + h264_release_decode_spec(hw, ctx); > + ctx->int_cond = 1; > + wake_up_interruptible(&ctx->queue); > + goto irq_handled; [Severity: High] If the execution takes this error path, ctx->int_cond = 1 satisfies the wait condition in m2mops_vdec_device_run(), which then returns directly without canceling the job. Does this omit a necessary v4l2_m2m_buf_done_and_job_finish() call and permanently stall the mem2mem context? > + } > + if (h264_ctx->new_pic_flag == 1) { > + regmap_write(hw->map[DOS_BUS], DPB_STATUS_REG, H264_ACTION_DECODE_NEWPIC); > + dev_dbg(&dev->plat_dev->dev, "action decode new pic\n"); > + h264_ctx->new_pic_flag = 0; > + } else { > + regmap_write(hw->map[DOS_BUS], DPB_STATUS_REG, H264_ACTION_DECODE_SLICE); > + dev_dbg(&dev->plat_dev->dev, "action decode new slice\n"); > + } > + break; [ ... ] > +static int aml_h264_load_fw_ext(void *priv, const u8 *data, u32 len) > +{ > + struct aml_h264_ctx *h264_ctx = (struct aml_h264_ctx *)priv; > + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)h264_ctx->v4l2_ctx; > + struct aml_vdec_hw *dec_hw; > + > + if (h264_ctx->mc_cpu_loaded) > + return 0; > + > + dec_hw = vdec_get_hw(ctx->dev); > + if (!dec_hw) > + return -1; > + > + if (len > MC_TOTAL_SIZE) { > + dev_info(dec_hw->dev, "size of mc_cpu_fw id invalid\n"); > + return -1; > + } [Severity: Medium] Should the firmware length also be checked against a minimum bounds before calling copy_mc_cpu_fw()? Passing truncated firmware could cause out-of-bounds reads when accessing fixed offsets (e.g., data + 0x6000) inside copy_mc_cpu_fw(). > + > + h264_ctx->mc_cpu_vaddr = dma_alloc_coherent(dec_hw->dev, MC_TOTAL_SIZE, > + &h264_ctx->mc_cpu_paddr, > + GFP_KERNEL); > + if (!h264_ctx->mc_cpu_vaddr) > + return -ENOMEM; > + > + copy_mc_cpu_fw(h264_ctx->mc_cpu_vaddr, data); > + > + h264_ctx->mc_cpu_loaded = true; > + > + dev_dbg(dec_hw->dev, "h264 mccpu fw loaded\n"); > + > + return 0; > +} > + > +int aml_h264_init(void *priv) > +{ > + struct aml_vdec_ctx *ctx = (struct aml_vdec_ctx *)priv; > + struct aml_vdec_hw *dec_hw; > + struct aml_h264_ctx *h264_ctx; > + int ret = 0; > + > + h264_ctx = kzalloc_obj(*h264_ctx, GFP_KERNEL); > + if (!h264_ctx) > + return -ENOMEM; > + > + h264_ctx->v4l2_ctx = ctx; > + dec_hw = vdec_get_hw(ctx->dev); > + if (!dec_hw) > + return -1; > + > + h264_ctx->mc_cpu_loaded = false; > + dec_hw->hw_ops.irq_handler = h264_isr; > + dec_hw->hw_ops.irq_threaded_func = h264_threaded_isr_func; > + dec_hw->hw_ops.load_firmware_ex = aml_h264_load_fw_ext; > + > + h264_ctx->lmem_addr = dma_alloc_coherent(dec_hw->dev, LMEM_DUMP_SIZE, > + &h264_ctx->lmem_phy_addr, > + GFP_KERNEL); > + if (!h264_ctx->lmem_addr) { > + ret = -ENOMEM; > + goto err_alloc_lmem; > + } > + > + h264_ctx->cma_alloc_vaddr = > + dma_alloc_coherent(dec_hw->dev, V_BUF_ADDR_OFFSET, > + &h264_ctx->cma_alloc_addr, GFP_KERNEL); > + if (!h264_ctx->cma_alloc_vaddr) { > + ret = -ENOMEM; > + goto err_alloc_workspace; > + } > + > + h264_ctx->workspace_offset = h264_ctx->cma_alloc_addr + DCAC_READ_MARGIN; > + h264_ctx->workspace_vaddr = h264_ctx->cma_alloc_vaddr + DCAC_READ_MARGIN; > + > + ctx->codec_priv = h264_ctx; > + dec_hw->curr_ctx = h264_ctx; [Severity: Critical] Similar to the issue in fops_vcodec_open(), could assigning the context to dec_hw->curr_ctx here break isolation across multiple concurrent instances? When multiple streams are active, the ISR will blindly dereference curr_ctx, potentially targeting the wrong or freed context. > + h264_ctx->col_buf_alloc_size = 0; > + h264_ctx->init_flag = 0; > + h264_ctx->new_pic_flag = 0; > + h264_ctx->param_set = 0; > + h264_ctx->reg_iqidct_control_init_flag = 0; > + h264_ctx->decode_pic_count = 0; > + > + return 0; -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4 at amlogic.com?part=4 From jian.hu at amlogic.com Tue May 26 03:05:01 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Tue, 26 May 2026 18:05:01 +0800 Subject: [PATCH 00/10] Add support for A9 family clock controller In-Reply-To: <1jldd662x1.fsf@starbuckisacylon.baylibre.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <1jldd662x1.fsf@starbuckisacylon.baylibre.com> Message-ID: <13da0733-32cf-4923-a923-21e517d1594a@amlogic.com> On 5/26/2026 3:33 PM, Jerome Brunet wrote: > [ EXTERNAL EMAIL ] > > On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: > >> There are 4 clock controllers in A9 SoC: >> - SCMI clock controller: these clocks are managed by the >> Trusted Firmware-A(TF-A) and handled through SCMI. >> - PLL clock controller. >> - peripheral clock controller. >> - AO clock controller. >> >> There are reserved register regions placed between individual PLLs, so a >> separate driver is implemented for each PLL, similar to T7. >> >> Compared to previous SoCs PLLs, the A9 PLL controller introduces 4 new features: >> 1.PLL l_detect signal supports active-high configuration. >> Previous A7 and T7 l_detect signals are active-low. >> 2.PLL reset signal supports active-low configuration. >> Previous reset signals are active-high. >> 3.Support POWER_OF_TWO for the PLL pre-divider N; >> the N pre-divider follows the same calculation rule as OD. >> 4.The PLL input path includes an inherent divide-by-2 divider. >> >> Implement the first three features in clk-pll.c (verified on A9 and T7), >> with no impact to PLL logic on existing SoCs. Add a fixed divide-by-2 to >> A9 PLL driver for the fourth feature. >> >> A9 PLL is composed as follows: >> >> PLL >> +---------------------------------+ >> | | >> | +--+ | >> in/2 >>---[ /2^N ]-->| | +-----+ | >> | | |------| DCO |----->> out >> | +--------->| | +--v--+ | >> | | +--+ | | >> | | | | >> | +--[ *(M + (F/Fmax) ]<--+ | >> | | >> +---------------------------------+ >> >> out = in / 2 * (m + frac / frac_max) / 2^n >> >> Signed-off-by: Jian Hu >> --- >> Jian Hu (10): >> dt-bindings: clock: Add Amlogic A9 SCMI clock controller >> dt-bindings: clock: Add Amlogic A9 PLL clock controller >> dt-bindings: clock: Add Amlogic A9 peripherals clock controller >> dt-bindings: clock: Add Amlogic A9 AO clock controller >> clk: amlogic: PLL l_detect signal supports active-high configuration >> clk: amlogic: PLL reset signal supports active-low configuration >> clk: amlogic: Support POWER_OF_TWO for PLL pre-divider >> clk: amlogic: Add A9 PLL clock controller driver >> clk: amlogic: Add A9 peripherals clock controller driver >> clk: amlogic: Add A9 AO clock controller driver >> >> .../bindings/clock/amlogic,a9-aoclkc.yaml | 76 + >> .../clock/amlogic,a9-peripherals-clkc.yaml | 150 ++ >> .../bindings/clock/amlogic,a9-pll-clkc.yaml | 110 + >> drivers/clk/meson/Kconfig | 28 + >> drivers/clk/meson/Makefile | 2 + >> drivers/clk/meson/a9-aoclk.c | 494 +++++ >> drivers/clk/meson/a9-peripherals.c | 2317 ++++++++++++++++++++ >> drivers/clk/meson/a9-pll.c | 831 +++++++ >> drivers/clk/meson/clk-pll.c | 79 +- >> drivers/clk/meson/clk-pll.h | 6 + >> include/dt-bindings/clock/amlogic,a9-aoclkc.h | 76 + >> .../clock/amlogic,a9-peripherals-clkc.h | 352 +++ >> include/dt-bindings/clock/amlogic,a9-pll-clkc.h | 55 + >> include/dt-bindings/clock/amlogic,a9-scmi-clkc.h | 51 + >> 14 files changed, 4609 insertions(+), 18 deletions(-) > For the next version, please split things up. > There is no hard dependency between the different controllers. This will > ease the review. > > The PLL controllers are bringing a new contraints in. The global/static > nature of the controllers is something that has been bothering me for a > while but there was no real reason to address it so far. Please give me > some time to think about. Feel free to re-post the other controllers in the > meantime. Ok. I will split the series and re-post the SCMI, peripherals, and aoclk controllers separately. Best regards, Jian >> --- >> base-commit: ca89c88bcf69daca829044c638a8163d5ce47af0 >> change-id: 20260511-b4-a9_clk-67652c1ae56e >> >> Best regards, > -- > Jerome From sashiko-bot at kernel.org Tue May 26 03:38:19 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Tue, 26 May 2026 10:38:19 +0000 Subject: [PATCH RFC RESEND v5 5/6] arm64: dts: amlogic: Add video decoder driver support for S4 SOCs In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-5-33bc817f93f4@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-5-33bc817f93f4@amlogic.com> Message-ID: <20260526103819.AFEA71F00A3A@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] The patch silently introduces an `optee` node which is not mentioned in the commit message, and places it incorrectly at the root of the device tree. - [Low] The newly added `canvas` and `video-codec` nodes break the expected unit-address sorting within the `soc` simple-bus node. -- commit 9f1bc8bd76d58f7598508700fcc37888ae1dbf0f Author: Zhentao Guo arm64: dts: amlogic: Add video decoder driver support for S4 SOCs This commit adds the vdec node to enable Amlogic V4L2 stateless video decoder support for S4 SOCs. It also introduces an optee node and a canvas node required for the decoder. > diff --git a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi > --- a/arch/arm64/boot/dts/amlogic/meson-s4.dtsi > +++ b/arch/arm64/boot/dts/amlogic/meson-s4.dtsi > @@ -96,6 +96,11 @@ saradc: adc at fe026000 { > status = "disabled"; > }; > > + optee { > + compatible = "linaro,optee-tz"; > + method = "smc"; > + }; [Severity: Medium] Should this optee node addition be explicitly mentioned in the commit message? The commit description currently only mentions adding the vdec node. Also, should this node be placed under a firmware node instead of at the root level of the device tree? > soc { > compatible = "simple-bus"; > #address-cells = <2>; [ ... ] > @@ -907,5 +912,34 @@ emmc: mmc at fe08c000 { > assigned-clocks = <&clkc_periphs CLKID_SD_EMMC_C>; > assigned-clock-rates = <24000000>; > }; > + > + canvas: video-lut at fe036048 { > + compatible = "amlogic,canvas"; > + reg = <0x0 0xfe036048 0x0 0x14>; > + }; > + > + video-codec at fe320000 { [Severity: Low] Does appending these nodes break the expected unit-address sorting within the soc simple-bus node? The canvas node at fe036048 and the video-codec node at fe320000 appear to be placed at the end of the block, after mmc at fe08c000, rather than being ordered sequentially. > + compatible = "amlogic,s4-vdec"; > + reg = <0x0 0xfe320000 0x0 0x10000>, > + <0x0 0xfe036000 0x0 0x20>; [ ... ] -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4 at amlogic.com?part=5 From sashiko-bot at kernel.org Tue May 26 03:52:44 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Tue, 26 May 2026 10:52:44 +0000 Subject: [PATCH RFC RESEND v5 6/6] arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC In-Reply-To: <20260526-b4-s4-vdec-upstream-v5-6-33bc817f93f4@amlogic.com> References: <20260526-b4-s4-vdec-upstream-v5-6-33bc817f93f4@amlogic.com> Message-ID: <20260526105245.585C01F000E9@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Low] CONFIG_VIDEO_AMLOGIC_VDEC selects MESON_SM without satisfying its ARM64_4K_PAGES dependency, causing Kconfig warnings under COMPILE_TEST or when using 16K/64K pages. - [Low] CONFIG_VIDEO_AMLOGIC_VDEC is inserted manually out of order in arm64 defconfig. -- commit 482a11927128f3e0aa6ba1ce2517d29726af8f3b Author: Zhentao Guo arm64: defconfig: Enable CONFIG_VIDEO_AMLOGIC_VDEC This commit enables the Amlogic V4L2 stateless video decoder driver as a module in the arm64 defconfig. > diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig > index 4567f4b34f293..14caac24d2009 100644 > --- a/arch/arm64/configs/defconfig > +++ b/arch/arm64/configs/defconfig > @@ -913,6 +913,7 @@ CONFIG_V4L_PLATFORM_DRIVERS=y > CONFIG_SDR_PLATFORM_DRIVERS=y > CONFIG_V4L_MEM2MEM_DRIVERS=y > CONFIG_VIDEO_AMPHION_VPU=m > +CONFIG_VIDEO_AMLOGIC_VDEC=m [Severity: Low] Does enabling this configuration expose a dependency issue in the driver's Kconfig? In the Kconfig definition for VIDEO_AMLOGIC_VDEC introduced earlier in this patchset, it unconditionally uses select MESON_SM. However, MESON_SM has a strict dependency on ARM64_4K_PAGES. Selecting a symbol without meeting its dependencies forces it on, which can trigger unmet direct dependency warnings during builds on architectures or configurations where ARM64_4K_PAGES is not met. This can happen on non-ARM64 COMPILE_TEST builds or ARM64 builds configured with 16K or 64K pages. Could the driver use depends on MESON_SM instead of select to avoid breaking automated Kconfig build testing? [Severity: Low] This isn't a bug, but was this defconfig change generated using make savedefconfig? In the media platform Kconfig hierarchy, the Amlogic platform Kconfig is included before the Amphion platform Kconfig. Running make savedefconfig would naturally place CONFIG_VIDEO_AMLOGIC_VDEC before CONFIG_VIDEO_AMPHION_VPU. Manually inserting it after Amphion will cause unnecessary churn the next time make savedefconfig is executed. > CONFIG_VIDEO_CADENCE_CSI2RX=m > CONFIG_VIDEO_WAVE_VPU=m > CONFIG_VIDEO_E5010_JPEG_ENC=m -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260526-b4-s4-vdec-upstream-v5-0-33bc817f93f4 at amlogic.com?part=6 From jbrunet at baylibre.com Tue May 26 05:27:12 2026 From: jbrunet at baylibre.com (Jerome Brunet) Date: Tue, 26 May 2026 14:27:12 +0200 Subject: [PATCH 07/10] clk: amlogic: Support POWER_OF_TWO for PLL pre-divider In-Reply-To: <3fda1592-f7d0-4e86-8615-602804673414@amlogic.com> (Jian Hu's message of "Tue, 26 May 2026 17:58:19 +0800") References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-7-41cb4071b7c9@amlogic.com> <1jy0hm6n7e.fsf@starbuckisacylon.baylibre.com> <8d89b669-e72e-4663-9596-999a12922d32@amlogic.com> <1jqzn65y9l.fsf@starbuckisacylon.baylibre.com> <3fda1592-f7d0-4e86-8615-602804673414@amlogic.com> Message-ID: <1ja4tm5pb3.fsf@starbuckisacylon.baylibre.com> On mar. 26 mai 2026 at 17:58, Jian Hu wrote: > On 5/20/2026 3:35 PM, Jerome Brunet wrote: >> [ EXTERNAL EMAIL ] >> >> On mer. 20 mai 2026 at 13:47, Jian Hu wrote: >> >>> On 5/14/2026 11:11 PM, Jerome Brunet wrote: >>>> [ EXTERNAL EMAIL ] >>>> >>>> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: >>>> >>>>> From: Jian Hu >>>>> >>>>> The A9 PLL pre-divider uses a division factor of 2^n to ensure a clock >>>>> duty cycle of 50% after predivision. >>>>> >>>>> Add flag 'CLK_MESON_PLL_N_POWER_OF_TWO' to indicate that the PLL >>>>> pre-divider division factor is 2^n. >>>> I understand what you are doing here but I have to ask why this can't be >>>> implemented with independent dividers that already supports power of 2 ? >>> >>> If we use independent dividers, the n member would have to be removed from >>> meson_clk_pll_data. >>> >>> However, n is referenced 35 times in clk-pll.c, which means we would need >>> to modify all >>> related logic across the file. This would be a relatively large >>> change. >> Yes >> >>> >>> Moreover, for all Amlogic chips, the n divider is an indispensable part of >>> the DCO clock. >> There is hardly a justification here >> >>> The difference between SoC generations is as follows: >>> Previous SoCs PLL: n = 1, 2, 3, 4... (linear divider) >>> A9 SoC PLL: n = 2^0, 2^1, 2^2, 2^3, 2^4... (power-of-two >>> divider) >> Yes that was fairly obvious >> >>> Therefore, splitting out the n divider from the DCO clock might not be a >>> good design choice. >> I'm not sure I agree and you've only stated your point of view without >> providing any technical justification here. >> >> From the datasheets of the different SoC we have, the documented >> limitation is always the DCO output rate range. Nothing related to n (or >> m, or the mult-range for that matter). This is a legacy problem, we >> started with monolithic driver and slowly simplified it. >> >> As far as I can see now, reworking the PLL driver to be a simple >> multiplier driver with range output rate constraint could actually be >> simpler than the current code. I would also make simpler to accomodate >> differences such as the one presented here. >> >> Unless you can provide technical reasons why going in this direction >> would be incorrect, that's where I'd prefer to go. >> >>> [...] >>> >>> Best regards, >>> >>> Jian >> -- >> Jerome > > > I agree that having an independent N divider would simplify the PLL rate > calculation. > > A separate pre-divider for N is technically possible, but there are some > hardware constraints that need to be considered: > > N = 1 is the preferred operating mode except a few fixed-frequency PLLs. > Larger N values reduce the PLL phase detector frequency, which may > negatively impact > jitter performance and overall PLL stability. Understood. You could really make a difference by going deeper and explaining what those constraints are, especially since you ask question internally at Amlogic. At the moment what is documented is a range regarding the output rate of the PLLs. A PLL is made of a pre-divider and fractional multiplier. and you are saying that for the multiplier to work and lock, there is actually a constraint the input rate too. If you can discuss with your HW team and clarify what the constraints really are, that would help to better model the PLL. In then more likely for us to figure out the best way to drive it. > > Because of this, we cannot guarantee stable system operation when arbitrary > larger > N values are used. > > Some PLLs require non-1 N values to generate specific fixed output > frequencies because > the target rate cannot be achieved with N = 1 while keeping the PLL while > keeping the > PLL within its valid operating range. So N is designed to have other values > ??to > satisfy this requirement. Again it seems like the constraints we are using are not the real limitation, just by-products, which the situation unclear. > > For example, the AXG PCIe PLL uses N = 3 to generate the required 100 MHz > output frequency, > since the target frequency cannot be achieved with N = 1. > PCIe is a topic in itself. It uses different ops for historic reasons though I suspect, with proper constraints, it would not really need to. > > Additionally, is the refactored pre-divider N implemented as a separate > patchset, > independent from the A9 PLL changes? I could be seen as a pre-requisite. > > > Best regards, > > > Jian -- Jerome From daniel.lezcano at oss.qualcomm.com Tue May 26 07:08:07 2026 From: daniel.lezcano at oss.qualcomm.com (Daniel Lezcano) Date: Tue, 26 May 2026 16:08:07 +0200 Subject: [PATCH v4 06/10] thermal/of: Rename the devm_thermal_of_cooling_device_register() function In-Reply-To: <20260526140802.1059293-12-daniel.lezcano@oss.qualcomm.com> References: <20260526140802.1059293-12-daniel.lezcano@oss.qualcomm.com> Message-ID: <20260526140802.1059293-18-daniel.lezcano@oss.qualcomm.com> To clarify that the function operates on child nodes, rename: devm_thermal_of_cooling_device_register() | v devm_thermal_of_child_cooling_device_register() Used the command: find . -type f -name '*.[ch]' -exec \ sed -i 's/devm_thermal_of_cooling_device_register/\ devm_thermal_of_child_cooling_device_register/g' {} \; Did not used clang-format-diff because it does not indent correctly and checkpatch complained. Manually reindented to make checkpatch happy This prepares for upcoming support of cooling devices identified by an ID rather than device tree child nodes. No functional change. Signed-off-by: Daniel Lezcano --- drivers/hwmon/amc6821.c | 2 +- drivers/hwmon/aspeed-pwm-tacho.c | 5 +++-- drivers/hwmon/emc2305.c | 6 +++--- drivers/hwmon/gpio-fan.c | 6 ++++-- drivers/hwmon/max6650.c | 6 +++--- drivers/hwmon/npcm750-pwm-fan.c | 6 ++++-- drivers/hwmon/pwm-fan.c | 5 +++-- drivers/hwmon/qnap-mcu-hwmon.c | 6 +++--- drivers/hwmon/tc654.c | 5 +++-- drivers/memory/tegra/tegra210-emc-core.c | 4 ++-- drivers/soc/qcom/qcom_aoss.c | 2 +- drivers/thermal/khadas_mcu_fan.c | 7 ++++--- drivers/thermal/tegra/soctherm.c | 6 +++--- drivers/thermal/thermal_of.c | 15 +++++++++------ include/linux/thermal.h | 16 ++++++++-------- 15 files changed, 54 insertions(+), 43 deletions(-) diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c index d5f864b360b0..8e5926b06070 100644 --- a/drivers/hwmon/amc6821.c +++ b/drivers/hwmon/amc6821.c @@ -1076,7 +1076,7 @@ static int amc6821_probe(struct i2c_client *client) "Failed to initialize hwmon\n"); if (IS_ENABLED(CONFIG_THERMAL) && fan_np && data->fan_cooling_levels) - return PTR_ERR_OR_ZERO(devm_thermal_of_cooling_device_register(dev, + return PTR_ERR_OR_ZERO(devm_thermal_of_child_cooling_device_register(dev, fan_np, client->name, data, &amc6821_cooling_ops)); return 0; diff --git a/drivers/hwmon/aspeed-pwm-tacho.c b/drivers/hwmon/aspeed-pwm-tacho.c index aa159bf158a3..1c5945d4ba37 100644 --- a/drivers/hwmon/aspeed-pwm-tacho.c +++ b/drivers/hwmon/aspeed-pwm-tacho.c @@ -841,8 +841,9 @@ static int aspeed_create_pwm_cooling(struct device *dev, } snprintf(cdev->name, MAX_CDEV_NAME_LEN, "%pOFn%d", child, pwm_port); - cdev->tcdev = devm_thermal_of_cooling_device_register(dev, child, - cdev->name, cdev, &aspeed_pwm_cool_ops); + cdev->tcdev = devm_thermal_of_child_cooling_device_register(dev, child, + cdev->name, cdev, + &aspeed_pwm_cool_ops); if (IS_ERR(cdev->tcdev)) return PTR_ERR(cdev->tcdev); diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c index 64b213e1451e..2505e9fac499 100644 --- a/drivers/hwmon/emc2305.c +++ b/drivers/hwmon/emc2305.c @@ -309,9 +309,9 @@ static int emc2305_set_single_tz(struct device *dev, struct device_node *fan_nod pwm = data->pwm_min[cdev_idx]; data->cdev_data[cdev_idx].cdev = - devm_thermal_of_cooling_device_register(dev, fan_node, - emc2305_fan_name[idx], data, - &emc2305_cooling_ops); + devm_thermal_of_child_cooling_device_register(dev, fan_node, + emc2305_fan_name[idx], data, + &emc2305_cooling_ops); if (IS_ERR(data->cdev_data[cdev_idx].cdev)) { dev_err(dev, "Failed to register cooling device %s\n", emc2305_fan_name[idx]); diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c index a8892ced1e54..084828e1e281 100644 --- a/drivers/hwmon/gpio-fan.c +++ b/drivers/hwmon/gpio-fan.c @@ -592,8 +592,10 @@ static int gpio_fan_probe(struct platform_device *pdev) } /* Optional cooling device register for Device tree platforms */ - fan_data->cdev = devm_thermal_of_cooling_device_register(dev, np, - "gpio-fan", fan_data, &gpio_fan_cool_ops); + fan_data->cdev = devm_thermal_of_child_cooling_device_register(dev, np, + "gpio-fan", + fan_data, + &gpio_fan_cool_ops); dev_info(dev, "GPIO fan initialized\n"); diff --git a/drivers/hwmon/max6650.c b/drivers/hwmon/max6650.c index 56b8157885bb..3466edd7d501 100644 --- a/drivers/hwmon/max6650.c +++ b/drivers/hwmon/max6650.c @@ -794,9 +794,9 @@ static int max6650_probe(struct i2c_client *client) return err; if (IS_ENABLED(CONFIG_THERMAL)) { - cooling_dev = devm_thermal_of_cooling_device_register(dev, - dev->of_node, client->name, - data, &max6650_cooling_ops); + cooling_dev = devm_thermal_of_child_cooling_device_register(dev, dev->of_node, + client->name, data, + &max6650_cooling_ops); if (IS_ERR(cooling_dev)) { dev_warn(dev, "thermal cooling device register failed: %ld\n", PTR_ERR(cooling_dev)); diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c index c8f5e695fb6d..aea0b8659f5f 100644 --- a/drivers/hwmon/npcm750-pwm-fan.c +++ b/drivers/hwmon/npcm750-pwm-fan.c @@ -857,8 +857,10 @@ static int npcm7xx_create_pwm_cooling(struct device *dev, snprintf(cdev->name, THERMAL_NAME_LENGTH, "%pOFn%d", child, pwm_port); - cdev->tcdev = devm_thermal_of_cooling_device_register(dev, child, - cdev->name, cdev, &npcm7xx_pwm_cool_ops); + cdev->tcdev = devm_thermal_of_child_cooling_device_register(dev, child, + cdev->name, + cdev, + &npcm7xx_pwm_cool_ops); if (IS_ERR(cdev->tcdev)) return PTR_ERR(cdev->tcdev); diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index 37269db2de84..e6a567d58579 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -685,8 +685,9 @@ static int pwm_fan_probe(struct platform_device *pdev) ctx->pwm_fan_state = ctx->pwm_fan_max_state; if (IS_ENABLED(CONFIG_THERMAL)) { - cdev = devm_thermal_of_cooling_device_register(dev, - dev->of_node, "pwm-fan", ctx, &pwm_fan_cooling_ops); + cdev = devm_thermal_of_child_cooling_device_register(dev, dev->of_node, + "pwm-fan", ctx, + &pwm_fan_cooling_ops); if (IS_ERR(cdev)) { ret = PTR_ERR(cdev); dev_err(dev, diff --git a/drivers/hwmon/qnap-mcu-hwmon.c b/drivers/hwmon/qnap-mcu-hwmon.c index e86e64c4d391..c1c1e9d6f340 100644 --- a/drivers/hwmon/qnap-mcu-hwmon.c +++ b/drivers/hwmon/qnap-mcu-hwmon.c @@ -337,9 +337,9 @@ static int qnap_mcu_hwmon_probe(struct platform_device *pdev) * levels and only succeed with either no or correct cooling levels. */ if (IS_ENABLED(CONFIG_THERMAL) && hwm->fan_cooling_levels) { - cdev = devm_thermal_of_cooling_device_register(dev, - to_of_node(hwm->fan_node), "qnap-mcu-hwmon", - hwm, &qnap_mcu_hwmon_cooling_ops); + cdev = devm_thermal_of_child_cooling_device_register(dev, to_of_node(hwm->fan_node), + "qnap-mcu-hwmon", hwm, + &qnap_mcu_hwmon_cooling_ops); if (IS_ERR(cdev)) return dev_err_probe(dev, PTR_ERR(cdev), "Failed to register qnap-mcu-hwmon as cooling device\n"); diff --git a/drivers/hwmon/tc654.c b/drivers/hwmon/tc654.c index 39fe5836f237..ba18b442b81e 100644 --- a/drivers/hwmon/tc654.c +++ b/drivers/hwmon/tc654.c @@ -541,8 +541,9 @@ static int tc654_probe(struct i2c_client *client) if (IS_ENABLED(CONFIG_THERMAL)) { struct thermal_cooling_device *cdev; - cdev = devm_thermal_of_cooling_device_register(dev, dev->of_node, client->name, - hwmon_dev, &tc654_fan_cool_ops); + cdev = devm_thermal_of_child_cooling_device_register(dev, dev->of_node, + client->name, hwmon_dev, + &tc654_fan_cool_ops); return PTR_ERR_OR_ZERO(cdev); } diff --git a/drivers/memory/tegra/tegra210-emc-core.c b/drivers/memory/tegra/tegra210-emc-core.c index e96ca4157d48..065ae8bc2830 100644 --- a/drivers/memory/tegra/tegra210-emc-core.c +++ b/drivers/memory/tegra/tegra210-emc-core.c @@ -1966,8 +1966,8 @@ static int tegra210_emc_probe(struct platform_device *pdev) tegra210_emc_debugfs_init(emc); - cd = devm_thermal_of_cooling_device_register(emc->dev, np, "emc", emc, - &tegra210_emc_cd_ops); + cd = devm_thermal_of_child_cooling_device_register(emc->dev, np, "emc", emc, + &tegra210_emc_cd_ops); if (IS_ERR(cd)) { err = PTR_ERR(cd); dev_err(emc->dev, "failed to register cooling device: %d\n", diff --git a/drivers/soc/qcom/qcom_aoss.c b/drivers/soc/qcom/qcom_aoss.c index c255662b8fc3..259c41f0c34e 100644 --- a/drivers/soc/qcom/qcom_aoss.c +++ b/drivers/soc/qcom/qcom_aoss.c @@ -381,7 +381,7 @@ static int qmp_cooling_device_add(struct qmp *qmp, qmp_cdev->qmp = qmp; qmp_cdev->state = !qmp_cdev_max_state; qmp_cdev->name = cdev_name; - qmp_cdev->cdev = devm_thermal_of_cooling_device_register + qmp_cdev->cdev = devm_thermal_of_child_cooling_device_register (qmp->dev, node, cdev_name, qmp_cdev, &qmp_cooling_device_ops); diff --git a/drivers/thermal/khadas_mcu_fan.c b/drivers/thermal/khadas_mcu_fan.c index d35e5313bea4..21b3d0a71bd0 100644 --- a/drivers/thermal/khadas_mcu_fan.c +++ b/drivers/thermal/khadas_mcu_fan.c @@ -90,9 +90,10 @@ static int khadas_mcu_fan_probe(struct platform_device *pdev) ctx->mcu = mcu; platform_set_drvdata(pdev, ctx); - cdev = devm_thermal_of_cooling_device_register(dev->parent, - dev->parent->of_node, "khadas-mcu-fan", ctx, - &khadas_mcu_fan_cooling_ops); + cdev = devm_thermal_of_child_cooling_device_register(dev->parent, + dev->parent->of_node, + "khadas-mcu-fan", ctx, + &khadas_mcu_fan_cooling_ops); if (IS_ERR(cdev)) { ret = PTR_ERR(cdev); dev_err(dev, "Failed to register khadas-mcu-fan as cooling device: %d\n", diff --git a/drivers/thermal/tegra/soctherm.c b/drivers/thermal/tegra/soctherm.c index 6a56638c98f1..d8e988a0d43e 100644 --- a/drivers/thermal/tegra/soctherm.c +++ b/drivers/thermal/tegra/soctherm.c @@ -1707,9 +1707,9 @@ static void soctherm_init_hw_throt_cdev(struct platform_device *pdev) stc->init = true; } else { - tcd = devm_thermal_of_cooling_device_register(dev, np_stcc, - (char *)name, ts, - &throt_cooling_ops); + tcd = devm_thermal_of_child_cooling_device_register(dev, np_stcc, + (char *)name, ts, + &throt_cooling_ops); if (IS_ERR_OR_NULL(tcd)) { dev_err(dev, "throttle-cfg: %s: failed to register cooling device\n", diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 8c49d449d43f..b59d2588ff7a 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -557,7 +557,7 @@ static void thermal_of_cooling_device_release(void *data) } /** - * devm_thermal_of_cooling_device_register() - register an OF thermal cooling + * devm_thermal_of_child_cooling_device_register() - register an OF thermal cooling * device * @dev: a valid struct device pointer of a sensor device. * @np: a pointer to a device tree node. @@ -570,14 +570,17 @@ static void thermal_of_cooling_device_release(void *data) * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself * to all the thermal zone devices registered at the same time. * + * This function should be used when a cooling controller has child + * nodes which are referenced in the thermal zone cooling map. + * * Return: a pointer to the created struct thermal_cooling_device or an * ERR_PTR. Caller must check return value with IS_ERR*() helpers. */ struct thermal_cooling_device * -devm_thermal_of_cooling_device_register(struct device *dev, - struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +devm_thermal_of_child_cooling_device_register(struct device *dev, + struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) { struct thermal_cooling_device *cdev; int ret; @@ -592,4 +595,4 @@ devm_thermal_of_cooling_device_register(struct device *dev, return cdev; } -EXPORT_SYMBOL_GPL(devm_thermal_of_cooling_device_register); +EXPORT_SYMBOL_GPL(devm_thermal_of_child_cooling_device_register); diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 6d1862ac187f..e6328234a42b 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -205,10 +205,10 @@ thermal_of_cooling_device_register(struct device_node *np, const char *type, voi const struct thermal_cooling_device_ops *ops); struct thermal_cooling_device * -devm_thermal_of_cooling_device_register(struct device *dev, - struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops); +devm_thermal_of_child_cooling_device_register(struct device *dev, + struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops); #else static inline @@ -232,10 +232,10 @@ thermal_of_cooling_device_register(struct device_node *np, } static inline struct thermal_cooling_device * -devm_thermal_of_cooling_device_register(struct device *dev, - struct device_node *np, - const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +devm_thermal_of_child_cooling_device_register(struct device *dev, + struct device_node *np, + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) { return ERR_PTR(-ENODEV); } -- 2.43.0 From lkp at intel.com Tue May 26 19:17:16 2026 From: lkp at intel.com (kernel test robot) Date: Wed, 27 May 2026 10:17:16 +0800 Subject: [PATCH] drm/meson: clean up KMS polling on register failure In-Reply-To: <20260524160657.17802-1-mhun512@gmail.com> References: <20260524160657.17802-1-mhun512@gmail.com> Message-ID: <202605271010.KFK2tlLa-lkp@intel.com> Hi Myeonghun, kernel test robot noticed the following build warnings: [auto build test WARNING on drm-misc/drm-misc-next] [also build test WARNING on linus/master v7.1-rc5 next-20260526] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Myeonghun-Pak/drm-meson-clean-up-KMS-polling-on-register-failure/20260525-000807 base: https://gitlab.freedesktop.org/drm/misc/kernel.git drm-misc-next patch link: https://lore.kernel.org/r/20260524160657.17802-1-mhun512%40gmail.com patch subject: [PATCH] drm/meson: clean up KMS polling on register failure config: x86_64-allyesconfig (https://download.01.org/0day-ci/archive/20260527/202605271010.KFK2tlLa-lkp at intel.com/config) compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260527/202605271010.KFK2tlLa-lkp at intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot | Closes: https://lore.kernel.org/oe-kbuild-all/202605271010.KFK2tlLa-lkp at intel.com/ All warnings (new ones prefixed by >>): >> drivers/gpu/drm/meson/meson_drv.c:363:1: warning: unused label 'uninstall_irq' [-Wunused-label] 363 | uninstall_irq: | ^~~~~~~~~~~~~~ 1 warning generated. vim +/uninstall_irq +363 drivers/gpu/drm/meson/meson_drv.c 8976eeee8de05f1 Neil Armstrong 2020-04-28 180 8604889f83381ca Neil Armstrong 2017-05-29 181 static int meson_drv_bind_master(struct device *dev, bool has_components) bbbe775ec5b5dac Neil Armstrong 2016-11-10 182 { a41e82e6c4575b6 Neil Armstrong 2017-04-04 183 struct platform_device *pdev = to_platform_device(dev); d1b5e41e13a7e9b Neil Armstrong 2019-10-21 184 const struct meson_drm_match_data *match; bbbe775ec5b5dac Neil Armstrong 2016-11-10 185 struct meson_drm *priv; bbbe775ec5b5dac Neil Armstrong 2016-11-10 186 struct drm_device *drm; bbbe775ec5b5dac Neil Armstrong 2016-11-10 187 struct resource *res; bbbe775ec5b5dac Neil Armstrong 2016-11-10 188 void __iomem *regs; 8976eeee8de05f1 Neil Armstrong 2020-04-28 189 int ret, i; bbbe775ec5b5dac Neil Armstrong 2016-11-10 190 bbbe775ec5b5dac Neil Armstrong 2016-11-10 191 /* Checks if an output connector is available */ bbbe775ec5b5dac Neil Armstrong 2016-11-10 192 if (!meson_vpu_has_available_connectors(dev)) { bbbe775ec5b5dac Neil Armstrong 2016-11-10 193 dev_err(dev, "No output connector available\n"); bbbe775ec5b5dac Neil Armstrong 2016-11-10 194 return -ENODEV; bbbe775ec5b5dac Neil Armstrong 2016-11-10 195 } bbbe775ec5b5dac Neil Armstrong 2016-11-10 196 d1b5e41e13a7e9b Neil Armstrong 2019-10-21 197 match = of_device_get_match_data(dev); d1b5e41e13a7e9b Neil Armstrong 2019-10-21 198 if (!match) d1b5e41e13a7e9b Neil Armstrong 2019-10-21 199 return -ENODEV; d1b5e41e13a7e9b Neil Armstrong 2019-10-21 200 bbbe775ec5b5dac Neil Armstrong 2016-11-10 201 drm = drm_dev_alloc(&meson_driver, dev); bbbe775ec5b5dac Neil Armstrong 2016-11-10 202 if (IS_ERR(drm)) bbbe775ec5b5dac Neil Armstrong 2016-11-10 203 return PTR_ERR(drm); bbbe775ec5b5dac Neil Armstrong 2016-11-10 204 bbbe775ec5b5dac Neil Armstrong 2016-11-10 205 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); bbbe775ec5b5dac Neil Armstrong 2016-11-10 206 if (!priv) { bbbe775ec5b5dac Neil Armstrong 2016-11-10 207 ret = -ENOMEM; bbbe775ec5b5dac Neil Armstrong 2016-11-10 208 goto free_drm; bbbe775ec5b5dac Neil Armstrong 2016-11-10 209 } bbbe775ec5b5dac Neil Armstrong 2016-11-10 210 drm->dev_private = priv; bbbe775ec5b5dac Neil Armstrong 2016-11-10 211 priv->drm = drm; bbbe775ec5b5dac Neil Armstrong 2016-11-10 212 priv->dev = dev; d1b5e41e13a7e9b Neil Armstrong 2019-10-21 213 priv->compat = match->compat; d1b5e41e13a7e9b Neil Armstrong 2019-10-21 214 priv->afbcd.ops = match->afbcd_ops; 528a25d040bc212 Julien Masson 2019-08-22 215 d4cb82aa2e4bc0e Cai Huoqing 2021-08-31 216 regs = devm_platform_ioremap_resource_byname(pdev, "vpu"); 2c18107b9d58972 Christophe JAILLET 2018-03-12 217 if (IS_ERR(regs)) { 2c18107b9d58972 Christophe JAILLET 2018-03-12 218 ret = PTR_ERR(regs); 2c18107b9d58972 Christophe JAILLET 2018-03-12 219 goto free_drm; 2c18107b9d58972 Christophe JAILLET 2018-03-12 220 } bbbe775ec5b5dac Neil Armstrong 2016-11-10 221 bbbe775ec5b5dac Neil Armstrong 2016-11-10 222 priv->io_base = regs; bbbe775ec5b5dac Neil Armstrong 2016-11-10 223 bbbe775ec5b5dac Neil Armstrong 2016-11-10 224 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hhi"); 01a9e9493fb3f65 Christophe JAILLET 2018-06-11 225 if (!res) { 01a9e9493fb3f65 Christophe JAILLET 2018-06-11 226 ret = -EINVAL; 01a9e9493fb3f65 Christophe JAILLET 2018-06-11 227 goto free_drm; 01a9e9493fb3f65 Christophe JAILLET 2018-06-11 228 } bbbe775ec5b5dac Neil Armstrong 2016-11-10 229 /* Simply ioremap since it may be a shared register zone */ bbbe775ec5b5dac Neil Armstrong 2016-11-10 230 regs = devm_ioremap(dev, res->start, resource_size(res)); 2c18107b9d58972 Christophe JAILLET 2018-03-12 231 if (!regs) { 2c18107b9d58972 Christophe JAILLET 2018-03-12 232 ret = -EADDRNOTAVAIL; 2c18107b9d58972 Christophe JAILLET 2018-03-12 233 goto free_drm; 2c18107b9d58972 Christophe JAILLET 2018-03-12 234 } bbbe775ec5b5dac Neil Armstrong 2016-11-10 235 bbbe775ec5b5dac Neil Armstrong 2016-11-10 236 priv->hhi = devm_regmap_init_mmio(dev, regs, bbbe775ec5b5dac Neil Armstrong 2016-11-10 237 &meson_regmap_config); bbbe775ec5b5dac Neil Armstrong 2016-11-10 238 if (IS_ERR(priv->hhi)) { bbbe775ec5b5dac Neil Armstrong 2016-11-10 239 dev_err(&pdev->dev, "Couldn't create the HHI regmap\n"); 2c18107b9d58972 Christophe JAILLET 2018-03-12 240 ret = PTR_ERR(priv->hhi); 2c18107b9d58972 Christophe JAILLET 2018-03-12 241 goto free_drm; bbbe775ec5b5dac Neil Armstrong 2016-11-10 242 } bbbe775ec5b5dac Neil Armstrong 2016-11-10 243 66cae477c380d1a Maxime Jourdan 2018-11-05 244 priv->canvas = meson_canvas_get(dev); 2bf6b5b0e374fcc Maxime Jourdan 2019-03-11 245 if (IS_ERR(priv->canvas)) { 2bf6b5b0e374fcc Maxime Jourdan 2019-03-11 246 ret = PTR_ERR(priv->canvas); 2bf6b5b0e374fcc Maxime Jourdan 2019-03-11 247 goto free_drm; 2bf6b5b0e374fcc Maxime Jourdan 2019-03-11 248 } 2bf6b5b0e374fcc Maxime Jourdan 2019-03-11 249 66cae477c380d1a Maxime Jourdan 2018-11-05 250 ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd1); 66cae477c380d1a Maxime Jourdan 2018-11-05 251 if (ret) 66cae477c380d1a Maxime Jourdan 2018-11-05 252 goto free_drm; f9a2348196d1ab9 Neil Armstrong 2018-11-06 253 ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_0); a695949b2e9bb6b Yao Zi 2024-07-03 254 if (ret) a695949b2e9bb6b Yao Zi 2024-07-03 255 goto free_canvas_osd1; f9a2348196d1ab9 Neil Armstrong 2018-11-06 256 ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_1); a695949b2e9bb6b Yao Zi 2024-07-03 257 if (ret) a695949b2e9bb6b Yao Zi 2024-07-03 258 goto free_canvas_vd1_0; f9a2348196d1ab9 Neil Armstrong 2018-11-06 259 ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_2); a695949b2e9bb6b Yao Zi 2024-07-03 260 if (ret) a695949b2e9bb6b Yao Zi 2024-07-03 261 goto free_canvas_vd1_1; bbbe775ec5b5dac Neil Armstrong 2016-11-10 262 bbbe775ec5b5dac Neil Armstrong 2016-11-10 263 priv->vsync_irq = platform_get_irq(pdev, 0); bbbe775ec5b5dac Neil Armstrong 2016-11-10 264 e770f6bf18182bc Christophe JAILLET 2018-03-12 265 ret = drm_vblank_init(drm, 1); e770f6bf18182bc Christophe JAILLET 2018-03-12 266 if (ret) a695949b2e9bb6b Yao Zi 2024-07-03 267 goto free_canvas_vd1_2; e770f6bf18182bc Christophe JAILLET 2018-03-12 268 8976eeee8de05f1 Neil Armstrong 2020-04-28 269 /* Assign limits per soc revision/package */ 8976eeee8de05f1 Neil Armstrong 2020-04-28 270 for (i = 0 ; i < ARRAY_SIZE(meson_drm_soc_attrs) ; ++i) { 8976eeee8de05f1 Neil Armstrong 2020-04-28 271 if (soc_device_match(meson_drm_soc_attrs[i].attrs)) { 8976eeee8de05f1 Neil Armstrong 2020-04-28 272 priv->limits = &meson_drm_soc_attrs[i].limits; 8976eeee8de05f1 Neil Armstrong 2020-04-28 273 break; 8976eeee8de05f1 Neil Armstrong 2020-04-28 274 } 8976eeee8de05f1 Neil Armstrong 2020-04-28 275 } 8976eeee8de05f1 Neil Armstrong 2020-04-28 276 6848c291a54f8cd Thomas Zimmermann 2021-04-12 277 /* 6848c291a54f8cd Thomas Zimmermann 2021-04-12 278 * Remove early framebuffers (ie. simplefb). The framebuffer can be 6848c291a54f8cd Thomas Zimmermann 2021-04-12 279 * located anywhere in RAM 6848c291a54f8cd Thomas Zimmermann 2021-04-12 280 */ 736db96696b6232 Thomas Zimmermann 2024-09-30 281 ret = aperture_remove_all_conflicting_devices(meson_driver.name); 6848c291a54f8cd Thomas Zimmermann 2021-04-12 282 if (ret) a695949b2e9bb6b Yao Zi 2024-07-03 283 goto free_canvas_vd1_2; e3de0aa6c9afdca Maxime Jourdan 2018-12-10 284 bd9ff7b521a647a Simona Vetter 2020-03-23 285 ret = drmm_mode_config_init(drm); bd9ff7b521a647a Simona Vetter 2020-03-23 286 if (ret) a695949b2e9bb6b Yao Zi 2024-07-03 287 goto free_canvas_vd1_2; a41e82e6c4575b6 Neil Armstrong 2017-04-04 288 drm->mode_config.max_width = 3840; a41e82e6c4575b6 Neil Armstrong 2017-04-04 289 drm->mode_config.max_height = 2160; a41e82e6c4575b6 Neil Armstrong 2017-04-04 290 drm->mode_config.funcs = &meson_mode_config_funcs; ce0210c12433031 Neil Armstrong 2019-01-14 291 drm->mode_config.helper_private = &meson_mode_config_helpers; a41e82e6c4575b6 Neil Armstrong 2017-04-04 292 a41e82e6c4575b6 Neil Armstrong 2017-04-04 293 /* Hardware Initialization */ a41e82e6c4575b6 Neil Armstrong 2017-04-04 294 09762525d6eafb3 Neil Armstrong 2017-12-06 295 meson_vpu_init(priv); a41e82e6c4575b6 Neil Armstrong 2017-04-04 296 meson_venc_init(priv); a41e82e6c4575b6 Neil Armstrong 2017-04-04 297 meson_vpp_init(priv); a41e82e6c4575b6 Neil Armstrong 2017-04-04 298 meson_viu_init(priv); d1b5e41e13a7e9b Neil Armstrong 2019-10-21 299 if (priv->afbcd.ops) { d1b5e41e13a7e9b Neil Armstrong 2019-10-21 300 ret = priv->afbcd.ops->init(priv); d1b5e41e13a7e9b Neil Armstrong 2019-10-21 301 if (ret) a695949b2e9bb6b Yao Zi 2024-07-03 302 goto free_canvas_vd1_2; d1b5e41e13a7e9b Neil Armstrong 2019-10-21 303 } bbbe775ec5b5dac Neil Armstrong 2016-11-10 304 bbbe775ec5b5dac Neil Armstrong 2016-11-10 305 /* Encoder Initialization */ bbbe775ec5b5dac Neil Armstrong 2016-11-10 306 1a9e51bef89af0f Martin Blumenstingl 2024-02-18 307 ret = meson_encoder_cvbs_probe(priv); bbbe775ec5b5dac Neil Armstrong 2016-11-10 308 if (ret) fa747d75f65d1b1 Martin Blumenstingl 2021-12-31 309 goto exit_afbcd; bbbe775ec5b5dac Neil Armstrong 2016-11-10 310 8604889f83381ca Neil Armstrong 2017-05-29 311 if (has_components) { 6a044642988b5f8 Neil Armstrong 2023-05-30 312 ret = component_bind_all(dev, drm); a41e82e6c4575b6 Neil Armstrong 2017-04-04 313 if (ret) { a41e82e6c4575b6 Neil Armstrong 2017-04-04 314 dev_err(drm->dev, "Couldn't bind all components\n"); 6a044642988b5f8 Neil Armstrong 2023-05-30 315 /* Do not try to unbind */ 6a044642988b5f8 Neil Armstrong 2023-05-30 316 has_components = false; fa747d75f65d1b1 Martin Blumenstingl 2021-12-31 317 goto exit_afbcd; a41e82e6c4575b6 Neil Armstrong 2017-04-04 318 } 8604889f83381ca Neil Armstrong 2017-05-29 319 } bbbe775ec5b5dac Neil Armstrong 2016-11-10 320 1a9e51bef89af0f Martin Blumenstingl 2024-02-18 321 ret = meson_encoder_hdmi_probe(priv); e67f6037ae1be34 Neil Armstrong 2021-10-20 322 if (ret) 6a044642988b5f8 Neil Armstrong 2023-05-30 323 goto exit_afbcd; e67f6037ae1be34 Neil Armstrong 2021-10-20 324 42dcf15f901c822 Neil Armstrong 2023-05-30 325 if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { 1a9e51bef89af0f Martin Blumenstingl 2024-02-18 326 ret = meson_encoder_dsi_probe(priv); 42dcf15f901c822 Neil Armstrong 2023-05-30 327 if (ret) 42dcf15f901c822 Neil Armstrong 2023-05-30 328 goto exit_afbcd; 42dcf15f901c822 Neil Armstrong 2023-05-30 329 } 42dcf15f901c822 Neil Armstrong 2023-05-30 330 bbbe775ec5b5dac Neil Armstrong 2016-11-10 331 ret = meson_plane_create(priv); bbbe775ec5b5dac Neil Armstrong 2016-11-10 332 if (ret) 6a044642988b5f8 Neil Armstrong 2023-05-30 333 goto exit_afbcd; bbbe775ec5b5dac Neil Armstrong 2016-11-10 334 f9a2348196d1ab9 Neil Armstrong 2018-11-06 335 ret = meson_overlay_create(priv); f9a2348196d1ab9 Neil Armstrong 2018-11-06 336 if (ret) 6a044642988b5f8 Neil Armstrong 2023-05-30 337 goto exit_afbcd; f9a2348196d1ab9 Neil Armstrong 2018-11-06 338 bbbe775ec5b5dac Neil Armstrong 2016-11-10 339 ret = meson_crtc_create(priv); bbbe775ec5b5dac Neil Armstrong 2016-11-10 340 if (ret) 6a044642988b5f8 Neil Armstrong 2023-05-30 341 goto exit_afbcd; bbbe775ec5b5dac Neil Armstrong 2016-11-10 342 65a969655cb91fa Thomas Zimmermann 2021-07-06 343 ret = request_irq(priv->vsync_irq, meson_irq, 0, drm->driver->name, drm); bbbe775ec5b5dac Neil Armstrong 2016-11-10 344 if (ret) 6a044642988b5f8 Neil Armstrong 2023-05-30 345 goto exit_afbcd; bbbe775ec5b5dac Neil Armstrong 2016-11-10 346 bbbe775ec5b5dac Neil Armstrong 2016-11-10 347 drm_mode_config_reset(drm); bbbe775ec5b5dac Neil Armstrong 2016-11-10 348 bbbe775ec5b5dac Neil Armstrong 2016-11-10 349 drm_kms_helper_poll_init(drm); bbbe775ec5b5dac Neil Armstrong 2016-11-10 350 bbbe775ec5b5dac Neil Armstrong 2016-11-10 351 platform_set_drvdata(pdev, priv); bbbe775ec5b5dac Neil Armstrong 2016-11-10 352 bbbe775ec5b5dac Neil Armstrong 2016-11-10 353 ret = drm_dev_register(drm, 0); bbbe775ec5b5dac Neil Armstrong 2016-11-10 354 if (ret) e31803f51415af4 Myeonghun Pak 2026-05-25 355 goto uninstall_poll; bbbe775ec5b5dac Neil Armstrong 2016-11-10 356 57a03512c49a2e3 Thomas Zimmermann 2024-09-24 357 drm_client_setup(drm, NULL); efbb9df91e03b31 Noralf Tr?nnes 2018-09-08 358 bbbe775ec5b5dac Neil Armstrong 2016-11-10 359 return 0; bbbe775ec5b5dac Neil Armstrong 2016-11-10 360 e31803f51415af4 Myeonghun Pak 2026-05-25 361 uninstall_poll: e31803f51415af4 Myeonghun Pak 2026-05-25 362 drm_kms_helper_poll_fini(drm); 2d8f92897ad816f Jean-Philippe Brucker 2019-03-22 @363 uninstall_irq: 65a969655cb91fa Thomas Zimmermann 2021-07-06 364 free_irq(priv->vsync_irq, drm); fa747d75f65d1b1 Martin Blumenstingl 2021-12-31 365 exit_afbcd: fa747d75f65d1b1 Martin Blumenstingl 2021-12-31 366 if (priv->afbcd.ops) fa747d75f65d1b1 Martin Blumenstingl 2021-12-31 367 priv->afbcd.ops->exit(priv); a695949b2e9bb6b Yao Zi 2024-07-03 368 free_canvas_vd1_2: a695949b2e9bb6b Yao Zi 2024-07-03 369 meson_canvas_free(priv->canvas, priv->canvas_id_vd1_2); a695949b2e9bb6b Yao Zi 2024-07-03 370 free_canvas_vd1_1: a695949b2e9bb6b Yao Zi 2024-07-03 371 meson_canvas_free(priv->canvas, priv->canvas_id_vd1_1); a695949b2e9bb6b Yao Zi 2024-07-03 372 free_canvas_vd1_0: a695949b2e9bb6b Yao Zi 2024-07-03 373 meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0); a695949b2e9bb6b Yao Zi 2024-07-03 374 free_canvas_osd1: a695949b2e9bb6b Yao Zi 2024-07-03 375 meson_canvas_free(priv->canvas, priv->canvas_id_osd1); bbbe775ec5b5dac Neil Armstrong 2016-11-10 376 free_drm: dcacf65139e3def Christophe JAILLET 2018-03-12 377 drm_dev_put(drm); bbbe775ec5b5dac Neil Armstrong 2016-11-10 378 42dcf15f901c822 Neil Armstrong 2023-05-30 379 meson_encoder_dsi_remove(priv); 6a044642988b5f8 Neil Armstrong 2023-05-30 380 meson_encoder_hdmi_remove(priv); 6a044642988b5f8 Neil Armstrong 2023-05-30 381 meson_encoder_cvbs_remove(priv); 6a044642988b5f8 Neil Armstrong 2023-05-30 382 6a044642988b5f8 Neil Armstrong 2023-05-30 383 if (has_components) 6a044642988b5f8 Neil Armstrong 2023-05-30 384 component_unbind_all(dev, drm); 6a044642988b5f8 Neil Armstrong 2023-05-30 385 bbbe775ec5b5dac Neil Armstrong 2016-11-10 386 return ret; bbbe775ec5b5dac Neil Armstrong 2016-11-10 387 } bbbe775ec5b5dac Neil Armstrong 2016-11-10 388 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki From lkp at intel.com Tue May 26 20:22:29 2026 From: lkp at intel.com (kernel test robot) Date: Wed, 27 May 2026 11:22:29 +0800 Subject: [PATCH] drm/meson: clean up KMS polling on register failure In-Reply-To: <20260524160657.17802-1-mhun512@gmail.com> References: <20260524160657.17802-1-mhun512@gmail.com> Message-ID: <202605271153.rpsbxgdB-lkp@intel.com> Hi Myeonghun, kernel test robot noticed the following build warnings: [auto build test WARNING on drm-misc/drm-misc-next] [also build test WARNING on linus/master v7.1-rc5 next-20260526] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Myeonghun-Pak/drm-meson-clean-up-KMS-polling-on-register-failure/20260525-000807 base: https://gitlab.freedesktop.org/drm/misc/kernel.git drm-misc-next patch link: https://lore.kernel.org/r/20260524160657.17802-1-mhun512%40gmail.com patch subject: [PATCH] drm/meson: clean up KMS polling on register failure config: i386-allmodconfig (https://download.01.org/0day-ci/archive/20260527/202605271153.rpsbxgdB-lkp at intel.com/config) compiler: gcc-14 (Debian 14.2.0-19) 14.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260527/202605271153.rpsbxgdB-lkp at intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot | Closes: https://lore.kernel.org/oe-kbuild-all/202605271153.rpsbxgdB-lkp at intel.com/ All warnings (new ones prefixed by >>): drivers/gpu/drm/meson/meson_drv.c: In function 'meson_drv_bind_master': >> drivers/gpu/drm/meson/meson_drv.c:363:1: warning: label 'uninstall_irq' defined but not used [-Wunused-label] 363 | uninstall_irq: | ^~~~~~~~~~~~~ vim +/uninstall_irq +363 drivers/gpu/drm/meson/meson_drv.c 8976eeee8de05f Neil Armstrong 2020-04-28 180 8604889f83381c Neil Armstrong 2017-05-29 181 static int meson_drv_bind_master(struct device *dev, bool has_components) bbbe775ec5b5da Neil Armstrong 2016-11-10 182 { a41e82e6c4575b Neil Armstrong 2017-04-04 183 struct platform_device *pdev = to_platform_device(dev); d1b5e41e13a7e9 Neil Armstrong 2019-10-21 184 const struct meson_drm_match_data *match; bbbe775ec5b5da Neil Armstrong 2016-11-10 185 struct meson_drm *priv; bbbe775ec5b5da Neil Armstrong 2016-11-10 186 struct drm_device *drm; bbbe775ec5b5da Neil Armstrong 2016-11-10 187 struct resource *res; bbbe775ec5b5da Neil Armstrong 2016-11-10 188 void __iomem *regs; 8976eeee8de05f Neil Armstrong 2020-04-28 189 int ret, i; bbbe775ec5b5da Neil Armstrong 2016-11-10 190 bbbe775ec5b5da Neil Armstrong 2016-11-10 191 /* Checks if an output connector is available */ bbbe775ec5b5da Neil Armstrong 2016-11-10 192 if (!meson_vpu_has_available_connectors(dev)) { bbbe775ec5b5da Neil Armstrong 2016-11-10 193 dev_err(dev, "No output connector available\n"); bbbe775ec5b5da Neil Armstrong 2016-11-10 194 return -ENODEV; bbbe775ec5b5da Neil Armstrong 2016-11-10 195 } bbbe775ec5b5da Neil Armstrong 2016-11-10 196 d1b5e41e13a7e9 Neil Armstrong 2019-10-21 197 match = of_device_get_match_data(dev); d1b5e41e13a7e9 Neil Armstrong 2019-10-21 198 if (!match) d1b5e41e13a7e9 Neil Armstrong 2019-10-21 199 return -ENODEV; d1b5e41e13a7e9 Neil Armstrong 2019-10-21 200 bbbe775ec5b5da Neil Armstrong 2016-11-10 201 drm = drm_dev_alloc(&meson_driver, dev); bbbe775ec5b5da Neil Armstrong 2016-11-10 202 if (IS_ERR(drm)) bbbe775ec5b5da Neil Armstrong 2016-11-10 203 return PTR_ERR(drm); bbbe775ec5b5da Neil Armstrong 2016-11-10 204 bbbe775ec5b5da Neil Armstrong 2016-11-10 205 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); bbbe775ec5b5da Neil Armstrong 2016-11-10 206 if (!priv) { bbbe775ec5b5da Neil Armstrong 2016-11-10 207 ret = -ENOMEM; bbbe775ec5b5da Neil Armstrong 2016-11-10 208 goto free_drm; bbbe775ec5b5da Neil Armstrong 2016-11-10 209 } bbbe775ec5b5da Neil Armstrong 2016-11-10 210 drm->dev_private = priv; bbbe775ec5b5da Neil Armstrong 2016-11-10 211 priv->drm = drm; bbbe775ec5b5da Neil Armstrong 2016-11-10 212 priv->dev = dev; d1b5e41e13a7e9 Neil Armstrong 2019-10-21 213 priv->compat = match->compat; d1b5e41e13a7e9 Neil Armstrong 2019-10-21 214 priv->afbcd.ops = match->afbcd_ops; 528a25d040bc21 Julien Masson 2019-08-22 215 d4cb82aa2e4bc0 Cai Huoqing 2021-08-31 216 regs = devm_platform_ioremap_resource_byname(pdev, "vpu"); 2c18107b9d5897 Christophe JAILLET 2018-03-12 217 if (IS_ERR(regs)) { 2c18107b9d5897 Christophe JAILLET 2018-03-12 218 ret = PTR_ERR(regs); 2c18107b9d5897 Christophe JAILLET 2018-03-12 219 goto free_drm; 2c18107b9d5897 Christophe JAILLET 2018-03-12 220 } bbbe775ec5b5da Neil Armstrong 2016-11-10 221 bbbe775ec5b5da Neil Armstrong 2016-11-10 222 priv->io_base = regs; bbbe775ec5b5da Neil Armstrong 2016-11-10 223 bbbe775ec5b5da Neil Armstrong 2016-11-10 224 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hhi"); 01a9e9493fb3f6 Christophe JAILLET 2018-06-11 225 if (!res) { 01a9e9493fb3f6 Christophe JAILLET 2018-06-11 226 ret = -EINVAL; 01a9e9493fb3f6 Christophe JAILLET 2018-06-11 227 goto free_drm; 01a9e9493fb3f6 Christophe JAILLET 2018-06-11 228 } bbbe775ec5b5da Neil Armstrong 2016-11-10 229 /* Simply ioremap since it may be a shared register zone */ bbbe775ec5b5da Neil Armstrong 2016-11-10 230 regs = devm_ioremap(dev, res->start, resource_size(res)); 2c18107b9d5897 Christophe JAILLET 2018-03-12 231 if (!regs) { 2c18107b9d5897 Christophe JAILLET 2018-03-12 232 ret = -EADDRNOTAVAIL; 2c18107b9d5897 Christophe JAILLET 2018-03-12 233 goto free_drm; 2c18107b9d5897 Christophe JAILLET 2018-03-12 234 } bbbe775ec5b5da Neil Armstrong 2016-11-10 235 bbbe775ec5b5da Neil Armstrong 2016-11-10 236 priv->hhi = devm_regmap_init_mmio(dev, regs, bbbe775ec5b5da Neil Armstrong 2016-11-10 237 &meson_regmap_config); bbbe775ec5b5da Neil Armstrong 2016-11-10 238 if (IS_ERR(priv->hhi)) { bbbe775ec5b5da Neil Armstrong 2016-11-10 239 dev_err(&pdev->dev, "Couldn't create the HHI regmap\n"); 2c18107b9d5897 Christophe JAILLET 2018-03-12 240 ret = PTR_ERR(priv->hhi); 2c18107b9d5897 Christophe JAILLET 2018-03-12 241 goto free_drm; bbbe775ec5b5da Neil Armstrong 2016-11-10 242 } bbbe775ec5b5da Neil Armstrong 2016-11-10 243 66cae477c380d1 Maxime Jourdan 2018-11-05 244 priv->canvas = meson_canvas_get(dev); 2bf6b5b0e374fc Maxime Jourdan 2019-03-11 245 if (IS_ERR(priv->canvas)) { 2bf6b5b0e374fc Maxime Jourdan 2019-03-11 246 ret = PTR_ERR(priv->canvas); 2bf6b5b0e374fc Maxime Jourdan 2019-03-11 247 goto free_drm; 2bf6b5b0e374fc Maxime Jourdan 2019-03-11 248 } 2bf6b5b0e374fc Maxime Jourdan 2019-03-11 249 66cae477c380d1 Maxime Jourdan 2018-11-05 250 ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd1); 66cae477c380d1 Maxime Jourdan 2018-11-05 251 if (ret) 66cae477c380d1 Maxime Jourdan 2018-11-05 252 goto free_drm; f9a2348196d1ab Neil Armstrong 2018-11-06 253 ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_0); a695949b2e9bb6 Yao Zi 2024-07-03 254 if (ret) a695949b2e9bb6 Yao Zi 2024-07-03 255 goto free_canvas_osd1; f9a2348196d1ab Neil Armstrong 2018-11-06 256 ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_1); a695949b2e9bb6 Yao Zi 2024-07-03 257 if (ret) a695949b2e9bb6 Yao Zi 2024-07-03 258 goto free_canvas_vd1_0; f9a2348196d1ab Neil Armstrong 2018-11-06 259 ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_vd1_2); a695949b2e9bb6 Yao Zi 2024-07-03 260 if (ret) a695949b2e9bb6 Yao Zi 2024-07-03 261 goto free_canvas_vd1_1; bbbe775ec5b5da Neil Armstrong 2016-11-10 262 bbbe775ec5b5da Neil Armstrong 2016-11-10 263 priv->vsync_irq = platform_get_irq(pdev, 0); bbbe775ec5b5da Neil Armstrong 2016-11-10 264 e770f6bf18182b Christophe JAILLET 2018-03-12 265 ret = drm_vblank_init(drm, 1); e770f6bf18182b Christophe JAILLET 2018-03-12 266 if (ret) a695949b2e9bb6 Yao Zi 2024-07-03 267 goto free_canvas_vd1_2; e770f6bf18182b Christophe JAILLET 2018-03-12 268 8976eeee8de05f Neil Armstrong 2020-04-28 269 /* Assign limits per soc revision/package */ 8976eeee8de05f Neil Armstrong 2020-04-28 270 for (i = 0 ; i < ARRAY_SIZE(meson_drm_soc_attrs) ; ++i) { 8976eeee8de05f Neil Armstrong 2020-04-28 271 if (soc_device_match(meson_drm_soc_attrs[i].attrs)) { 8976eeee8de05f Neil Armstrong 2020-04-28 272 priv->limits = &meson_drm_soc_attrs[i].limits; 8976eeee8de05f Neil Armstrong 2020-04-28 273 break; 8976eeee8de05f Neil Armstrong 2020-04-28 274 } 8976eeee8de05f Neil Armstrong 2020-04-28 275 } 8976eeee8de05f Neil Armstrong 2020-04-28 276 6848c291a54f8c Thomas Zimmermann 2021-04-12 277 /* 6848c291a54f8c Thomas Zimmermann 2021-04-12 278 * Remove early framebuffers (ie. simplefb). The framebuffer can be 6848c291a54f8c Thomas Zimmermann 2021-04-12 279 * located anywhere in RAM 6848c291a54f8c Thomas Zimmermann 2021-04-12 280 */ 736db96696b623 Thomas Zimmermann 2024-09-30 281 ret = aperture_remove_all_conflicting_devices(meson_driver.name); 6848c291a54f8c Thomas Zimmermann 2021-04-12 282 if (ret) a695949b2e9bb6 Yao Zi 2024-07-03 283 goto free_canvas_vd1_2; e3de0aa6c9afdc Maxime Jourdan 2018-12-10 284 bd9ff7b521a647 Simona Vetter 2020-03-23 285 ret = drmm_mode_config_init(drm); bd9ff7b521a647 Simona Vetter 2020-03-23 286 if (ret) a695949b2e9bb6 Yao Zi 2024-07-03 287 goto free_canvas_vd1_2; a41e82e6c4575b Neil Armstrong 2017-04-04 288 drm->mode_config.max_width = 3840; a41e82e6c4575b Neil Armstrong 2017-04-04 289 drm->mode_config.max_height = 2160; a41e82e6c4575b Neil Armstrong 2017-04-04 290 drm->mode_config.funcs = &meson_mode_config_funcs; ce0210c1243303 Neil Armstrong 2019-01-14 291 drm->mode_config.helper_private = &meson_mode_config_helpers; a41e82e6c4575b Neil Armstrong 2017-04-04 292 a41e82e6c4575b Neil Armstrong 2017-04-04 293 /* Hardware Initialization */ a41e82e6c4575b Neil Armstrong 2017-04-04 294 09762525d6eafb Neil Armstrong 2017-12-06 295 meson_vpu_init(priv); a41e82e6c4575b Neil Armstrong 2017-04-04 296 meson_venc_init(priv); a41e82e6c4575b Neil Armstrong 2017-04-04 297 meson_vpp_init(priv); a41e82e6c4575b Neil Armstrong 2017-04-04 298 meson_viu_init(priv); d1b5e41e13a7e9 Neil Armstrong 2019-10-21 299 if (priv->afbcd.ops) { d1b5e41e13a7e9 Neil Armstrong 2019-10-21 300 ret = priv->afbcd.ops->init(priv); d1b5e41e13a7e9 Neil Armstrong 2019-10-21 301 if (ret) a695949b2e9bb6 Yao Zi 2024-07-03 302 goto free_canvas_vd1_2; d1b5e41e13a7e9 Neil Armstrong 2019-10-21 303 } bbbe775ec5b5da Neil Armstrong 2016-11-10 304 bbbe775ec5b5da Neil Armstrong 2016-11-10 305 /* Encoder Initialization */ bbbe775ec5b5da Neil Armstrong 2016-11-10 306 1a9e51bef89af0 Martin Blumenstingl 2024-02-18 307 ret = meson_encoder_cvbs_probe(priv); bbbe775ec5b5da Neil Armstrong 2016-11-10 308 if (ret) fa747d75f65d1b Martin Blumenstingl 2021-12-31 309 goto exit_afbcd; bbbe775ec5b5da Neil Armstrong 2016-11-10 310 8604889f83381c Neil Armstrong 2017-05-29 311 if (has_components) { 6a044642988b5f Neil Armstrong 2023-05-30 312 ret = component_bind_all(dev, drm); a41e82e6c4575b Neil Armstrong 2017-04-04 313 if (ret) { a41e82e6c4575b Neil Armstrong 2017-04-04 314 dev_err(drm->dev, "Couldn't bind all components\n"); 6a044642988b5f Neil Armstrong 2023-05-30 315 /* Do not try to unbind */ 6a044642988b5f Neil Armstrong 2023-05-30 316 has_components = false; fa747d75f65d1b Martin Blumenstingl 2021-12-31 317 goto exit_afbcd; a41e82e6c4575b Neil Armstrong 2017-04-04 318 } 8604889f83381c Neil Armstrong 2017-05-29 319 } bbbe775ec5b5da Neil Armstrong 2016-11-10 320 1a9e51bef89af0 Martin Blumenstingl 2024-02-18 321 ret = meson_encoder_hdmi_probe(priv); e67f6037ae1be3 Neil Armstrong 2021-10-20 322 if (ret) 6a044642988b5f Neil Armstrong 2023-05-30 323 goto exit_afbcd; e67f6037ae1be3 Neil Armstrong 2021-10-20 324 42dcf15f901c82 Neil Armstrong 2023-05-30 325 if (meson_vpu_is_compatible(priv, VPU_COMPATIBLE_G12A)) { 1a9e51bef89af0 Martin Blumenstingl 2024-02-18 326 ret = meson_encoder_dsi_probe(priv); 42dcf15f901c82 Neil Armstrong 2023-05-30 327 if (ret) 42dcf15f901c82 Neil Armstrong 2023-05-30 328 goto exit_afbcd; 42dcf15f901c82 Neil Armstrong 2023-05-30 329 } 42dcf15f901c82 Neil Armstrong 2023-05-30 330 bbbe775ec5b5da Neil Armstrong 2016-11-10 331 ret = meson_plane_create(priv); bbbe775ec5b5da Neil Armstrong 2016-11-10 332 if (ret) 6a044642988b5f Neil Armstrong 2023-05-30 333 goto exit_afbcd; bbbe775ec5b5da Neil Armstrong 2016-11-10 334 f9a2348196d1ab Neil Armstrong 2018-11-06 335 ret = meson_overlay_create(priv); f9a2348196d1ab Neil Armstrong 2018-11-06 336 if (ret) 6a044642988b5f Neil Armstrong 2023-05-30 337 goto exit_afbcd; f9a2348196d1ab Neil Armstrong 2018-11-06 338 bbbe775ec5b5da Neil Armstrong 2016-11-10 339 ret = meson_crtc_create(priv); bbbe775ec5b5da Neil Armstrong 2016-11-10 340 if (ret) 6a044642988b5f Neil Armstrong 2023-05-30 341 goto exit_afbcd; bbbe775ec5b5da Neil Armstrong 2016-11-10 342 65a969655cb91f Thomas Zimmermann 2021-07-06 343 ret = request_irq(priv->vsync_irq, meson_irq, 0, drm->driver->name, drm); bbbe775ec5b5da Neil Armstrong 2016-11-10 344 if (ret) 6a044642988b5f Neil Armstrong 2023-05-30 345 goto exit_afbcd; bbbe775ec5b5da Neil Armstrong 2016-11-10 346 bbbe775ec5b5da Neil Armstrong 2016-11-10 347 drm_mode_config_reset(drm); bbbe775ec5b5da Neil Armstrong 2016-11-10 348 bbbe775ec5b5da Neil Armstrong 2016-11-10 349 drm_kms_helper_poll_init(drm); bbbe775ec5b5da Neil Armstrong 2016-11-10 350 bbbe775ec5b5da Neil Armstrong 2016-11-10 351 platform_set_drvdata(pdev, priv); bbbe775ec5b5da Neil Armstrong 2016-11-10 352 bbbe775ec5b5da Neil Armstrong 2016-11-10 353 ret = drm_dev_register(drm, 0); bbbe775ec5b5da Neil Armstrong 2016-11-10 354 if (ret) e31803f51415af Myeonghun Pak 2026-05-25 355 goto uninstall_poll; bbbe775ec5b5da Neil Armstrong 2016-11-10 356 57a03512c49a2e Thomas Zimmermann 2024-09-24 357 drm_client_setup(drm, NULL); efbb9df91e03b3 Noralf Tr?nnes 2018-09-08 358 bbbe775ec5b5da Neil Armstrong 2016-11-10 359 return 0; bbbe775ec5b5da Neil Armstrong 2016-11-10 360 e31803f51415af Myeonghun Pak 2026-05-25 361 uninstall_poll: e31803f51415af Myeonghun Pak 2026-05-25 362 drm_kms_helper_poll_fini(drm); 2d8f92897ad816 Jean-Philippe Brucker 2019-03-22 @363 uninstall_irq: 65a969655cb91f Thomas Zimmermann 2021-07-06 364 free_irq(priv->vsync_irq, drm); fa747d75f65d1b Martin Blumenstingl 2021-12-31 365 exit_afbcd: fa747d75f65d1b Martin Blumenstingl 2021-12-31 366 if (priv->afbcd.ops) fa747d75f65d1b Martin Blumenstingl 2021-12-31 367 priv->afbcd.ops->exit(priv); a695949b2e9bb6 Yao Zi 2024-07-03 368 free_canvas_vd1_2: a695949b2e9bb6 Yao Zi 2024-07-03 369 meson_canvas_free(priv->canvas, priv->canvas_id_vd1_2); a695949b2e9bb6 Yao Zi 2024-07-03 370 free_canvas_vd1_1: a695949b2e9bb6 Yao Zi 2024-07-03 371 meson_canvas_free(priv->canvas, priv->canvas_id_vd1_1); a695949b2e9bb6 Yao Zi 2024-07-03 372 free_canvas_vd1_0: a695949b2e9bb6 Yao Zi 2024-07-03 373 meson_canvas_free(priv->canvas, priv->canvas_id_vd1_0); a695949b2e9bb6 Yao Zi 2024-07-03 374 free_canvas_osd1: a695949b2e9bb6 Yao Zi 2024-07-03 375 meson_canvas_free(priv->canvas, priv->canvas_id_osd1); bbbe775ec5b5da Neil Armstrong 2016-11-10 376 free_drm: dcacf65139e3de Christophe JAILLET 2018-03-12 377 drm_dev_put(drm); bbbe775ec5b5da Neil Armstrong 2016-11-10 378 42dcf15f901c82 Neil Armstrong 2023-05-30 379 meson_encoder_dsi_remove(priv); 6a044642988b5f Neil Armstrong 2023-05-30 380 meson_encoder_hdmi_remove(priv); 6a044642988b5f Neil Armstrong 2023-05-30 381 meson_encoder_cvbs_remove(priv); 6a044642988b5f Neil Armstrong 2023-05-30 382 6a044642988b5f Neil Armstrong 2023-05-30 383 if (has_components) 6a044642988b5f Neil Armstrong 2023-05-30 384 component_unbind_all(dev, drm); 6a044642988b5f Neil Armstrong 2023-05-30 385 bbbe775ec5b5da Neil Armstrong 2016-11-10 386 return ret; bbbe775ec5b5da Neil Armstrong 2016-11-10 387 } bbbe775ec5b5da Neil Armstrong 2016-11-10 388 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki From lee at kernel.org Wed May 27 07:53:22 2026 From: lee at kernel.org (Lee Jones) Date: Wed, 27 May 2026 15:53:22 +0100 Subject: [PATCH v6 4/8] mfd: khadas-mcu: Add support for VIM4 MCU variant In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-4-cccc9b61f465@aliel.fr> References: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> <20260516-add-mcu-fan-khadas-vim4-v6-4-cccc9b61f465@aliel.fr> Message-ID: <20260527145322.GB671544@google.com> On Sat, 16 May 2026, Ronald Claveau via B4 Relay wrote: > From: Ronald Claveau > > Refactor probe() to use per-variant khadas_mcu_data > instead of hardcoded globals. > > Add dedicated regmap configuration and device data for the VIM4 MCU, > with its own volatile/writeable registers. > > Add the fan control register > (0?100 levels vs 0?3 for previous supported boards). > > Add a new compatible string "khadas,vim4-mcu". > > Reviewed-by: Neil Armstrong > Signed-off-by: Ronald Claveau > --- > drivers/mfd/khadas-mcu.c | 119 +++++++++++++++++++++++++++++++++++++++++------ > 1 file changed, 104 insertions(+), 15 deletions(-) > > diff --git a/drivers/mfd/khadas-mcu.c b/drivers/mfd/khadas-mcu.c > index ba981a7886921..7bc538232a445 100644 > --- a/drivers/mfd/khadas-mcu.c > +++ b/drivers/mfd/khadas-mcu.c > @@ -75,15 +75,91 @@ static const struct regmap_config khadas_mcu_regmap_config = { > .cache_type = REGCACHE_MAPLE, > }; > > -static struct mfd_cell khadas_mcu_fan_cells[] = { > +static const struct khadas_mcu_fan_pdata khadas_mcu_fan_pdata = { > + .fan_reg = KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG, > + .max_level = 3, /* Fan speed: 0 = off, 1 = low, 2 = medium, 3 = high */ > +}; > + > +static const struct mfd_cell khadas_mcu_fan_cells[] = { > /* VIM1/2 Rev13+ and VIM3 only */ > - { .name = "khadas-mcu-fan-ctrl", }, > + { > + .name = "khadas-mcu-fan-ctrl", > + .platform_data = &khadas_mcu_fan_pdata, > + .pdata_size = sizeof(khadas_mcu_fan_pdata), > + }, > }; > > -static struct mfd_cell khadas_mcu_cells[] = { > +static const struct mfd_cell khadas_mcu_cells[] = { > { .name = "khadas-mcu-user-mem", }, > }; > > +static const struct khadas_mcu_data khadas_mcu_data = { > + .regmap_config = &khadas_mcu_regmap_config, > + .cells = khadas_mcu_cells, > + .ncells = ARRAY_SIZE(khadas_mcu_cells), > + .fan_cells = khadas_mcu_fan_cells, > + .nfan_cells = ARRAY_SIZE(khadas_mcu_fan_cells), > +}; Let's not over-complicate things please. You can do all of this with local variables inside khadas_mcu_probe() and omit the terribly named khadas_mcu_data structure entirely. > +static bool khadas_mcu_vim4_reg_volatile(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case KHADAS_MCU_PWR_OFF_CMD_REG: > + case KHADAS_MCU_VIM4_REST_CONF_REG: > + case KHADAS_MCU_WOL_INIT_START_REG: > + case KHADAS_MCU_VIM4_LED_ON_RAM_REG: > + case KHADAS_MCU_VIM4_FAN_CTRL_REG: > + case KHADAS_MCU_VIM4_WDT_EN_REG: > + case KHADAS_MCU_VIM4_SYS_RST_REG: > + return true; > + default: > + return false; > + } > +} > + > +static bool khadas_mcu_vim4_reg_writeable(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case KHADAS_MCU_VERSION_0_REG: > + case KHADAS_MCU_VERSION_1_REG: > + case KHADAS_MCU_SHUTDOWN_NORMAL_STATUS_REG: > + return false; > + default: > + return true; > + } > +} > + > +static const struct regmap_config khadas_mcu_vim4_regmap_config = { > + .reg_bits = 8, > + .reg_stride = 1, > + .val_bits = 8, > + .max_register = KHADAS_MCU_VIM4_SYS_RST_REG, > + .volatile_reg = khadas_mcu_vim4_reg_volatile, > + .writeable_reg = khadas_mcu_vim4_reg_writeable, > + .cache_type = REGCACHE_MAPLE, > +}; > + > +static const struct khadas_mcu_fan_pdata khadas_vim4_fan_pdata = { > + .fan_reg = KHADAS_MCU_VIM4_FAN_CTRL_REG, > + .max_level = 0x64, > +}; > + > +static const struct mfd_cell khadas_mcu_vim4_cells[] = { > + { > + .name = "khadas-mcu-fan-ctrl", > + .platform_data = &khadas_vim4_fan_pdata, > + .pdata_size = sizeof(khadas_vim4_fan_pdata), > + }, > +}; > + > +static const struct khadas_mcu_data khadas_vim4_mcu_data = { > + .regmap_config = &khadas_mcu_vim4_regmap_config, > + .cells = NULL, > + .ncells = 0, > + .fan_cells = khadas_mcu_vim4_cells, > + .nfan_cells = ARRAY_SIZE(khadas_mcu_vim4_cells), > +}; > + > static int khadas_mcu_probe(struct i2c_client *client) > { > struct device *dev = &client->dev; > @@ -94,28 +170,40 @@ static int khadas_mcu_probe(struct i2c_client *client) > if (!ddata) > return -ENOMEM; > > + switch ((uintptr_t)i2c_get_match_data(client)) { > + case KHADAS_MCU_GENERIC: > + ddata->data = &khadas_mcu_data; > + break; > + case KHADAS_MCU_VIM4: > + ddata->data = &khadas_vim4_mcu_data; > + break; > + default: > + return -ENODEV; > + } > + > i2c_set_clientdata(client, ddata); > > ddata->dev = dev; > > - ddata->regmap = devm_regmap_init_i2c(client, &khadas_mcu_regmap_config); > + ddata->regmap = devm_regmap_init_i2c(client, ddata->data->regmap_config); > if (IS_ERR(ddata->regmap)) { > ret = PTR_ERR(ddata->regmap); > - dev_err(dev, "Failed to allocate register map: %d\n", ret); > - return ret; > + return dev_err_probe(dev, ret, "Failed to allocate register map\n"); > } > > - ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, > - khadas_mcu_cells, > - ARRAY_SIZE(khadas_mcu_cells), > - NULL, 0, NULL); > - if (ret) > - return ret; > + if (ddata->data->cells && ddata->data->ncells) { > + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, > + ddata->data->cells, > + ddata->data->ncells, > + NULL, 0, NULL); > + if (ret) > + return ret; > + } > > if (of_property_present(dev->of_node, "#cooling-cells")) > return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, > - khadas_mcu_fan_cells, > - ARRAY_SIZE(khadas_mcu_fan_cells), > + ddata->data->fan_cells, > + ddata->data->nfan_cells, > NULL, 0, NULL); > > return 0; > @@ -123,7 +211,8 @@ static int khadas_mcu_probe(struct i2c_client *client) > > #ifdef CONFIG_OF > static const struct of_device_id khadas_mcu_of_match[] = { > - { .compatible = "khadas,mcu", }, > + { .compatible = "khadas,mcu", .data = (void *)KHADAS_MCU_GENERIC }, > + { .compatible = "khadas,vim4-mcu", .data = (void *)KHADAS_MCU_VIM4 }, > {}, > }; > MODULE_DEVICE_TABLE(of, khadas_mcu_of_match); > > -- > 2.49.0 > > -- Lee Jones From lee at kernel.org Wed May 27 07:54:23 2026 From: lee at kernel.org (Lee Jones) Date: Wed, 27 May 2026 15:54:23 +0100 Subject: [PATCH v6 3/8] mfd: khadas-mcu: Add per-variant configuration infrastructure and VIM4 support In-Reply-To: <20260516-add-mcu-fan-khadas-vim4-v6-3-cccc9b61f465@aliel.fr> References: <20260516-add-mcu-fan-khadas-vim4-v6-0-cccc9b61f465@aliel.fr> <20260516-add-mcu-fan-khadas-vim4-v6-3-cccc9b61f465@aliel.fr> Message-ID: <20260527145423.GC671544@google.com> On Sat, 16 May 2026, Ronald Claveau via B4 Relay wrote: > From: Ronald Claveau > > Introduce a per-variant configuration structure (khadas_mcu_data) > holding the regmap config and MFD cells, > selected at probe time via the of_device_id match data. > This makes adding other variants straightforward. > > Add an enum khadas_mcu_type used as value to match. > > Also introduce khadas_mcu_fan_pdata to pass fan register address and > maximum level to the fan sub-driver, removing the hardcoded constants. > > Reviewed-by: Neil Armstrong > Signed-off-by: Ronald Claveau > --- > include/linux/mfd/khadas-mcu.h | 44 ++++++++++++++++++++++++++++++++++++++++-- > 1 file changed, 42 insertions(+), 2 deletions(-) > > diff --git a/include/linux/mfd/khadas-mcu.h b/include/linux/mfd/khadas-mcu.h > index a99ba2ed0e4e0..88de49b78f5e6 100644 > --- a/include/linux/mfd/khadas-mcu.h > +++ b/include/linux/mfd/khadas-mcu.h > @@ -70,6 +70,13 @@ > #define KHADAS_MCU_WOL_INIT_START_REG 0x87 /* WO */ > #define KHADAS_MCU_CMD_FAN_STATUS_CTRL_REG 0x88 /* WO */ > > +/* VIM4 specific registers */ > +#define KHADAS_MCU_VIM4_REST_CONF_REG 0x2c /* WO - reset EEPROM */ > +#define KHADAS_MCU_VIM4_LED_ON_RAM_REG 0x89 /* WO - LED volatile */ > +#define KHADAS_MCU_VIM4_FAN_CTRL_REG 0x8a /* WO */ > +#define KHADAS_MCU_VIM4_WDT_EN_REG 0x8b /* WO */ > +#define KHADAS_MCU_VIM4_SYS_RST_REG 0x91 /* WO */ > + > enum { > KHADAS_BOARD_VIM1 = 0x1, > KHADAS_BOARD_VIM2, > @@ -82,10 +89,43 @@ enum { > * struct khadas_mcu - Khadas MCU structure > * @device: device reference used for logs > * @regmap: register map > + * @data: pointer to variant-specific config > */ > struct khadas_mcu { > - struct device *dev; > - struct regmap *regmap; > + struct device *dev; > + struct regmap *regmap; > + const struct khadas_mcu_data *data; > +}; > + > +/** > + * struct khadas_mcu_data - per-variant configuration > + * @regmap_config: regmap configuration > + * @cells: MFD sub-devices > + * @ncells: number of sub-devices > + * @fan_cells: MFD fan sub-devices > + * @nfan_cells: number of fan sub-devices > + */ > +struct khadas_mcu_data { > + const struct regmap_config *regmap_config; > + const struct mfd_cell *cells; > + int ncells; > + const struct mfd_cell *fan_cells; > + int nfan_cells; > +}; My alarm bells started ringing as soon as I saw this! > +/** > + * struct khadas_mcu_fan_pdata - fan sub-driver configuration > + * @fan_reg: register address to write the fan level > + * @max_level: maximum fan level > + */ > +struct khadas_mcu_fan_pdata { > + unsigned int fan_reg; > + unsigned int max_level; > +}; > + > +enum khadas_mcu_type { > + KHADAS_MCU_GENERIC, /* VIM1/2/3, Edge, Edge-V */ > + KHADAS_MCU_VIM4, > }; > > #endif /* MFD_KHADAS_MCU_H */ > > -- > 2.49.0 > > -- Lee Jones From vsetti at baylibre.com Thu May 28 08:01:59 2026 From: vsetti at baylibre.com (Valerio Setti) Date: Thu, 28 May 2026 17:01:59 +0200 Subject: [PATCH 4/4] ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data In-Reply-To: <891be10c-99f1-45b7-bd31-ec5080cfc780@baylibre.com> References: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> <20260515-reshape-aiu-as-axg-v1-4-53b457784ff3@baylibre.com> <758a4ef9-1a3e-475a-ae1e-83523330d006@sirena.org.uk> <891be10c-99f1-45b7-bd31-ec5080cfc780@baylibre.com> Message-ID: On 5/22/26 18:24, Valerio Setti wrote: > > On 5/22/26 01:15, Mark Brown wrote: >> On Fri, May 15, 2026 at 05:10:40PM +0200, Valerio Setti wrote: >>> Create a new DAPM widget for "I2S formatter" and place it on the path >>> between FIFO and output DAI interface. Remove I2S output formatting code >>> from aiu-encoder-i2s since it's now implemented from aiu-formatter-i2s. >> >> This series, it looks like this specific patch, is breaking pcm-test on >> my libretech Le Potato board, the clocking looks to be seriously messed >> up.? I'm getting: >> >> [...] >> >> Full log: >> >> ??? https://lava.sirena.org.uk/scheduler/job/2786342#L1934 >> >> The prior patches seem to test fine, it's this one that seems to >> introduce the issue. > > Thanks a lot for the heads up and please apologize for the problem. > I wasn't aware of these testing tools so I based my testing on playing > with userspace alsa tools on the physical board that I have. > > I will take a look at it ASAP and send a properly tested v2. > Hi! I investigated a bit on the issue caused by my v1 patch series and I think I've found the root cause. I suspect my patch series helped discovering a misbehavior that was already present, so I'm seeking for suggestions on the proper way to proceed. # First: the background By default on OdroidC2/LePotato boards the only available audio playback is through HDMI. From DAI point of view the flow is as follows: "I2S FIFO" -> "I2S Encoder Playback" -> "CODEC CTRL HDMI I2S IN" -> "HDMI CTRL SRC" (mixer) -> "CODEC CTRL HDMI OUT Capture" In this chain the mixer "HDMI CTRL SRC" by default starts as "DISABLED" so it should prevent "CODEC CTRL HDMI OUT Capture" from receiving data. # What changed before/after the last commit of my patch series? - Before: "aiu-encoder-i2s.c" was calling "aiu_encoder_i2s_setup_desc()" in "hw_params()". Audio playback shouldn't work in this condition because as I mentioned "HDMI CTRL SRC"="DISABLED" by default, but apparently configuring the AIU_I2S_SOURCE_DESC register is enough to make the playback to work properly. - After: those configurations are only set when "aiu_formatter_i2s_prepare" is called which happens if "I2S Formatter" widget is enabled. Since "HDMI CTRL SRC"="DISABLED" then "I2S Formatter" is not powered up and therefore its callbacks are not called and the playback fails. Simply issuing the following command: $ amixer sset 'AIU HDMI CTRL SRC' 'I2S' resolves the problem and all pcm-tests pass. This also explains why I didn't catch this issue before sending the v1 series: I tested with NXP SGTL5000 codec, not the HDMI one. # Final question I have 2 alternative proposals for this: - Change the default value of "HDMI CTRL SRC" so that at boot it's set to some working configuration (ex: "I2S"). This should be done somewhere in "aiu-codec-ctrl" I think. - Run the 'amixer' command above before running ALSA tests. Any suggestion on what's the best approach? Thanks a lot. -- Valerio From broonie at kernel.org Thu May 28 08:53:35 2026 From: broonie at kernel.org (Mark Brown) Date: Thu, 28 May 2026 16:53:35 +0100 Subject: [PATCH 4/4] ASoC: meson: aiu: use aiu-formatter-i2s to format I2S output data In-Reply-To: References: <20260515-reshape-aiu-as-axg-v1-0-53b457784ff3@baylibre.com> <20260515-reshape-aiu-as-axg-v1-4-53b457784ff3@baylibre.com> <758a4ef9-1a3e-475a-ae1e-83523330d006@sirena.org.uk> <891be10c-99f1-45b7-bd31-ec5080cfc780@baylibre.com> Message-ID: <512b2bb6-620f-428f-9724-73972e589c46@sirena.org.uk> On Thu, May 28, 2026 at 05:01:59PM +0200, Valerio Setti wrote: > I have 2 alternative proposals for this: > - Change the default value of "HDMI CTRL SRC" so that at boot it's set to > some working configuration (ex: "I2S"). This should be done somewhere in > "aiu-codec-ctrl" I think. > - Run the 'amixer' command above before running ALSA tests. > Any suggestion on what's the best approach? Having to set routing is usually fine, I do that for a bunch of my boards but not Potato. The only issue with this is that if people upgrade the kernel they'll see audio break so ideally we'd have the same default behaviour as we had before. -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 488 bytes Desc: not available URL: From ynorov at nvidia.com Thu May 28 11:36:07 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:07 -0400 Subject: [PATCH 00/16] lib/cpumask: get rid of cpumap_print_to_pagebuf() Message-ID: <20260528183625.870813-1-ynorov@nvidia.com> cpumap_print_to_pagebuf() is the equivalent for the "&*pb[l]" notation in printk-like functions. In some cases, it makes people to create temporary buffers for the printed cpumasks, where it can be avoided. Get rid of it in a favor of more standard printing API. Each patch, except for the last one, is independent and may be moved with the corresponding subsystem. Or I can take it in bitmap-for-next, at maintainers' discretion. On top of bitmap-for-next. (Apologize for the bulky CC list. Feel free to review only relevant patches.) Yury Norov (16): psci: simplify hotplug_tests() arm: Use sysfs_emit() for cpumask show callbacks powerpc: Use sysfs_emit() for cpumask show callbacks x86/events: Use sysfs_emit() for cpumask show callbacks ACPI: pad: Use sysfs_emit() for idlecpus show cpu: Use sysfs_emit() for cpumask show callback devfreq: Use sysfs_emit() for cpumask show callbacks fpga: dfl-fme-perf: Use sysfs_emit() for cpumask show hwtracing: hisi_ptt: Use sysfs_emit() for cpumask show RDMA/hfi1: Use sysfs_emit() for cpumask show helper nvdimm: Use sysfs_emit() for cpumask show callback PCI/sysfs: Use sysfs_emit() for cpumask show callbacks perf: Use sysfs_emit() for cpumask show callbacks powercap: intel_rapl: Use sysfs_emit() for cpumask show thermal: intel: Use sysfs_emit() for powerclamp cpumask lib/bitmap-str: get rid of cpumap_print_to_pagebuf() arch/arm/mach-imx/mmdc.c | 2 +- arch/arm/mm/cache-l2x0-pmu.c | 2 +- arch/powerpc/kernel/cacheinfo.c | 3 ++- arch/powerpc/perf/hv-24x7.c | 2 +- arch/powerpc/perf/hv-gpci.c | 2 +- arch/powerpc/perf/imc-pmu.c | 2 +- arch/x86/events/amd/iommu.c | 2 +- arch/x86/events/amd/power.c | 2 +- arch/x86/events/amd/uncore.c | 2 +- arch/x86/events/intel/core.c | 2 +- arch/x86/events/intel/uncore.c | 2 +- drivers/acpi/acpi_pad.c | 4 ++-- drivers/base/cpu.c | 2 +- drivers/devfreq/event/rockchip-dfi.c | 2 +- drivers/devfreq/hisi_uncore_freq.c | 2 +- drivers/firmware/psci/psci_checker.c | 14 ++------------ drivers/fpga/dfl-fme-perf.c | 2 +- drivers/hwtracing/ptt/hisi_ptt.c | 2 +- drivers/infiniband/hw/hfi1/sdma.c | 3 ++- drivers/nvdimm/nd_perf.c | 2 +- drivers/pci/pci-sysfs.c | 7 ++++--- drivers/perf/alibaba_uncore_drw_pmu.c | 2 +- drivers/perf/amlogic/meson_ddr_pmu_core.c | 2 +- drivers/perf/arm-cci.c | 2 +- drivers/perf/arm-ccn.c | 2 +- drivers/perf/arm-cmn.c | 2 +- drivers/perf/arm-ni.c | 2 +- drivers/perf/arm_cspmu/arm_cspmu.c | 2 +- drivers/perf/arm_dmc620_pmu.c | 4 ++-- drivers/perf/arm_dsu_pmu.c | 2 +- drivers/perf/arm_pmu.c | 2 +- drivers/perf/arm_smmuv3_pmu.c | 2 +- drivers/perf/arm_spe_pmu.c | 2 +- drivers/perf/cxl_pmu.c | 2 +- drivers/perf/dwc_pcie_pmu.c | 2 +- drivers/perf/fsl_imx8_ddr_perf.c | 2 +- drivers/perf/fsl_imx9_ddr_perf.c | 2 +- drivers/perf/fujitsu_uncore_pmu.c | 2 +- drivers/perf/hisilicon/hisi_pcie_pmu.c | 2 +- drivers/perf/hisilicon/hisi_uncore_pmu.c | 2 +- drivers/perf/marvell_cn10k_ddr_pmu.c | 2 +- drivers/perf/marvell_cn10k_tad_pmu.c | 2 +- drivers/perf/marvell_pem_pmu.c | 2 +- drivers/perf/nvidia_t410_c2c_pmu.c | 2 +- drivers/perf/nvidia_t410_cmem_latency_pmu.c | 2 +- drivers/perf/qcom_l2_pmu.c | 2 +- drivers/perf/qcom_l3_pmu.c | 2 +- drivers/perf/starfive_starlink_pmu.c | 2 +- drivers/perf/thunderx2_pmu.c | 2 +- drivers/perf/xgene_pmu.c | 2 +- drivers/powercap/intel_rapl_common.c | 2 +- drivers/thermal/intel/intel_powerclamp.c | 2 +- include/linux/cpumask.h | 19 ------------------- kernel/events/core.c | 2 +- lib/bitmap-str.c | 9 ++++----- 55 files changed, 65 insertions(+), 92 deletions(-) -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:08 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:08 -0400 Subject: [PATCH 01/16] psci: simplify hotplug_tests() In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-2-ynorov@nvidia.com> Switch to pr_info("... %pbl"), and drop the temporary buffer allocation. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- drivers/firmware/psci/psci_checker.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/drivers/firmware/psci/psci_checker.c b/drivers/firmware/psci/psci_checker.c index e67ba9891082..ecd745bb90bf 100644 --- a/drivers/firmware/psci/psci_checker.c +++ b/drivers/firmware/psci/psci_checker.c @@ -186,7 +186,6 @@ static int hotplug_tests(void) { int i, nb_cpu_group, err = -ENOMEM; cpumask_var_t offlined_cpus, *cpu_groups; - char *page_buf; if (!alloc_cpumask_var(&offlined_cpus, GFP_KERNEL)) return err; @@ -194,10 +193,6 @@ static int hotplug_tests(void) nb_cpu_group = alloc_init_cpu_groups(&cpu_groups); if (nb_cpu_group < 0) goto out_free_cpus; - page_buf = (char *)__get_free_page(GFP_KERNEL); - if (!page_buf) - goto out_free_cpu_groups; - /* * Of course the last CPU cannot be powered down and cpu_down() should * refuse doing that. @@ -210,16 +205,11 @@ static int hotplug_tests(void) * off, the cpu group itself should shut down. */ for (i = 0; i < nb_cpu_group; ++i) { - ssize_t len = cpumap_print_to_pagebuf(true, page_buf, - cpu_groups[i]); - /* Remove trailing newline. */ - page_buf[len - 1] = '\0'; - pr_info("Trying to turn off and on again group %d (CPUs %s)\n", - i, page_buf); + pr_info("Trying to turn off and on again group %d (CPUs %*pbl)\n", + i, cpumask_pr_args(cpu_groups[i])); err += down_and_up_cpus(cpu_groups[i], offlined_cpus); } - free_page((unsigned long)page_buf); out_free_cpu_groups: free_cpu_groups(nb_cpu_group, &cpu_groups); out_free_cpus: -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:09 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:09 -0400 Subject: [PATCH 02/16] arm: Use sysfs_emit() for cpumask show callbacks In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-3-ynorov@nvidia.com> These callbacks are sysfs show paths. Use sysfs_emit() and cpumask_pr_args() to emit the masks. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- arch/arm/mach-imx/mmdc.c | 2 +- arch/arm/mm/cache-l2x0-pmu.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm/mach-imx/mmdc.c b/arch/arm/mach-imx/mmdc.c index b71467c48b87..f6d993b9b1d4 100644 --- a/arch/arm/mach-imx/mmdc.c +++ b/arch/arm/mach-imx/mmdc.c @@ -127,7 +127,7 @@ static ssize_t mmdc_pmu_cpumask_show(struct device *dev, { struct mmdc_pmu *pmu_mmdc = dev_get_drvdata(dev); - return cpumap_print_to_pagebuf(true, buf, &pmu_mmdc->cpu); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&pmu_mmdc->cpu)); } static struct device_attribute mmdc_pmu_cpumask_attr = diff --git a/arch/arm/mm/cache-l2x0-pmu.c b/arch/arm/mm/cache-l2x0-pmu.c index 3d9caf7464bf..478227078837 100644 --- a/arch/arm/mm/cache-l2x0-pmu.c +++ b/arch/arm/mm/cache-l2x0-pmu.c @@ -390,7 +390,7 @@ static struct attribute_group l2x0_pmu_event_attrs_group = { static ssize_t l2x0_pmu_cpumask_show(struct device *dev, struct device_attribute *attr, char *buf) { - return cpumap_print_to_pagebuf(true, buf, &pmu_cpu); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&pmu_cpu)); } static struct device_attribute l2x0_pmu_cpumask_attr = -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:10 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:10 -0400 Subject: [PATCH 03/16] powerpc: Use sysfs_emit() for cpumask show callbacks In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-4-ynorov@nvidia.com> These callbacks are sysfs show paths. Use sysfs_emit() and cpumask_pr_args() to emit the masks. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- arch/powerpc/kernel/cacheinfo.c | 3 ++- arch/powerpc/perf/hv-24x7.c | 2 +- arch/powerpc/perf/hv-gpci.c | 2 +- arch/powerpc/perf/imc-pmu.c | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/arch/powerpc/kernel/cacheinfo.c b/arch/powerpc/kernel/cacheinfo.c index 90d51d9b3ed2..be415d2bb697 100644 --- a/arch/powerpc/kernel/cacheinfo.c +++ b/arch/powerpc/kernel/cacheinfo.c @@ -689,7 +689,8 @@ show_shared_cpumap(struct kobject *k, struct kobj_attribute *attr, char *buf, bo mask = &cache->shared_cpu_map; - return cpumap_print_to_pagebuf(list, buf, mask); + return sysfs_emit(buf, list ? "%*pbl\n" : "%*pb\n", + cpumask_pr_args(mask)); } static ssize_t shared_cpu_map_show(struct kobject *k, struct kobj_attribute *attr, char *buf) diff --git a/arch/powerpc/perf/hv-24x7.c b/arch/powerpc/perf/hv-24x7.c index 243c0a1c8cda..d661fc972c92 100644 --- a/arch/powerpc/perf/hv-24x7.c +++ b/arch/powerpc/perf/hv-24x7.c @@ -428,7 +428,7 @@ static char *memdup_to_str(char *maybe_str, int max_len, gfp_t gfp) static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, char *buf) { - return cpumap_print_to_pagebuf(true, buf, &hv_24x7_cpumask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&hv_24x7_cpumask)); } static ssize_t sockets_show(struct device *dev, diff --git a/arch/powerpc/perf/hv-gpci.c b/arch/powerpc/perf/hv-gpci.c index 10c82cf8f5b3..655b9553ca54 100644 --- a/arch/powerpc/perf/hv-gpci.c +++ b/arch/powerpc/perf/hv-gpci.c @@ -99,7 +99,7 @@ static ssize_t kernel_version_show(struct device *dev, static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, char *buf) { - return cpumap_print_to_pagebuf(true, buf, &hv_gpci_cpumask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&hv_gpci_cpumask)); } /* Interface attribute array index to store system information */ diff --git a/arch/powerpc/perf/imc-pmu.c b/arch/powerpc/perf/imc-pmu.c index c1563b4eaa94..7cb909c47889 100644 --- a/arch/powerpc/perf/imc-pmu.c +++ b/arch/powerpc/perf/imc-pmu.c @@ -117,7 +117,7 @@ static ssize_t imc_pmu_cpumask_get_attr(struct device *dev, return 0; } - return cpumap_print_to_pagebuf(true, buf, active_mask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(active_mask)); } static DEVICE_ATTR(cpumask, S_IRUGO, imc_pmu_cpumask_get_attr, NULL); -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:11 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:11 -0400 Subject: [PATCH 04/16] x86/events: Use sysfs_emit() for cpumask show callbacks In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-5-ynorov@nvidia.com> These callbacks are sysfs show paths. Use sysfs_emit() and cpumask_pr_args() to emit the masks. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- arch/x86/events/amd/iommu.c | 2 +- arch/x86/events/amd/power.c | 2 +- arch/x86/events/amd/uncore.c | 2 +- arch/x86/events/intel/core.c | 2 +- arch/x86/events/intel/uncore.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/x86/events/amd/iommu.c b/arch/x86/events/amd/iommu.c index 07b110e8418a..f332c7089bd5 100644 --- a/arch/x86/events/amd/iommu.c +++ b/arch/x86/events/amd/iommu.c @@ -137,7 +137,7 @@ static ssize_t _iommu_cpumask_show(struct device *dev, struct device_attribute *attr, char *buf) { - return cpumap_print_to_pagebuf(true, buf, &iommu_cpumask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&iommu_cpumask)); } static DEVICE_ATTR(cpumask, S_IRUGO, _iommu_cpumask_show, NULL); diff --git a/arch/x86/events/amd/power.c b/arch/x86/events/amd/power.c index dad42790cf7d..890609961a6f 100644 --- a/arch/x86/events/amd/power.c +++ b/arch/x86/events/amd/power.c @@ -147,7 +147,7 @@ static void pmu_event_read(struct perf_event *event) static ssize_t get_attr_cpumask(struct device *dev, struct device_attribute *attr, char *buf) { - return cpumap_print_to_pagebuf(true, buf, &cpu_mask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&cpu_mask)); } static DEVICE_ATTR(cpumask, S_IRUGO, get_attr_cpumask, NULL); diff --git a/arch/x86/events/amd/uncore.c b/arch/x86/events/amd/uncore.c index dd956cfcadef..797dcce8bd89 100644 --- a/arch/x86/events/amd/uncore.c +++ b/arch/x86/events/amd/uncore.c @@ -321,7 +321,7 @@ static ssize_t amd_uncore_attr_show_cpumask(struct device *dev, struct pmu *ptr = dev_get_drvdata(dev); struct amd_uncore_pmu *pmu = container_of(ptr, struct amd_uncore_pmu, pmu); - return cpumap_print_to_pagebuf(true, buf, &pmu->active_mask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&pmu->active_mask)); } static DEVICE_ATTR(cpumask, S_IRUGO, amd_uncore_attr_show_cpumask, NULL); diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index dd1e3aa75ee9..5e9b65b2d1c1 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -7311,7 +7311,7 @@ static ssize_t intel_hybrid_get_attr_cpus(struct device *dev, struct x86_hybrid_pmu *pmu = container_of(dev_get_drvdata(dev), struct x86_hybrid_pmu, pmu); - return cpumap_print_to_pagebuf(true, buf, &pmu->supported_cpus); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&pmu->supported_cpus)); } static DEVICE_ATTR(cpus, S_IRUGO, intel_hybrid_get_attr_cpus, NULL); diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index e9cc1ba921c5..746d0d526f1d 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -842,7 +842,7 @@ static ssize_t uncore_get_attr_cpumask(struct device *dev, { struct intel_uncore_pmu *pmu = container_of(dev_get_drvdata(dev), struct intel_uncore_pmu, pmu); - return cpumap_print_to_pagebuf(true, buf, &pmu->cpu_mask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&pmu->cpu_mask)); } static DEVICE_ATTR(cpumask, S_IRUGO, uncore_get_attr_cpumask, NULL); -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:12 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:12 -0400 Subject: [PATCH 05/16] ACPI: pad: Use sysfs_emit() for idlecpus show In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-6-ynorov@nvidia.com> idlecpus_show() is a sysfs show callback. Use sysfs_emit() and cpumask_pr_args() to emit the mask. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- drivers/acpi/acpi_pad.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/acpi_pad.c b/drivers/acpi/acpi_pad.c index ec94b09bb747..04d61a6cc95f 100644 --- a/drivers/acpi/acpi_pad.c +++ b/drivers/acpi/acpi_pad.c @@ -334,8 +334,8 @@ static ssize_t idlecpus_store(struct device *dev, static ssize_t idlecpus_show(struct device *dev, struct device_attribute *attr, char *buf) { - return cpumap_print_to_pagebuf(false, buf, - to_cpumask(pad_busy_cpus_bits)); + return sysfs_emit(buf, "%*pb\n", + cpumask_pr_args(to_cpumask(pad_busy_cpus_bits))); } static DEVICE_ATTR_RW(idlecpus); -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:13 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:13 -0400 Subject: [PATCH 06/16] cpu: Use sysfs_emit() for cpumask show callback In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-7-ynorov@nvidia.com> show_cpus_attr() is a sysfs show callback. Use sysfs_emit() and cpumask_pr_args() to emit the mask. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- drivers/base/cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index 875abdc9942e..2ebc4e87ed9f 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -218,7 +218,7 @@ static ssize_t show_cpus_attr(struct device *dev, { struct cpu_attr *ca = container_of(attr, struct cpu_attr, attr); - return cpumap_print_to_pagebuf(true, buf, ca->map); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(ca->map)); } #define _CPU_ATTR(name, map) \ -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:14 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:14 -0400 Subject: [PATCH 07/16] devfreq: Use sysfs_emit() for cpumask show callbacks In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-8-ynorov@nvidia.com> These callbacks are sysfs show paths. Use sysfs_emit() and cpumask_pr_args() to emit the masks. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- drivers/devfreq/event/rockchip-dfi.c | 2 +- drivers/devfreq/hisi_uncore_freq.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/devfreq/event/rockchip-dfi.c b/drivers/devfreq/event/rockchip-dfi.c index 5e6e7e900bda..255aee1bdd91 100644 --- a/drivers/devfreq/event/rockchip-dfi.c +++ b/drivers/devfreq/event/rockchip-dfi.c @@ -354,7 +354,7 @@ static ssize_t ddr_perf_cpumask_show(struct device *dev, struct pmu *pmu = dev_get_drvdata(dev); struct rockchip_dfi *dfi = container_of(pmu, struct rockchip_dfi, pmu); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(dfi->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(dfi->cpu))); } static struct device_attribute ddr_perf_cpumask_attr = diff --git a/drivers/devfreq/hisi_uncore_freq.c b/drivers/devfreq/hisi_uncore_freq.c index 4d00d813c8ac..23b262d23a66 100644 --- a/drivers/devfreq/hisi_uncore_freq.c +++ b/drivers/devfreq/hisi_uncore_freq.c @@ -541,7 +541,7 @@ static ssize_t related_cpus_show(struct device *dev, { struct hisi_uncore_freq *uncore = dev_get_drvdata(dev->parent); - return cpumap_print_to_pagebuf(true, buf, &uncore->related_cpus); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&uncore->related_cpus)); } static DEVICE_ATTR_RO(related_cpus); -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:15 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:15 -0400 Subject: [PATCH 08/16] fpga: dfl-fme-perf: Use sysfs_emit() for cpumask show In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-9-ynorov@nvidia.com> cpumask_show() is a sysfs show callback. Use sysfs_emit() and cpumask_pr_args() to emit the mask. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- drivers/fpga/dfl-fme-perf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/fpga/dfl-fme-perf.c b/drivers/fpga/dfl-fme-perf.c index 7422d2bc6f37..7aa4983ab67d 100644 --- a/drivers/fpga/dfl-fme-perf.c +++ b/drivers/fpga/dfl-fme-perf.c @@ -183,7 +183,7 @@ static ssize_t cpumask_show(struct device *dev, priv = to_fme_perf_priv(pmu); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(priv->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(priv->cpu))); } static DEVICE_ATTR_RO(cpumask); -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:16 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:16 -0400 Subject: [PATCH 09/16] hwtracing: hisi_ptt: Use sysfs_emit() for cpumask show In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-10-ynorov@nvidia.com> cpumask_show() is a sysfs show callback. Use sysfs_emit() and cpumask_pr_args() to emit the mask. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- drivers/hwtracing/ptt/hisi_ptt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwtracing/ptt/hisi_ptt.c b/drivers/hwtracing/ptt/hisi_ptt.c index 94c371c49135..233c4c32513c 100644 --- a/drivers/hwtracing/ptt/hisi_ptt.c +++ b/drivers/hwtracing/ptt/hisi_ptt.c @@ -780,7 +780,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, struct hisi_ptt *hisi_ptt = to_hisi_ptt(dev_get_drvdata(dev)); const cpumask_t *cpumask = cpumask_of_node(dev_to_node(&hisi_ptt->pdev->dev)); - return cpumap_print_to_pagebuf(true, buf, cpumask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); } static DEVICE_ATTR_RO(cpumask); -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:17 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:17 -0400 Subject: [PATCH 10/16] RDMA/hfi1: Use sysfs_emit() for cpumask show helper In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-11-ynorov@nvidia.com> sdma_get_cpu_to_sde_map() is used by a sysfs show callback. Use sysfs_emit() and cpumask_pr_args() to emit the mask. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- drivers/infiniband/hw/hfi1/sdma.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/infiniband/hw/hfi1/sdma.c b/drivers/infiniband/hw/hfi1/sdma.c index cfd9dd0f7e81..f253c8ee182d 100644 --- a/drivers/infiniband/hw/hfi1/sdma.c +++ b/drivers/infiniband/hw/hfi1/sdma.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "hfi.h" #include "common.h" @@ -1049,7 +1050,7 @@ ssize_t sdma_get_cpu_to_sde_map(struct sdma_engine *sde, char *buf) if (cpumask_empty(&sde->cpu_mask)) snprintf(buf, PAGE_SIZE, "%s\n", "empty"); else - cpumap_print_to_pagebuf(true, buf, &sde->cpu_mask); + sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&sde->cpu_mask)); mutex_unlock(&process_to_sde_mutex); return strnlen(buf, PAGE_SIZE); } -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:18 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:18 -0400 Subject: [PATCH 11/16] nvdimm: Use sysfs_emit() for cpumask show callback In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-12-ynorov@nvidia.com> nvdimm_pmu_cpumask_show() is a sysfs show callback. Use sysfs_emit() and cpumask_pr_args() to emit the mask. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- drivers/nvdimm/nd_perf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/nvdimm/nd_perf.c b/drivers/nvdimm/nd_perf.c index e0b51438dc9b..9e497cae65b3 100644 --- a/drivers/nvdimm/nd_perf.c +++ b/drivers/nvdimm/nd_perf.c @@ -123,7 +123,7 @@ static ssize_t nvdimm_pmu_cpumask_show(struct device *dev, nd_pmu = container_of(pmu, struct nvdimm_pmu, pmu); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(nd_pmu->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(nd_pmu->cpu))); } static int nvdimm_pmu_cpu_offline(unsigned int cpu, struct hlist_node *node) -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:22 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:22 -0400 Subject: [PATCH 15/16] thermal: intel: Use sysfs_emit() for powerclamp cpumask In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-16-ynorov@nvidia.com> cpumask_get() is used as a sysfs getter for the cpumask module parameter. Use sysfs_emit() and cpumask_pr_args() to emit the mask. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- drivers/thermal/intel/intel_powerclamp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c index ccf380da12f2..bd7fd98dc310 100644 --- a/drivers/thermal/intel/intel_powerclamp.c +++ b/drivers/thermal/intel/intel_powerclamp.c @@ -200,7 +200,7 @@ static int cpumask_get(char *buf, const struct kernel_param *kp) if (!cpumask_available(idle_injection_cpu_mask)) return -ENODEV; - return cpumap_print_to_pagebuf(false, buf, idle_injection_cpu_mask); + return sysfs_emit(buf, "%*pb\n", cpumask_pr_args(idle_injection_cpu_mask)); } static const struct kernel_param_ops cpumask_ops = { -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:23 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:23 -0400 Subject: [PATCH 16/16] lib/bitmap-str: get rid of cpumap_print_to_pagebuf() In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-17-ynorov@nvidia.com> Now that all users of the function are switched to the alternatives, drop the function. Signed-off-by: Yury Norov --- include/linux/cpumask.h | 19 ------------------- lib/bitmap-str.c | 9 ++++----- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/include/linux/cpumask.h b/include/linux/cpumask.h index d3cda0544954..4c8bb6953107 100644 --- a/include/linux/cpumask.h +++ b/include/linux/cpumask.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -1315,24 +1314,6 @@ static __always_inline bool cpu_dying(unsigned int cpu) } #endif /* NR_CPUS > BITS_PER_LONG */ -/** - * cpumap_print_to_pagebuf - copies the cpumask into the buffer either - * as comma-separated list of cpus or hex values of cpumask - * @list: indicates whether the cpumap must be list - * @mask: the cpumask to copy - * @buf: the buffer to copy into - * - * Return: the length of the (null-terminated) @buf string, zero if - * nothing is copied. - */ -static __always_inline ssize_t -cpumap_print_to_pagebuf(bool list, char *buf, const struct cpumask *mask) -{ - /* Opencode offset_in_page(buf) to not include linux/mm.h */ - return scnprintf(buf, PAGE_SIZE - ((unsigned long)buf & ~PAGE_MASK), - list ? "%*pbl\n" : "%*pb\n", cpumask_pr_args(mask)); -} - /** * cpumap_print_bitmask_to_buf - copies the cpumask into the buffer as * hex values of cpumask diff --git a/lib/bitmap-str.c b/lib/bitmap-str.c index 26d36c938c6a..dd9aa0635fa5 100644 --- a/lib/bitmap-str.c +++ b/lib/bitmap-str.c @@ -75,8 +75,7 @@ static int bitmap_print_to_buf(bool list, char *buf, const unsigned long *maskp, * @off: in the string from which we are copying, We copy to @buf * @count: the maximum number of bytes to print * - * The sprintf("%*pb[l]") is used indirectly via its cpumap wrapper - * cpumap_print_to_pagebuf() or directly by drivers to export hexadecimal + * The sprintf("%*pb[l]") format is used by drivers to export hexadecimal * bitmask and decimal list to userspace by sysfs ABI. * Drivers might be using a normal attribute for this kind of ABIs. A * normal attribute typically has show entry as below:: @@ -115,9 +114,9 @@ static int bitmap_print_to_buf(bool list, char *buf, const unsigned long *maskp, * parameters such as off, count from bin_attribute show entry to this API. * * The role of cpumap_print_bitmask_to_buf() and cpumap_print_list_to_buf() - * is similar with cpumap_print_to_pagebuf(), the difference is that - * scnprintf("%*pb[l]") mainly serves sysfs attribute with the assumption - * the destination buffer is exactly one page and won't be more than one page. + * is similar to direct sysfs_emit("%*pb[l]") formatting, but the latter + * assumes the destination buffer is exactly one page and won't be more than + * one page. * cpumap_print_bitmask_to_buf() and cpumap_print_list_to_buf(), on the other * hand, mainly serves bin_attribute which doesn't work with exact one page, * and it can break the size limit of converted decimal list and hexadecimal -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:19 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:19 -0400 Subject: [PATCH 12/16] PCI/sysfs: Use sysfs_emit() for cpumask show callbacks In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-13-ynorov@nvidia.com> These callbacks are sysfs show paths. Use sysfs_emit() and cpumask_pr_args() to emit the masks. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- drivers/pci/pci-sysfs.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index d37860841260..319c1d1459ac 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -114,7 +114,8 @@ static ssize_t pci_dev_show_local_cpu(struct device *dev, bool list, #else mask = cpumask_of_pcibus(to_pci_dev(dev)->bus); #endif - return cpumap_print_to_pagebuf(list, buf, mask); + return sysfs_emit(buf, list ? "%*pbl\n" : "%*pb\n", + cpumask_pr_args(mask)); } static ssize_t local_cpus_show(struct device *dev, @@ -139,7 +140,7 @@ static ssize_t cpuaffinity_show(struct device *dev, { const struct cpumask *cpumask = cpumask_of_pcibus(to_pci_bus(dev)); - return cpumap_print_to_pagebuf(false, buf, cpumask); + return sysfs_emit(buf, "%*pb\n", cpumask_pr_args(cpumask)); } static DEVICE_ATTR_RO(cpuaffinity); @@ -148,7 +149,7 @@ static ssize_t cpulistaffinity_show(struct device *dev, { const struct cpumask *cpumask = cpumask_of_pcibus(to_pci_bus(dev)); - return cpumap_print_to_pagebuf(true, buf, cpumask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); } static DEVICE_ATTR_RO(cpulistaffinity); -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:20 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:20 -0400 Subject: [PATCH 13/16] perf: Use sysfs_emit() for cpumask show callbacks In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-14-ynorov@nvidia.com> These callbacks are sysfs show paths. Use sysfs_emit() and cpumask_pr_args() to emit the masks. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- drivers/perf/alibaba_uncore_drw_pmu.c | 2 +- drivers/perf/amlogic/meson_ddr_pmu_core.c | 2 +- drivers/perf/arm-cci.c | 2 +- drivers/perf/arm-ccn.c | 2 +- drivers/perf/arm-cmn.c | 2 +- drivers/perf/arm-ni.c | 2 +- drivers/perf/arm_cspmu/arm_cspmu.c | 2 +- drivers/perf/arm_dmc620_pmu.c | 4 ++-- drivers/perf/arm_dsu_pmu.c | 2 +- drivers/perf/arm_pmu.c | 2 +- drivers/perf/arm_smmuv3_pmu.c | 2 +- drivers/perf/arm_spe_pmu.c | 2 +- drivers/perf/cxl_pmu.c | 2 +- drivers/perf/dwc_pcie_pmu.c | 2 +- drivers/perf/fsl_imx8_ddr_perf.c | 2 +- drivers/perf/fsl_imx9_ddr_perf.c | 2 +- drivers/perf/fujitsu_uncore_pmu.c | 2 +- drivers/perf/hisilicon/hisi_pcie_pmu.c | 2 +- drivers/perf/hisilicon/hisi_uncore_pmu.c | 2 +- drivers/perf/marvell_cn10k_ddr_pmu.c | 2 +- drivers/perf/marvell_cn10k_tad_pmu.c | 2 +- drivers/perf/marvell_pem_pmu.c | 2 +- drivers/perf/nvidia_t410_c2c_pmu.c | 2 +- drivers/perf/nvidia_t410_cmem_latency_pmu.c | 2 +- drivers/perf/qcom_l2_pmu.c | 2 +- drivers/perf/qcom_l3_pmu.c | 2 +- drivers/perf/starfive_starlink_pmu.c | 2 +- drivers/perf/thunderx2_pmu.c | 2 +- drivers/perf/xgene_pmu.c | 2 +- kernel/events/core.c | 2 +- 30 files changed, 31 insertions(+), 31 deletions(-) diff --git a/drivers/perf/alibaba_uncore_drw_pmu.c b/drivers/perf/alibaba_uncore_drw_pmu.c index ac49d3b2dad6..74786a5dd6a2 100644 --- a/drivers/perf/alibaba_uncore_drw_pmu.c +++ b/drivers/perf/alibaba_uncore_drw_pmu.c @@ -221,7 +221,7 @@ static ssize_t ali_drw_pmu_cpumask_show(struct device *dev, { struct ali_drw_pmu *drw_pmu = to_ali_drw_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(drw_pmu->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(drw_pmu->cpu))); } static struct device_attribute ali_drw_pmu_cpumask_attr = diff --git a/drivers/perf/amlogic/meson_ddr_pmu_core.c b/drivers/perf/amlogic/meson_ddr_pmu_core.c index c1e755c356a3..f614aa3434a5 100644 --- a/drivers/perf/amlogic/meson_ddr_pmu_core.c +++ b/drivers/perf/amlogic/meson_ddr_pmu_core.c @@ -191,7 +191,7 @@ static ssize_t meson_ddr_perf_cpumask_show(struct device *dev, { struct ddr_pmu *pmu = dev_get_drvdata(dev); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); } static struct device_attribute meson_ddr_perf_cpumask_attr = diff --git a/drivers/perf/arm-cci.c b/drivers/perf/arm-cci.c index 1cc3214d6b6d..f0ef0a679e74 100644 --- a/drivers/perf/arm-cci.c +++ b/drivers/perf/arm-cci.c @@ -1351,7 +1351,7 @@ static ssize_t pmu_cpumask_attr_show(struct device *dev, struct pmu *pmu = dev_get_drvdata(dev); struct cci_pmu *cci_pmu = to_cci_pmu(pmu); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(cci_pmu->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(cci_pmu->cpu))); } static struct device_attribute pmu_cpumask_attr = diff --git a/drivers/perf/arm-ccn.c b/drivers/perf/arm-ccn.c index 8af3563fdf60..d5dcb4280434 100644 --- a/drivers/perf/arm-ccn.c +++ b/drivers/perf/arm-ccn.c @@ -538,7 +538,7 @@ static ssize_t arm_ccn_pmu_cpumask_show(struct device *dev, { struct arm_ccn *ccn = pmu_to_arm_ccn(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(ccn->dt.cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(ccn->dt.cpu))); } static struct device_attribute arm_ccn_pmu_cpumask_attr = diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c index f5305c8fdca4..2187ba763b72 100644 --- a/drivers/perf/arm-cmn.c +++ b/drivers/perf/arm-cmn.c @@ -1326,7 +1326,7 @@ static ssize_t arm_cmn_cpumask_show(struct device *dev, { struct arm_cmn *cmn = to_cmn(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(cmn->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(cmn->cpu))); } static struct device_attribute arm_cmn_cpumask_attr = diff --git a/drivers/perf/arm-ni.c b/drivers/perf/arm-ni.c index 66858c65215d..03a1c6bf9223 100644 --- a/drivers/perf/arm-ni.c +++ b/drivers/perf/arm-ni.c @@ -239,7 +239,7 @@ static ssize_t arm_ni_cpumask_show(struct device *dev, { struct arm_ni *ni = cd_to_ni(pmu_to_cd(dev_get_drvdata(dev))); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(ni->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(ni->cpu))); } static struct device_attribute arm_ni_cpumask_attr = diff --git a/drivers/perf/arm_cspmu/arm_cspmu.c b/drivers/perf/arm_cspmu/arm_cspmu.c index 80fb314d5135..e6292021f653 100644 --- a/drivers/perf/arm_cspmu/arm_cspmu.c +++ b/drivers/perf/arm_cspmu/arm_cspmu.c @@ -305,7 +305,7 @@ static ssize_t arm_cspmu_cpumask_show(struct device *dev, default: return 0; } - return cpumap_print_to_pagebuf(true, buf, cpumask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); } static struct attribute *arm_cspmu_cpumask_attrs[] = { diff --git a/drivers/perf/arm_dmc620_pmu.c b/drivers/perf/arm_dmc620_pmu.c index 4f6b196160f8..467147a05eec 100644 --- a/drivers/perf/arm_dmc620_pmu.c +++ b/drivers/perf/arm_dmc620_pmu.c @@ -237,8 +237,8 @@ static ssize_t dmc620_pmu_cpumask_show(struct device *dev, { struct dmc620_pmu *dmc620_pmu = to_dmc620_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, - cpumask_of(dmc620_pmu->irq->cpu)); + return sysfs_emit(buf, "%*pbl\n", + cpumask_pr_args(cpumask_of(dmc620_pmu->irq->cpu))); } static struct device_attribute dmc620_pmu_cpumask_attr = diff --git a/drivers/perf/arm_dsu_pmu.c b/drivers/perf/arm_dsu_pmu.c index 32b0dd7c693b..bcbd19e075a5 100644 --- a/drivers/perf/arm_dsu_pmu.c +++ b/drivers/perf/arm_dsu_pmu.c @@ -157,7 +157,7 @@ static ssize_t dsu_pmu_cpumask_show(struct device *dev, default: return 0; } - return cpumap_print_to_pagebuf(true, buf, cpumask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); } static struct attribute *dsu_pmu_format_attrs[] = { diff --git a/drivers/perf/arm_pmu.c b/drivers/perf/arm_pmu.c index 939bcbd433aa..51ab6cc52ca0 100644 --- a/drivers/perf/arm_pmu.c +++ b/drivers/perf/arm_pmu.c @@ -570,7 +570,7 @@ static ssize_t cpus_show(struct device *dev, struct device_attribute *attr, char *buf) { struct arm_pmu *armpmu = to_arm_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, &armpmu->supported_cpus); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&armpmu->supported_cpus)); } static DEVICE_ATTR_RO(cpus); diff --git a/drivers/perf/arm_smmuv3_pmu.c b/drivers/perf/arm_smmuv3_pmu.c index 621f02a7f43b..8ce34e6bb82b 100644 --- a/drivers/perf/arm_smmuv3_pmu.c +++ b/drivers/perf/arm_smmuv3_pmu.c @@ -537,7 +537,7 @@ static ssize_t smmu_pmu_cpumask_show(struct device *dev, { struct smmu_pmu *smmu_pmu = to_smmu_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(smmu_pmu->on_cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(smmu_pmu->on_cpu))); } static struct device_attribute smmu_pmu_cpumask_attr = diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c index dbd0da111639..9f786fd48cdd 100644 --- a/drivers/perf/arm_spe_pmu.c +++ b/drivers/perf/arm_spe_pmu.c @@ -343,7 +343,7 @@ static ssize_t cpumask_show(struct device *dev, { struct arm_spe_pmu *spe_pmu = dev_get_drvdata(dev); - return cpumap_print_to_pagebuf(true, buf, &spe_pmu->supported_cpus); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&spe_pmu->supported_cpus)); } static DEVICE_ATTR_RO(cpumask); diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c index 68a54d97d2a8..0735eb33f5f3 100644 --- a/drivers/perf/cxl_pmu.c +++ b/drivers/perf/cxl_pmu.c @@ -493,7 +493,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, { struct cxl_pmu_info *info = dev_get_drvdata(dev); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(info->on_cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(info->on_cpu))); } static DEVICE_ATTR_RO(cpumask); diff --git a/drivers/perf/dwc_pcie_pmu.c b/drivers/perf/dwc_pcie_pmu.c index 5385401fa9cf..291e776d6f6a 100644 --- a/drivers/perf/dwc_pcie_pmu.c +++ b/drivers/perf/dwc_pcie_pmu.c @@ -117,7 +117,7 @@ static ssize_t cpumask_show(struct device *dev, { struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pcie_pmu->on_cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pcie_pmu->on_cpu))); } static DEVICE_ATTR_RO(cpumask); diff --git a/drivers/perf/fsl_imx8_ddr_perf.c b/drivers/perf/fsl_imx8_ddr_perf.c index bcdf5575d71c..3760ebe02674 100644 --- a/drivers/perf/fsl_imx8_ddr_perf.c +++ b/drivers/perf/fsl_imx8_ddr_perf.c @@ -237,7 +237,7 @@ static ssize_t ddr_perf_cpumask_show(struct device *dev, { struct ddr_pmu *pmu = dev_get_drvdata(dev); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); } static struct device_attribute ddr_perf_cpumask_attr = diff --git a/drivers/perf/fsl_imx9_ddr_perf.c b/drivers/perf/fsl_imx9_ddr_perf.c index 7050b48c0467..6fee5eb5087a 100644 --- a/drivers/perf/fsl_imx9_ddr_perf.c +++ b/drivers/perf/fsl_imx9_ddr_perf.c @@ -159,7 +159,7 @@ static ssize_t ddr_perf_cpumask_show(struct device *dev, { struct ddr_pmu *pmu = dev_get_drvdata(dev); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); } static struct device_attribute ddr_perf_cpumask_attr = diff --git a/drivers/perf/fujitsu_uncore_pmu.c b/drivers/perf/fujitsu_uncore_pmu.c index c3c6f56474ad..a07877632d53 100644 --- a/drivers/perf/fujitsu_uncore_pmu.c +++ b/drivers/perf/fujitsu_uncore_pmu.c @@ -374,7 +374,7 @@ static ssize_t cpumask_show(struct device *dev, { struct uncore_pmu *uncorepmu = to_uncore_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(uncorepmu->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(uncorepmu->cpu))); } static DEVICE_ATTR_RO(cpumask); diff --git a/drivers/perf/hisilicon/hisi_pcie_pmu.c b/drivers/perf/hisilicon/hisi_pcie_pmu.c index c5394d007b61..0f55d871c67e 100644 --- a/drivers/perf/hisilicon/hisi_pcie_pmu.c +++ b/drivers/perf/hisilicon/hisi_pcie_pmu.c @@ -121,7 +121,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, c { struct hisi_pcie_pmu *pcie_pmu = to_pcie_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pcie_pmu->on_cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pcie_pmu->on_cpu))); } static DEVICE_ATTR_RO(cpumask); diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.c b/drivers/perf/hisilicon/hisi_uncore_pmu.c index de71dcf11653..0ff2fdf4b3e2 100644 --- a/drivers/perf/hisilicon/hisi_uncore_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.c @@ -56,7 +56,7 @@ static ssize_t hisi_associated_cpus_sysfs_show(struct device *dev, { struct hisi_pmu *hisi_pmu = to_hisi_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, &hisi_pmu->associated_cpus); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&hisi_pmu->associated_cpus)); } static DEVICE_ATTR(associated_cpus, 0444, hisi_associated_cpus_sysfs_show, NULL); diff --git a/drivers/perf/marvell_cn10k_ddr_pmu.c b/drivers/perf/marvell_cn10k_ddr_pmu.c index 72ac17efd846..8681e8715cb3 100644 --- a/drivers/perf/marvell_cn10k_ddr_pmu.c +++ b/drivers/perf/marvell_cn10k_ddr_pmu.c @@ -364,7 +364,7 @@ static ssize_t cn10k_ddr_perf_cpumask_show(struct device *dev, { struct cn10k_ddr_pmu *pmu = dev_get_drvdata(dev); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); } static struct device_attribute cn10k_ddr_perf_cpumask_attr = diff --git a/drivers/perf/marvell_cn10k_tad_pmu.c b/drivers/perf/marvell_cn10k_tad_pmu.c index 51ccb0befa05..54909d0031b7 100644 --- a/drivers/perf/marvell_cn10k_tad_pmu.c +++ b/drivers/perf/marvell_cn10k_tad_pmu.c @@ -258,7 +258,7 @@ static ssize_t tad_pmu_cpumask_show(struct device *dev, { struct tad_pmu *tad_pmu = to_tad_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(tad_pmu->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(tad_pmu->cpu))); } static DEVICE_ATTR(cpumask, 0444, tad_pmu_cpumask_show, NULL); diff --git a/drivers/perf/marvell_pem_pmu.c b/drivers/perf/marvell_pem_pmu.c index 29fbcd1848e4..cf1d8cdb1318 100644 --- a/drivers/perf/marvell_pem_pmu.c +++ b/drivers/perf/marvell_pem_pmu.c @@ -164,7 +164,7 @@ static ssize_t pem_perf_cpumask_show(struct device *dev, { struct pem_pmu *pmu = dev_get_drvdata(dev); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); } static struct device_attribute pem_perf_cpumask_attr = diff --git a/drivers/perf/nvidia_t410_c2c_pmu.c b/drivers/perf/nvidia_t410_c2c_pmu.c index 411987153ff3..bff875f4f625 100644 --- a/drivers/perf/nvidia_t410_c2c_pmu.c +++ b/drivers/perf/nvidia_t410_c2c_pmu.c @@ -658,7 +658,7 @@ static ssize_t nv_c2c_pmu_cpumask_show(struct device *dev, default: return 0; } - return cpumap_print_to_pagebuf(true, buf, cpumask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); } #define NV_C2C_PMU_CPUMASK_ATTR(_name, _config) \ diff --git a/drivers/perf/nvidia_t410_cmem_latency_pmu.c b/drivers/perf/nvidia_t410_cmem_latency_pmu.c index acb8f5571522..6c8e41598ec1 100644 --- a/drivers/perf/nvidia_t410_cmem_latency_pmu.c +++ b/drivers/perf/nvidia_t410_cmem_latency_pmu.c @@ -501,7 +501,7 @@ static ssize_t cmem_lat_pmu_cpumask_show(struct device *dev, default: return 0; } - return cpumap_print_to_pagebuf(true, buf, cpumask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); } #define NV_PMU_CPUMASK_ATTR(_name, _config) \ diff --git a/drivers/perf/qcom_l2_pmu.c b/drivers/perf/qcom_l2_pmu.c index ea8c85729937..c0c522b10b72 100644 --- a/drivers/perf/qcom_l2_pmu.c +++ b/drivers/perf/qcom_l2_pmu.c @@ -638,7 +638,7 @@ static ssize_t l2_cache_pmu_cpumask_show(struct device *dev, { struct l2cache_pmu *l2cache_pmu = to_l2cache_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, &l2cache_pmu->cpumask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&l2cache_pmu->cpumask)); } static struct device_attribute l2_cache_pmu_cpumask_attr = diff --git a/drivers/perf/qcom_l3_pmu.c b/drivers/perf/qcom_l3_pmu.c index 66e6cabd6fff..c8d259dd1f80 100644 --- a/drivers/perf/qcom_l3_pmu.c +++ b/drivers/perf/qcom_l3_pmu.c @@ -663,7 +663,7 @@ static ssize_t cpumask_show(struct device *dev, { struct l3cache_pmu *l3pmu = to_l3cache_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, &l3pmu->cpumask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&l3pmu->cpumask)); } static DEVICE_ATTR_RO(cpumask); diff --git a/drivers/perf/starfive_starlink_pmu.c b/drivers/perf/starfive_starlink_pmu.c index 964897c2baa9..222a0a34e211 100644 --- a/drivers/perf/starfive_starlink_pmu.c +++ b/drivers/perf/starfive_starlink_pmu.c @@ -131,7 +131,7 @@ cpumask_show(struct device *dev, struct device_attribute *attr, char *buf) { struct starlink_pmu *starlink_pmu = to_starlink_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, &starlink_pmu->cpumask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&starlink_pmu->cpumask)); } static DEVICE_ATTR_RO(cpumask); diff --git a/drivers/perf/thunderx2_pmu.c b/drivers/perf/thunderx2_pmu.c index 6ed4707bd6bb..a69c02d2d874 100644 --- a/drivers/perf/thunderx2_pmu.c +++ b/drivers/perf/thunderx2_pmu.c @@ -254,7 +254,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, struct tx2_uncore_pmu *tx2_pmu; tx2_pmu = pmu_to_tx2_pmu(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, cpumask_of(tx2_pmu->cpu)); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(tx2_pmu->cpu))); } static DEVICE_ATTR_RO(cpumask); diff --git a/drivers/perf/xgene_pmu.c b/drivers/perf/xgene_pmu.c index 33b5497bdc06..e9e4871db08d 100644 --- a/drivers/perf/xgene_pmu.c +++ b/drivers/perf/xgene_pmu.c @@ -595,7 +595,7 @@ static ssize_t cpumask_show(struct device *dev, { struct xgene_pmu_dev *pmu_dev = to_pmu_dev(dev_get_drvdata(dev)); - return cpumap_print_to_pagebuf(true, buf, &pmu_dev->parent->cpu); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&pmu_dev->parent->cpu)); } static DEVICE_ATTR_RO(cpumask); diff --git a/kernel/events/core.c b/kernel/events/core.c index 7935d5663944..61689d348abd 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -12657,7 +12657,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, struct cpumask *mask = perf_scope_cpumask(pmu->scope); if (mask) - return cpumap_print_to_pagebuf(true, buf, mask); + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(mask)); return 0; } -- 2.51.0 From ynorov at nvidia.com Thu May 28 11:36:21 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 14:36:21 -0400 Subject: [PATCH 14/16] powercap: intel_rapl: Use sysfs_emit() for cpumask show In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528183625.870813-15-ynorov@nvidia.com> cpumask_show() is a sysfs show callback. Use sysfs_emit() and cpumask_pr_args() to emit the mask. This prepares for removing cpumap_print_to_pagebuf(). Signed-off-by: Yury Norov --- drivers/powercap/intel_rapl_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/powercap/intel_rapl_common.c b/drivers/powercap/intel_rapl_common.c index a8dd02dff0a0..b38d4a7799a8 100644 --- a/drivers/powercap/intel_rapl_common.c +++ b/drivers/powercap/intel_rapl_common.c @@ -1441,7 +1441,7 @@ static ssize_t cpumask_show(struct device *dev, } cpus_read_unlock(); - ret = cpumap_print_to_pagebuf(true, buf, cpu_mask); + ret = sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpu_mask)); free_cpumask_var(cpu_mask); -- 2.51.0 From akpm at linux-foundation.org Thu May 28 12:18:06 2026 From: akpm at linux-foundation.org (Andrew Morton) Date: Thu, 28 May 2026 12:18:06 -0700 Subject: [PATCH 00/16] lib/cpumask: get rid of cpumap_print_to_pagebuf() In-Reply-To: <20260528183625.870813-1-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> Message-ID: <20260528121806.2b54606ba6e42f7f371d95c3@linux-foundation.org> On Thu, 28 May 2026 14:36:07 -0400 Yury Norov wrote: > cpumap_print_to_pagebuf() is the equivalent for the "&*pb[l]" notation > in printk-like functions. In some cases, it makes people to create > temporary buffers for the printed cpumasks, where it can be avoided. > > Get rid of it in a favor of more standard printing API. > > Each patch, except for the last one, is independent and may be moved with > the corresponding subsystem. Or I can take it in bitmap-for-next, at > maintainers' discretion. > > On top of bitmap-for-next. Sashiko doesn't attempt bitmap-for-next, so it couldn't apply this series. https://sashiko.dev/#/patchset/20260528183625.870813-1-ynorov at nvidia.com From ynorov at nvidia.com Thu May 28 12:26:28 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 15:26:28 -0400 Subject: [PATCH 00/16] lib/cpumask: get rid of cpumap_print_to_pagebuf() In-Reply-To: <20260528121806.2b54606ba6e42f7f371d95c3@linux-foundation.org> References: <20260528183625.870813-1-ynorov@nvidia.com> <20260528121806.2b54606ba6e42f7f371d95c3@linux-foundation.org> Message-ID: On Thu, May 28, 2026 at 12:18:06PM -0700, Andrew Morton wrote: > On Thu, 28 May 2026 14:36:07 -0400 Yury Norov wrote: > > > cpumap_print_to_pagebuf() is the equivalent for the "&*pb[l]" notation > > in printk-like functions. In some cases, it makes people to create > > temporary buffers for the printed cpumasks, where it can be avoided. > > > > Get rid of it in a favor of more standard printing API. > > > > Each patch, except for the last one, is independent and may be moved with > > the corresponding subsystem. Or I can take it in bitmap-for-next, at > > maintainers' discretion. > > > > On top of bitmap-for-next. > > Sashiko doesn't attempt bitmap-for-next, so it couldn't apply this series. > https://sashiko.dev/#/patchset/20260528183625.870813-1-ynorov at nvidia.com OK... What should I do about that? From akpm at linux-foundation.org Thu May 28 12:29:03 2026 From: akpm at linux-foundation.org (Andrew Morton) Date: Thu, 28 May 2026 12:29:03 -0700 Subject: [PATCH 00/16] lib/cpumask: get rid of cpumap_print_to_pagebuf() In-Reply-To: References: <20260528183625.870813-1-ynorov@nvidia.com> <20260528121806.2b54606ba6e42f7f371d95c3@linux-foundation.org> Message-ID: <20260528122903.cf74cf905418ab2d144607c3@linux-foundation.org> On Thu, 28 May 2026 15:26:28 -0400 Yury Norov wrote: > On Thu, May 28, 2026 at 12:18:06PM -0700, Andrew Morton wrote: > > On Thu, 28 May 2026 14:36:07 -0400 Yury Norov wrote: > > > > > cpumap_print_to_pagebuf() is the equivalent for the "&*pb[l]" notation > > > in printk-like functions. In some cases, it makes people to create > > > temporary buffers for the printed cpumasks, where it can be avoided. > > > > > > Get rid of it in a favor of more standard printing API. > > > > > > Each patch, except for the last one, is independent and may be moved with > > > the corresponding subsystem. Or I can take it in bitmap-for-next, at > > > maintainers' discretion. > > > > > > On top of bitmap-for-next. > > > > Sashiko doesn't attempt bitmap-for-next, so it couldn't apply this series. > > https://sashiko.dev/#/patchset/20260528183625.870813-1-ynorov at nvidia.com > > OK... What should I do about that? Rebase onto something which Sashiko *does* attempt. Mainline, a few mm.git branches. Maybe linux-next. Roman, is there a list of trees/branches which Sashiko tries to apply series to? Thanks. From ynorov at nvidia.com Thu May 28 12:32:34 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 15:32:34 -0400 Subject: [PATCH 00/16] lib/cpumask: get rid of cpumap_print_to_pagebuf() In-Reply-To: <20260528122903.cf74cf905418ab2d144607c3@linux-foundation.org> References: <20260528183625.870813-1-ynorov@nvidia.com> <20260528121806.2b54606ba6e42f7f371d95c3@linux-foundation.org> <20260528122903.cf74cf905418ab2d144607c3@linux-foundation.org> Message-ID: On Thu, May 28, 2026 at 12:29:03PM -0700, Andrew Morton wrote: > On Thu, 28 May 2026 15:26:28 -0400 Yury Norov wrote: > > > On Thu, May 28, 2026 at 12:18:06PM -0700, Andrew Morton wrote: > > > On Thu, 28 May 2026 14:36:07 -0400 Yury Norov wrote: > > > > > > > cpumap_print_to_pagebuf() is the equivalent for the "&*pb[l]" notation > > > > in printk-like functions. In some cases, it makes people to create > > > > temporary buffers for the printed cpumasks, where it can be avoided. > > > > > > > > Get rid of it in a favor of more standard printing API. > > > > > > > > Each patch, except for the last one, is independent and may be moved with > > > > the corresponding subsystem. Or I can take it in bitmap-for-next, at > > > > maintainers' discretion. > > > > > > > > On top of bitmap-for-next. > > > > > > Sashiko doesn't attempt bitmap-for-next, so it couldn't apply this series. > > > https://sashiko.dev/#/patchset/20260528183625.870813-1-ynorov at nvidia.com > > > > OK... What should I do about that? > > Rebase onto something which Sashiko *does* attempt. Mainline, a few > mm.git branches. Maybe linux-next. Is Sashiko a new mandatory requirement now? Documentation doesn't even mention the bot. > Roman, is there a list of trees/branches which Sashiko tries to apply > series to? Hi Roman, Can you add bitmap-for-next in the list? Thanks, Yury From irogers at google.com Thu May 28 12:44:15 2026 From: irogers at google.com (Ian Rogers) Date: Thu, 28 May 2026 12:44:15 -0700 Subject: [PATCH 00/16] lib/cpumask: get rid of cpumap_print_to_pagebuf() In-Reply-To: References: <20260528183625.870813-1-ynorov@nvidia.com> <20260528121806.2b54606ba6e42f7f371d95c3@linux-foundation.org> <20260528122903.cf74cf905418ab2d144607c3@linux-foundation.org> Message-ID: On Thu, May 28, 2026 at 12:32?PM Yury Norov wrote: > > On Thu, May 28, 2026 at 12:29:03PM -0700, Andrew Morton wrote: > > On Thu, 28 May 2026 15:26:28 -0400 Yury Norov wrote: > > > > > On Thu, May 28, 2026 at 12:18:06PM -0700, Andrew Morton wrote: > > > > On Thu, 28 May 2026 14:36:07 -0400 Yury Norov wrote: > > > > > > > > > cpumap_print_to_pagebuf() is the equivalent for the "&*pb[l]" notation > > > > > in printk-like functions. In some cases, it makes people to create > > > > > temporary buffers for the printed cpumasks, where it can be avoided. > > > > > > > > > > Get rid of it in a favor of more standard printing API. > > > > > > > > > > Each patch, except for the last one, is independent and may be moved with > > > > > the corresponding subsystem. Or I can take it in bitmap-for-next, at > > > > > maintainers' discretion. > > > > > > > > > > On top of bitmap-for-next. > > > > > > > > Sashiko doesn't attempt bitmap-for-next, so it couldn't apply this series. > > > > https://sashiko.dev/#/patchset/20260528183625.870813-1-ynorov at nvidia.com > > > > > > OK... What should I do about that? > > > > Rebase onto something which Sashiko *does* attempt. Mainline, a few > > mm.git branches. Maybe linux-next. > > Is Sashiko a new mandatory requirement now? Documentation doesn't even > mention the bot. > > > Roman, is there a list of trees/branches which Sashiko tries to apply > > series to? > > Hi Roman, > > Can you add bitmap-for-next in the list? Fwiw, you can see the list of branches attempted and the SHA they are at in the Baseline drop down: Baseline Status Log tip/x86/core (0f61b1860cc3f52aef9036d7235ed1f017632193) Failed View Log powerpc/HEAD (6916d5703ddf9a38f1f6c2cc793381a24ee914c6) Failed View Log chanwoo/HEAD (7fd2df204f342fc17d1a0bfcd474b24232fb0f32) Failed View Log linux-arm/HEAD (dd6c438c3e64a5ff0b5d7e78f7f9be547803ef1b) Failed View Log linux-pm/HEAD (e7ae89a0c97ce2b68b0983cd01eda67cf373517d) Failed View Log linux-fpga/HEAD Failed View Log pci/HEAD (254f49634ee16a731174d2ae34bc50bd5f45e731) Failed View Log linux-pm/thermal (21c315342b81526874acfa311f11b3f72bed4e14) Failed View Log rdma/HEAD (67464f388d52ec172be62c99fc43697437ffa384) Failed View Log linux-next/HEAD (f7af91adc230aa99e23330ecf85bc9badd9780ad) Failed View Log HEAD (917719c412c48687d4a176965d1fa35320ec457c) Failed View Log Thanks, Ian From ynorov at nvidia.com Thu May 28 12:52:29 2026 From: ynorov at nvidia.com (Yury Norov) Date: Thu, 28 May 2026 15:52:29 -0400 Subject: [PATCH 00/16] lib/cpumask: get rid of cpumap_print_to_pagebuf() In-Reply-To: References: <20260528183625.870813-1-ynorov@nvidia.com> <20260528121806.2b54606ba6e42f7f371d95c3@linux-foundation.org> <20260528122903.cf74cf905418ab2d144607c3@linux-foundation.org> Message-ID: On Thu, May 28, 2026 at 12:44:15PM -0700, Ian Rogers wrote: > On Thu, May 28, 2026 at 12:32?PM Yury Norov wrote: > > > > On Thu, May 28, 2026 at 12:29:03PM -0700, Andrew Morton wrote: > > > On Thu, 28 May 2026 15:26:28 -0400 Yury Norov wrote: > > > > > > > On Thu, May 28, 2026 at 12:18:06PM -0700, Andrew Morton wrote: > > > > > On Thu, 28 May 2026 14:36:07 -0400 Yury Norov wrote: > > > > > > > > > > > cpumap_print_to_pagebuf() is the equivalent for the "&*pb[l]" notation > > > > > > in printk-like functions. In some cases, it makes people to create > > > > > > temporary buffers for the printed cpumasks, where it can be avoided. > > > > > > > > > > > > Get rid of it in a favor of more standard printing API. > > > > > > > > > > > > Each patch, except for the last one, is independent and may be moved with > > > > > > the corresponding subsystem. Or I can take it in bitmap-for-next, at > > > > > > maintainers' discretion. > > > > > > > > > > > > On top of bitmap-for-next. > > > > > > > > > > Sashiko doesn't attempt bitmap-for-next, so it couldn't apply this series. > > > > > https://sashiko.dev/#/patchset/20260528183625.870813-1-ynorov at nvidia.com > > > > > > > > OK... What should I do about that? > > > > > > Rebase onto something which Sashiko *does* attempt. Mainline, a few > > > mm.git branches. Maybe linux-next. > > > > Is Sashiko a new mandatory requirement now? Documentation doesn't even > > mention the bot. > > > > > Roman, is there a list of trees/branches which Sashiko tries to apply > > > series to? > > > > Hi Roman, > > > > Can you add bitmap-for-next in the list? > > Fwiw, you can see the list of branches attempted and the SHA they are > at in the Baseline drop down: > > Baseline Status Log > tip/x86/core (0f61b1860cc3f52aef9036d7235ed1f017632193) Failed View Log > powerpc/HEAD (6916d5703ddf9a38f1f6c2cc793381a24ee914c6) Failed View Log > chanwoo/HEAD (7fd2df204f342fc17d1a0bfcd474b24232fb0f32) Failed View Log > linux-arm/HEAD (dd6c438c3e64a5ff0b5d7e78f7f9be547803ef1b) Failed View Log > linux-pm/HEAD (e7ae89a0c97ce2b68b0983cd01eda67cf373517d) Failed View Log > linux-fpga/HEAD Failed View Log > pci/HEAD (254f49634ee16a731174d2ae34bc50bd5f45e731) Failed View Log > linux-pm/thermal (21c315342b81526874acfa311f11b3f72bed4e14) Failed View Log > rdma/HEAD (67464f388d52ec172be62c99fc43697437ffa384) Failed View Log > linux-next/HEAD (f7af91adc230aa99e23330ecf85bc9badd9780ad) Failed View Log > HEAD (917719c412c48687d4a176965d1fa35320ec457c) Failed View Log Thanks, Ian. Bitmap-for-next is tracked with linux-next. From akpm at linux-foundation.org Thu May 28 13:02:11 2026 From: akpm at linux-foundation.org (Andrew Morton) Date: Thu, 28 May 2026 13:02:11 -0700 Subject: [PATCH 00/16] lib/cpumask: get rid of cpumap_print_to_pagebuf() In-Reply-To: References: <20260528183625.870813-1-ynorov@nvidia.com> <20260528121806.2b54606ba6e42f7f371d95c3@linux-foundation.org> <20260528122903.cf74cf905418ab2d144607c3@linux-foundation.org> Message-ID: <20260528130211.54589cb876ca5e0d55caf117@linux-foundation.org> On Thu, 28 May 2026 15:32:34 -0400 Yury Norov wrote: > > > > Sashiko doesn't attempt bitmap-for-next, so it couldn't apply this series. > > > > https://sashiko.dev/#/patchset/20260528183625.870813-1-ynorov at nvidia.com > > > > > > OK... What should I do about that? > > > > Rebase onto something which Sashiko *does* attempt. Mainline, a few > > mm.git branches. Maybe linux-next. > > Is Sashiko a new mandatory requirement now? Documentation doesn't even > mention the bot. It's early days and things are still evolving. No, I'm not aware of any team having made it mandatory but boy it's helpful. Authors appreciate it because it finds bugs, and nobody wants to add bugs to Linux. From xianwei.zhao at amlogic.com Thu May 28 20:02:11 2026 From: xianwei.zhao at amlogic.com (Xianwei Zhao) Date: Fri, 29 May 2026 11:02:11 +0800 Subject: [PATCH v2 0/2] pinctrl: add support amlogic a9 In-Reply-To: References: <20260507-a9-pinctrl-v2-0-49774feff2ef@amlogic.com> Message-ID: <36cdfa84-1e83-4a44-a529-653d476778fd@amlogic.com> Hi Linus, I don't see these patches in the for-next branch yet. Were they intentionally left out? On 2026/5/12 04:26, Linus Walleij wrote: > On Thu, May 7, 2026 at 10:21?AM Xianwei Zhao via B4 Relay > wrote: > >> Add pinctrl bindings and driver about for amlogic a9. >> >> Signed-off-by: Xianwei Zhao > Patches applied! > > Yours, > Linus Walleij From yi.s.ding at gmail.com Thu May 28 23:00:05 2026 From: yi.s.ding at gmail.com (Yi Ding) Date: Thu, 28 May 2026 23:00:05 -0700 Subject: [PATCH] media: cec: meson: ao-cec-g12a: name the CEC core regmap to avoid debugfs clash Message-ID: <20260529060005.94700-1-yi.s.ding@gmail.com> The driver registers two regmaps on the same platform device: an MMIO regmap for the AO CEC registers, and an indirect regmap (using reg_read()/reg_write() callbacks) for the CEC controller core registers. Neither regmap_config sets a .name, so both default their debugfs directory to the device name and collide: debugfs: 'ff800280.cec' already exists in 'regmap' Because of the clash the second regmap's debugfs directory fails to register, so its registers can no longer be inspected via debugfs. Give the indirect CEC core regmap a distinct name. The two debugfs directories then become ".cec" and ".cec-core". This only affects debugfs naming; register access is unchanged. Tested on an ODROID-N2 (Amlogic S922X): the warning is gone and both /sys/kernel/debug/regmap/ff800280.cec and ff800280.cec-core are present. Fixes: b7778c46683c ("media: platform: meson: Add Amlogic Meson G12A AO CEC Controller driver") Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Yi Ding --- drivers/media/cec/platform/meson/ao-cec-g12a.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/cec/platform/meson/ao-cec-g12a.c b/drivers/media/cec/platform/meson/ao-cec-g12a.c index 41f5b8669..2c914f000 100644 --- a/drivers/media/cec/platform/meson/ao-cec-g12a.c +++ b/drivers/media/cec/platform/meson/ao-cec-g12a.c @@ -405,6 +405,7 @@ static int meson_ao_cec_g12a_write(void *context, unsigned int addr, } static const struct regmap_config meson_ao_cec_g12a_cec_regmap_conf = { + .name = "core", .reg_bits = 8, .val_bits = 8, .reg_read = meson_ao_cec_g12a_read, -- 2.47.3 From jian.hu at amlogic.com Fri May 29 00:08:24 2026 From: jian.hu at amlogic.com (Jian Hu) Date: Fri, 29 May 2026 15:08:24 +0800 Subject: [PATCH 07/10] clk: amlogic: Support POWER_OF_TWO for PLL pre-divider In-Reply-To: <1ja4tm5pb3.fsf@starbuckisacylon.baylibre.com> References: <20260511-b4-a9_clk-v1-0-41cb4071b7c9@amlogic.com> <20260511-b4-a9_clk-v1-7-41cb4071b7c9@amlogic.com> <1jy0hm6n7e.fsf@starbuckisacylon.baylibre.com> <8d89b669-e72e-4663-9596-999a12922d32@amlogic.com> <1jqzn65y9l.fsf@starbuckisacylon.baylibre.com> <3fda1592-f7d0-4e86-8615-602804673414@amlogic.com> <1ja4tm5pb3.fsf@starbuckisacylon.baylibre.com> Message-ID: On 5/26/2026 8:27 PM, Jerome Brunet wrote: > [ EXTERNAL EMAIL ] > > On mar. 26 mai 2026 at 17:58, Jian Hu wrote: > >> On 5/20/2026 3:35 PM, Jerome Brunet wrote: >>> [ EXTERNAL EMAIL ] >>> >>> On mer. 20 mai 2026 at 13:47, Jian Hu wrote: >>> >>>> On 5/14/2026 11:11 PM, Jerome Brunet wrote: >>>>> [ EXTERNAL EMAIL ] >>>>> >>>>> On lun. 11 mai 2026 at 20:47, Jian Hu via B4 Relay wrote: >>>>> >>>>>> From: Jian Hu >>>>>> >>>>>> The A9 PLL pre-divider uses a division factor of 2^n to ensure a clock >>>>>> duty cycle of 50% after predivision. >>>>>> >>>>>> Add flag 'CLK_MESON_PLL_N_POWER_OF_TWO' to indicate that the PLL >>>>>> pre-divider division factor is 2^n. >>>>> I understand what you are doing here but I have to ask why this can't be >>>>> implemented with independent dividers that already supports power of 2 ? >>>> If we use independent dividers, the n member would have to be removed from >>>> meson_clk_pll_data. >>>> >>>> However, n is referenced 35 times in clk-pll.c, which means we would need >>>> to modify all >>>> related logic across the file. This would be a relatively large >>>> change. >>> Yes >>> >>>> Moreover, for all Amlogic chips, the n divider is an indispensable part of >>>> the DCO clock. >>> There is hardly a justification here >>> >>>> The difference between SoC generations is as follows: >>>> Previous SoCs PLL: n = 1, 2, 3, 4... (linear divider) >>>> A9 SoC PLL: n = 2^0, 2^1, 2^2, 2^3, 2^4... (power-of-two >>>> divider) >>> Yes that was fairly obvious >>> >>>> Therefore, splitting out the n divider from the DCO clock might not be a >>>> good design choice. >>> I'm not sure I agree and you've only stated your point of view without >>> providing any technical justification here. >>> >>> From the datasheets of the different SoC we have, the documented >>> limitation is always the DCO output rate range. Nothing related to n (or >>> m, or the mult-range for that matter). This is a legacy problem, we >>> started with monolithic driver and slowly simplified it. >>> >>> As far as I can see now, reworking the PLL driver to be a simple >>> multiplier driver with range output rate constraint could actually be >>> simpler than the current code. I would also make simpler to accomodate >>> differences such as the one presented here. >>> >>> Unless you can provide technical reasons why going in this direction >>> would be incorrect, that's where I'd prefer to go. >>> >>>> [...] >>>> >>>> Best regards, >>>> >>>> Jian >>> -- >>> Jerome >> >> I agree that having an independent N divider would simplify the PLL rate >> calculation. >> >> A separate pre-divider for N is technically possible, but there are some >> hardware constraints that need to be considered: >> >> N = 1 is the preferred operating mode except a few fixed-frequency PLLs. >> Larger N values reduce the PLL phase detector frequency, which may >> negatively impact >> jitter performance and overall PLL stability. > Understood. You could really make a difference by going deeper and > explaining what those constraints are, especially since you ask question > internally at Amlogic. > > At the moment what is documented is a range regarding the output rate of > the PLLs. A PLL is made of a pre-divider and fractional multiplier. > and you are saying that for the multiplier to work and lock, there is > actually a constraint the input rate too. > > If you can discuss with your HW team and clarify what the constraints > really are, that would help to better model the PLL. In then more likely > for us to figure out the best way to drive it. I have discussed with the HW PLL team. And here is the discussion results: When N increases by a factor of X, the PLL bandwidth decreases by a factor of X, deviating from the default optimal bandwidth, which leads to a decrease in clock performance and affects the stability of clock-dependent modules in the chip. > >> Because of this, we cannot guarantee stable system operation when arbitrary >> larger >> N values are used. >> >> Some PLLs require non-1 N values to generate specific fixed output >> frequencies because >> the target rate cannot be achieved with N = 1 while keeping the PLL while >> keeping the >> PLL within its valid operating range. So N is designed to have other values >> ??to >> satisfy this requirement. > Again it seems like the constraints we are using are not the real > limitation, just by-products, which the situation unclear. When N=3, this type of PLL is designed with the optimal bandwidth based on N=3. >> For example, the AXG PCIe PLL uses N = 3 to generate the required 100 MHz >> output frequency, >> since the target frequency cannot be achieved with N = 1. >> > PCIe is a topic in itself. It uses different ops for historic reasons though > I suspect, with proper constraints, it would not really need to. PCIe has a strict protocol that must be followed. It is also a highly sensitive block with hundreds of complex and stringent lab test requirements. The PCIe PLL lock sequence needs to fully follow the Amlogic HW team's released demo code. >> Additionally, is the refactored pre-divider N implemented as a separate >> patchset, >> independent from the A9 PLL changes? > I could be seen as a pre-requisite. Understood. >> >> Best regards, >> >> >> Jian > -- > Jerome Best regards, Jian From neil.armstrong at linaro.org Fri May 29 00:31:27 2026 From: neil.armstrong at linaro.org (Neil Armstrong) Date: Fri, 29 May 2026 09:31:27 +0200 Subject: [PATCH] media: cec: meson: ao-cec-g12a: name the CEC core regmap to avoid debugfs clash In-Reply-To: <20260529060005.94700-1-yi.s.ding@gmail.com> References: <20260529060005.94700-1-yi.s.ding@gmail.com> Message-ID: <8ef7a26d-8e32-4684-b0ff-dc0c3ff4af73@linaro.org> On 5/29/26 08:00, Yi Ding wrote: > The driver registers two regmaps on the same platform device: an MMIO > regmap for the AO CEC registers, and an indirect regmap (using > reg_read()/reg_write() callbacks) for the CEC controller core registers. > Neither regmap_config sets a .name, so both default their debugfs > directory to the device name and collide: > > debugfs: 'ff800280.cec' already exists in 'regmap' > > Because of the clash the second regmap's debugfs directory fails to > register, so its registers can no longer be inspected via debugfs. > > Give the indirect CEC core regmap a distinct name. The two debugfs > directories then become ".cec" and ".cec-core". This only > affects debugfs naming; register access is unchanged. > > Tested on an ODROID-N2 (Amlogic S922X): the warning is gone and both > /sys/kernel/debug/regmap/ff800280.cec and ff800280.cec-core are present. > > Fixes: b7778c46683c ("media: platform: meson: Add Amlogic Meson G12A AO CEC Controller driver") > Assisted-by: Claude:claude-opus-4-8 > Signed-off-by: Yi Ding > --- > drivers/media/cec/platform/meson/ao-cec-g12a.c | 1 + > 1 file changed, 1 insertion(+) > > diff --git a/drivers/media/cec/platform/meson/ao-cec-g12a.c b/drivers/media/cec/platform/meson/ao-cec-g12a.c > index 41f5b8669..2c914f000 100644 > --- a/drivers/media/cec/platform/meson/ao-cec-g12a.c > +++ b/drivers/media/cec/platform/meson/ao-cec-g12a.c > @@ -405,6 +405,7 @@ static int meson_ao_cec_g12a_write(void *context, unsigned int addr, > } > > static const struct regmap_config meson_ao_cec_g12a_cec_regmap_conf = { > + .name = "core", > .reg_bits = 8, > .val_bits = 8, > .reg_read = meson_ao_cec_g12a_read, Good catch Reviewed-by: Neil Armstrong Thanks, Neil From robin.murphy at arm.com Fri May 29 04:05:08 2026 From: robin.murphy at arm.com (Robin Murphy) Date: Fri, 29 May 2026 12:05:08 +0100 Subject: [PATCH 13/16] perf: Use sysfs_emit() for cpumask show callbacks In-Reply-To: <20260528183625.870813-14-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> <20260528183625.870813-14-ynorov@nvidia.com> Message-ID: <7e980b99-1e4e-408b-8ebd-4d28116e7ad5@arm.com> On 2026-05-28 7:36 pm, Yury Norov wrote: > These callbacks are sysfs show paths. > > Use sysfs_emit() and cpumask_pr_args() to emit the masks. > > This prepares for removing cpumap_print_to_pagebuf(). TBH, looking at this diff I think it only shows the value of having a helper to abstract the boilerplate... I'm not sure I agree with the argument of removing something entirely just because it may occasionally be misused, but could we at least have something like: #define sysfs_emit_cpumask(buf, mask) \ sysfs_emit((buf), "%*pbl\n", cpumask_pr_args(mask)) to save the mess in all the many places where the current cpumap_print_to_pagebuf() usage _is_ entirely appropriate? Thansk, Robin. > Signed-off-by: Yury Norov > --- > drivers/perf/alibaba_uncore_drw_pmu.c | 2 +- > drivers/perf/amlogic/meson_ddr_pmu_core.c | 2 +- > drivers/perf/arm-cci.c | 2 +- > drivers/perf/arm-ccn.c | 2 +- > drivers/perf/arm-cmn.c | 2 +- > drivers/perf/arm-ni.c | 2 +- > drivers/perf/arm_cspmu/arm_cspmu.c | 2 +- > drivers/perf/arm_dmc620_pmu.c | 4 ++-- > drivers/perf/arm_dsu_pmu.c | 2 +- > drivers/perf/arm_pmu.c | 2 +- > drivers/perf/arm_smmuv3_pmu.c | 2 +- > drivers/perf/arm_spe_pmu.c | 2 +- > drivers/perf/cxl_pmu.c | 2 +- > drivers/perf/dwc_pcie_pmu.c | 2 +- > drivers/perf/fsl_imx8_ddr_perf.c | 2 +- > drivers/perf/fsl_imx9_ddr_perf.c | 2 +- > drivers/perf/fujitsu_uncore_pmu.c | 2 +- > drivers/perf/hisilicon/hisi_pcie_pmu.c | 2 +- > drivers/perf/hisilicon/hisi_uncore_pmu.c | 2 +- > drivers/perf/marvell_cn10k_ddr_pmu.c | 2 +- > drivers/perf/marvell_cn10k_tad_pmu.c | 2 +- > drivers/perf/marvell_pem_pmu.c | 2 +- > drivers/perf/nvidia_t410_c2c_pmu.c | 2 +- > drivers/perf/nvidia_t410_cmem_latency_pmu.c | 2 +- > drivers/perf/qcom_l2_pmu.c | 2 +- > drivers/perf/qcom_l3_pmu.c | 2 +- > drivers/perf/starfive_starlink_pmu.c | 2 +- > drivers/perf/thunderx2_pmu.c | 2 +- > drivers/perf/xgene_pmu.c | 2 +- > kernel/events/core.c | 2 +- > 30 files changed, 31 insertions(+), 31 deletions(-) > > diff --git a/drivers/perf/alibaba_uncore_drw_pmu.c b/drivers/perf/alibaba_uncore_drw_pmu.c > index ac49d3b2dad6..74786a5dd6a2 100644 > --- a/drivers/perf/alibaba_uncore_drw_pmu.c > +++ b/drivers/perf/alibaba_uncore_drw_pmu.c > @@ -221,7 +221,7 @@ static ssize_t ali_drw_pmu_cpumask_show(struct device *dev, > { > struct ali_drw_pmu *drw_pmu = to_ali_drw_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(drw_pmu->cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(drw_pmu->cpu))); > } > > static struct device_attribute ali_drw_pmu_cpumask_attr = > diff --git a/drivers/perf/amlogic/meson_ddr_pmu_core.c b/drivers/perf/amlogic/meson_ddr_pmu_core.c > index c1e755c356a3..f614aa3434a5 100644 > --- a/drivers/perf/amlogic/meson_ddr_pmu_core.c > +++ b/drivers/perf/amlogic/meson_ddr_pmu_core.c > @@ -191,7 +191,7 @@ static ssize_t meson_ddr_perf_cpumask_show(struct device *dev, > { > struct ddr_pmu *pmu = dev_get_drvdata(dev); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); > } > > static struct device_attribute meson_ddr_perf_cpumask_attr = > diff --git a/drivers/perf/arm-cci.c b/drivers/perf/arm-cci.c > index 1cc3214d6b6d..f0ef0a679e74 100644 > --- a/drivers/perf/arm-cci.c > +++ b/drivers/perf/arm-cci.c > @@ -1351,7 +1351,7 @@ static ssize_t pmu_cpumask_attr_show(struct device *dev, > struct pmu *pmu = dev_get_drvdata(dev); > struct cci_pmu *cci_pmu = to_cci_pmu(pmu); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(cci_pmu->cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(cci_pmu->cpu))); > } > > static struct device_attribute pmu_cpumask_attr = > diff --git a/drivers/perf/arm-ccn.c b/drivers/perf/arm-ccn.c > index 8af3563fdf60..d5dcb4280434 100644 > --- a/drivers/perf/arm-ccn.c > +++ b/drivers/perf/arm-ccn.c > @@ -538,7 +538,7 @@ static ssize_t arm_ccn_pmu_cpumask_show(struct device *dev, > { > struct arm_ccn *ccn = pmu_to_arm_ccn(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(ccn->dt.cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(ccn->dt.cpu))); > } > > static struct device_attribute arm_ccn_pmu_cpumask_attr = > diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c > index f5305c8fdca4..2187ba763b72 100644 > --- a/drivers/perf/arm-cmn.c > +++ b/drivers/perf/arm-cmn.c > @@ -1326,7 +1326,7 @@ static ssize_t arm_cmn_cpumask_show(struct device *dev, > { > struct arm_cmn *cmn = to_cmn(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(cmn->cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(cmn->cpu))); > } > > static struct device_attribute arm_cmn_cpumask_attr = > diff --git a/drivers/perf/arm-ni.c b/drivers/perf/arm-ni.c > index 66858c65215d..03a1c6bf9223 100644 > --- a/drivers/perf/arm-ni.c > +++ b/drivers/perf/arm-ni.c > @@ -239,7 +239,7 @@ static ssize_t arm_ni_cpumask_show(struct device *dev, > { > struct arm_ni *ni = cd_to_ni(pmu_to_cd(dev_get_drvdata(dev))); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(ni->cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(ni->cpu))); > } > > static struct device_attribute arm_ni_cpumask_attr = > diff --git a/drivers/perf/arm_cspmu/arm_cspmu.c b/drivers/perf/arm_cspmu/arm_cspmu.c > index 80fb314d5135..e6292021f653 100644 > --- a/drivers/perf/arm_cspmu/arm_cspmu.c > +++ b/drivers/perf/arm_cspmu/arm_cspmu.c > @@ -305,7 +305,7 @@ static ssize_t arm_cspmu_cpumask_show(struct device *dev, > default: > return 0; > } > - return cpumap_print_to_pagebuf(true, buf, cpumask); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); > } > > static struct attribute *arm_cspmu_cpumask_attrs[] = { > diff --git a/drivers/perf/arm_dmc620_pmu.c b/drivers/perf/arm_dmc620_pmu.c > index 4f6b196160f8..467147a05eec 100644 > --- a/drivers/perf/arm_dmc620_pmu.c > +++ b/drivers/perf/arm_dmc620_pmu.c > @@ -237,8 +237,8 @@ static ssize_t dmc620_pmu_cpumask_show(struct device *dev, > { > struct dmc620_pmu *dmc620_pmu = to_dmc620_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, > - cpumask_of(dmc620_pmu->irq->cpu)); > + return sysfs_emit(buf, "%*pbl\n", > + cpumask_pr_args(cpumask_of(dmc620_pmu->irq->cpu))); > } > > static struct device_attribute dmc620_pmu_cpumask_attr = > diff --git a/drivers/perf/arm_dsu_pmu.c b/drivers/perf/arm_dsu_pmu.c > index 32b0dd7c693b..bcbd19e075a5 100644 > --- a/drivers/perf/arm_dsu_pmu.c > +++ b/drivers/perf/arm_dsu_pmu.c > @@ -157,7 +157,7 @@ static ssize_t dsu_pmu_cpumask_show(struct device *dev, > default: > return 0; > } > - return cpumap_print_to_pagebuf(true, buf, cpumask); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); > } > > static struct attribute *dsu_pmu_format_attrs[] = { > diff --git a/drivers/perf/arm_pmu.c b/drivers/perf/arm_pmu.c > index 939bcbd433aa..51ab6cc52ca0 100644 > --- a/drivers/perf/arm_pmu.c > +++ b/drivers/perf/arm_pmu.c > @@ -570,7 +570,7 @@ static ssize_t cpus_show(struct device *dev, > struct device_attribute *attr, char *buf) > { > struct arm_pmu *armpmu = to_arm_pmu(dev_get_drvdata(dev)); > - return cpumap_print_to_pagebuf(true, buf, &armpmu->supported_cpus); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&armpmu->supported_cpus)); > } > > static DEVICE_ATTR_RO(cpus); > diff --git a/drivers/perf/arm_smmuv3_pmu.c b/drivers/perf/arm_smmuv3_pmu.c > index 621f02a7f43b..8ce34e6bb82b 100644 > --- a/drivers/perf/arm_smmuv3_pmu.c > +++ b/drivers/perf/arm_smmuv3_pmu.c > @@ -537,7 +537,7 @@ static ssize_t smmu_pmu_cpumask_show(struct device *dev, > { > struct smmu_pmu *smmu_pmu = to_smmu_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(smmu_pmu->on_cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(smmu_pmu->on_cpu))); > } > > static struct device_attribute smmu_pmu_cpumask_attr = > diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c > index dbd0da111639..9f786fd48cdd 100644 > --- a/drivers/perf/arm_spe_pmu.c > +++ b/drivers/perf/arm_spe_pmu.c > @@ -343,7 +343,7 @@ static ssize_t cpumask_show(struct device *dev, > { > struct arm_spe_pmu *spe_pmu = dev_get_drvdata(dev); > > - return cpumap_print_to_pagebuf(true, buf, &spe_pmu->supported_cpus); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&spe_pmu->supported_cpus)); > } > static DEVICE_ATTR_RO(cpumask); > > diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c > index 68a54d97d2a8..0735eb33f5f3 100644 > --- a/drivers/perf/cxl_pmu.c > +++ b/drivers/perf/cxl_pmu.c > @@ -493,7 +493,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, > { > struct cxl_pmu_info *info = dev_get_drvdata(dev); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(info->on_cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(info->on_cpu))); > } > static DEVICE_ATTR_RO(cpumask); > > diff --git a/drivers/perf/dwc_pcie_pmu.c b/drivers/perf/dwc_pcie_pmu.c > index 5385401fa9cf..291e776d6f6a 100644 > --- a/drivers/perf/dwc_pcie_pmu.c > +++ b/drivers/perf/dwc_pcie_pmu.c > @@ -117,7 +117,7 @@ static ssize_t cpumask_show(struct device *dev, > { > struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pcie_pmu->on_cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pcie_pmu->on_cpu))); > } > static DEVICE_ATTR_RO(cpumask); > > diff --git a/drivers/perf/fsl_imx8_ddr_perf.c b/drivers/perf/fsl_imx8_ddr_perf.c > index bcdf5575d71c..3760ebe02674 100644 > --- a/drivers/perf/fsl_imx8_ddr_perf.c > +++ b/drivers/perf/fsl_imx8_ddr_perf.c > @@ -237,7 +237,7 @@ static ssize_t ddr_perf_cpumask_show(struct device *dev, > { > struct ddr_pmu *pmu = dev_get_drvdata(dev); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); > } > > static struct device_attribute ddr_perf_cpumask_attr = > diff --git a/drivers/perf/fsl_imx9_ddr_perf.c b/drivers/perf/fsl_imx9_ddr_perf.c > index 7050b48c0467..6fee5eb5087a 100644 > --- a/drivers/perf/fsl_imx9_ddr_perf.c > +++ b/drivers/perf/fsl_imx9_ddr_perf.c > @@ -159,7 +159,7 @@ static ssize_t ddr_perf_cpumask_show(struct device *dev, > { > struct ddr_pmu *pmu = dev_get_drvdata(dev); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); > } > > static struct device_attribute ddr_perf_cpumask_attr = > diff --git a/drivers/perf/fujitsu_uncore_pmu.c b/drivers/perf/fujitsu_uncore_pmu.c > index c3c6f56474ad..a07877632d53 100644 > --- a/drivers/perf/fujitsu_uncore_pmu.c > +++ b/drivers/perf/fujitsu_uncore_pmu.c > @@ -374,7 +374,7 @@ static ssize_t cpumask_show(struct device *dev, > { > struct uncore_pmu *uncorepmu = to_uncore_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(uncorepmu->cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(uncorepmu->cpu))); > } > static DEVICE_ATTR_RO(cpumask); > > diff --git a/drivers/perf/hisilicon/hisi_pcie_pmu.c b/drivers/perf/hisilicon/hisi_pcie_pmu.c > index c5394d007b61..0f55d871c67e 100644 > --- a/drivers/perf/hisilicon/hisi_pcie_pmu.c > +++ b/drivers/perf/hisilicon/hisi_pcie_pmu.c > @@ -121,7 +121,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, c > { > struct hisi_pcie_pmu *pcie_pmu = to_pcie_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pcie_pmu->on_cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pcie_pmu->on_cpu))); > } > static DEVICE_ATTR_RO(cpumask); > > diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.c b/drivers/perf/hisilicon/hisi_uncore_pmu.c > index de71dcf11653..0ff2fdf4b3e2 100644 > --- a/drivers/perf/hisilicon/hisi_uncore_pmu.c > +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.c > @@ -56,7 +56,7 @@ static ssize_t hisi_associated_cpus_sysfs_show(struct device *dev, > { > struct hisi_pmu *hisi_pmu = to_hisi_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, &hisi_pmu->associated_cpus); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&hisi_pmu->associated_cpus)); > } > static DEVICE_ATTR(associated_cpus, 0444, hisi_associated_cpus_sysfs_show, NULL); > > diff --git a/drivers/perf/marvell_cn10k_ddr_pmu.c b/drivers/perf/marvell_cn10k_ddr_pmu.c > index 72ac17efd846..8681e8715cb3 100644 > --- a/drivers/perf/marvell_cn10k_ddr_pmu.c > +++ b/drivers/perf/marvell_cn10k_ddr_pmu.c > @@ -364,7 +364,7 @@ static ssize_t cn10k_ddr_perf_cpumask_show(struct device *dev, > { > struct cn10k_ddr_pmu *pmu = dev_get_drvdata(dev); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); > } > > static struct device_attribute cn10k_ddr_perf_cpumask_attr = > diff --git a/drivers/perf/marvell_cn10k_tad_pmu.c b/drivers/perf/marvell_cn10k_tad_pmu.c > index 51ccb0befa05..54909d0031b7 100644 > --- a/drivers/perf/marvell_cn10k_tad_pmu.c > +++ b/drivers/perf/marvell_cn10k_tad_pmu.c > @@ -258,7 +258,7 @@ static ssize_t tad_pmu_cpumask_show(struct device *dev, > { > struct tad_pmu *tad_pmu = to_tad_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(tad_pmu->cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(tad_pmu->cpu))); > } > > static DEVICE_ATTR(cpumask, 0444, tad_pmu_cpumask_show, NULL); > diff --git a/drivers/perf/marvell_pem_pmu.c b/drivers/perf/marvell_pem_pmu.c > index 29fbcd1848e4..cf1d8cdb1318 100644 > --- a/drivers/perf/marvell_pem_pmu.c > +++ b/drivers/perf/marvell_pem_pmu.c > @@ -164,7 +164,7 @@ static ssize_t pem_perf_cpumask_show(struct device *dev, > { > struct pem_pmu *pmu = dev_get_drvdata(dev); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); > } > > static struct device_attribute pem_perf_cpumask_attr = > diff --git a/drivers/perf/nvidia_t410_c2c_pmu.c b/drivers/perf/nvidia_t410_c2c_pmu.c > index 411987153ff3..bff875f4f625 100644 > --- a/drivers/perf/nvidia_t410_c2c_pmu.c > +++ b/drivers/perf/nvidia_t410_c2c_pmu.c > @@ -658,7 +658,7 @@ static ssize_t nv_c2c_pmu_cpumask_show(struct device *dev, > default: > return 0; > } > - return cpumap_print_to_pagebuf(true, buf, cpumask); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); > } > > #define NV_C2C_PMU_CPUMASK_ATTR(_name, _config) \ > diff --git a/drivers/perf/nvidia_t410_cmem_latency_pmu.c b/drivers/perf/nvidia_t410_cmem_latency_pmu.c > index acb8f5571522..6c8e41598ec1 100644 > --- a/drivers/perf/nvidia_t410_cmem_latency_pmu.c > +++ b/drivers/perf/nvidia_t410_cmem_latency_pmu.c > @@ -501,7 +501,7 @@ static ssize_t cmem_lat_pmu_cpumask_show(struct device *dev, > default: > return 0; > } > - return cpumap_print_to_pagebuf(true, buf, cpumask); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); > } > > #define NV_PMU_CPUMASK_ATTR(_name, _config) \ > diff --git a/drivers/perf/qcom_l2_pmu.c b/drivers/perf/qcom_l2_pmu.c > index ea8c85729937..c0c522b10b72 100644 > --- a/drivers/perf/qcom_l2_pmu.c > +++ b/drivers/perf/qcom_l2_pmu.c > @@ -638,7 +638,7 @@ static ssize_t l2_cache_pmu_cpumask_show(struct device *dev, > { > struct l2cache_pmu *l2cache_pmu = to_l2cache_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, &l2cache_pmu->cpumask); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&l2cache_pmu->cpumask)); > } > > static struct device_attribute l2_cache_pmu_cpumask_attr = > diff --git a/drivers/perf/qcom_l3_pmu.c b/drivers/perf/qcom_l3_pmu.c > index 66e6cabd6fff..c8d259dd1f80 100644 > --- a/drivers/perf/qcom_l3_pmu.c > +++ b/drivers/perf/qcom_l3_pmu.c > @@ -663,7 +663,7 @@ static ssize_t cpumask_show(struct device *dev, > { > struct l3cache_pmu *l3pmu = to_l3cache_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, &l3pmu->cpumask); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&l3pmu->cpumask)); > } > > static DEVICE_ATTR_RO(cpumask); > diff --git a/drivers/perf/starfive_starlink_pmu.c b/drivers/perf/starfive_starlink_pmu.c > index 964897c2baa9..222a0a34e211 100644 > --- a/drivers/perf/starfive_starlink_pmu.c > +++ b/drivers/perf/starfive_starlink_pmu.c > @@ -131,7 +131,7 @@ cpumask_show(struct device *dev, struct device_attribute *attr, char *buf) > { > struct starlink_pmu *starlink_pmu = to_starlink_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, &starlink_pmu->cpumask); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&starlink_pmu->cpumask)); > } > > static DEVICE_ATTR_RO(cpumask); > diff --git a/drivers/perf/thunderx2_pmu.c b/drivers/perf/thunderx2_pmu.c > index 6ed4707bd6bb..a69c02d2d874 100644 > --- a/drivers/perf/thunderx2_pmu.c > +++ b/drivers/perf/thunderx2_pmu.c > @@ -254,7 +254,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, > struct tx2_uncore_pmu *tx2_pmu; > > tx2_pmu = pmu_to_tx2_pmu(dev_get_drvdata(dev)); > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(tx2_pmu->cpu)); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(tx2_pmu->cpu))); > } > static DEVICE_ATTR_RO(cpumask); > > diff --git a/drivers/perf/xgene_pmu.c b/drivers/perf/xgene_pmu.c > index 33b5497bdc06..e9e4871db08d 100644 > --- a/drivers/perf/xgene_pmu.c > +++ b/drivers/perf/xgene_pmu.c > @@ -595,7 +595,7 @@ static ssize_t cpumask_show(struct device *dev, > { > struct xgene_pmu_dev *pmu_dev = to_pmu_dev(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, &pmu_dev->parent->cpu); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&pmu_dev->parent->cpu)); > } > > static DEVICE_ATTR_RO(cpumask); > diff --git a/kernel/events/core.c b/kernel/events/core.c > index 7935d5663944..61689d348abd 100644 > --- a/kernel/events/core.c > +++ b/kernel/events/core.c > @@ -12657,7 +12657,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, > struct cpumask *mask = perf_scope_cpumask(pmu->scope); > > if (mask) > - return cpumap_print_to_pagebuf(true, buf, mask); > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(mask)); > return 0; > } > From david.laight.linux at gmail.com Fri May 29 05:06:19 2026 From: david.laight.linux at gmail.com (David Laight) Date: Fri, 29 May 2026 13:06:19 +0100 Subject: [PATCH 13/16] perf: Use sysfs_emit() for cpumask show callbacks In-Reply-To: <7e980b99-1e4e-408b-8ebd-4d28116e7ad5@arm.com> References: <20260528183625.870813-1-ynorov@nvidia.com> <20260528183625.870813-14-ynorov@nvidia.com> <7e980b99-1e4e-408b-8ebd-4d28116e7ad5@arm.com> Message-ID: <20260529130619.12f24264@pumpkin> On Fri, 29 May 2026 12:05:08 +0100 Robin Murphy wrote: > On 2026-05-28 7:36 pm, Yury Norov wrote: > > These callbacks are sysfs show paths. > > > > Use sysfs_emit() and cpumask_pr_args() to emit the masks. > > > > This prepares for removing cpumap_print_to_pagebuf(). > > TBH, looking at this diff I think it only shows the value of having a > helper to abstract the boilerplate... > > I'm not sure I agree with the argument of removing something entirely > just because it may occasionally be misused, but could we at least have > something like: > > #define sysfs_emit_cpumask(buf, mask) \ > sysfs_emit((buf), "%*pbl\n", cpumask_pr_args(mask)) > > to save the mess in all the many places where the current > cpumap_print_to_pagebuf() usage _is_ entirely appropriate? That has the advantage of letting you change how it is done (again) without having to find all the callers. -- David > > Thansk, > Robin. > > > Signed-off-by: Yury Norov > > --- > > drivers/perf/alibaba_uncore_drw_pmu.c | 2 +- > > drivers/perf/amlogic/meson_ddr_pmu_core.c | 2 +- > > drivers/perf/arm-cci.c | 2 +- > > drivers/perf/arm-ccn.c | 2 +- > > drivers/perf/arm-cmn.c | 2 +- > > drivers/perf/arm-ni.c | 2 +- > > drivers/perf/arm_cspmu/arm_cspmu.c | 2 +- > > drivers/perf/arm_dmc620_pmu.c | 4 ++-- > > drivers/perf/arm_dsu_pmu.c | 2 +- > > drivers/perf/arm_pmu.c | 2 +- > > drivers/perf/arm_smmuv3_pmu.c | 2 +- > > drivers/perf/arm_spe_pmu.c | 2 +- > > drivers/perf/cxl_pmu.c | 2 +- > > drivers/perf/dwc_pcie_pmu.c | 2 +- > > drivers/perf/fsl_imx8_ddr_perf.c | 2 +- > > drivers/perf/fsl_imx9_ddr_perf.c | 2 +- > > drivers/perf/fujitsu_uncore_pmu.c | 2 +- > > drivers/perf/hisilicon/hisi_pcie_pmu.c | 2 +- > > drivers/perf/hisilicon/hisi_uncore_pmu.c | 2 +- > > drivers/perf/marvell_cn10k_ddr_pmu.c | 2 +- > > drivers/perf/marvell_cn10k_tad_pmu.c | 2 +- > > drivers/perf/marvell_pem_pmu.c | 2 +- > > drivers/perf/nvidia_t410_c2c_pmu.c | 2 +- > > drivers/perf/nvidia_t410_cmem_latency_pmu.c | 2 +- > > drivers/perf/qcom_l2_pmu.c | 2 +- > > drivers/perf/qcom_l3_pmu.c | 2 +- > > drivers/perf/starfive_starlink_pmu.c | 2 +- > > drivers/perf/thunderx2_pmu.c | 2 +- > > drivers/perf/xgene_pmu.c | 2 +- > > kernel/events/core.c | 2 +- > > 30 files changed, 31 insertions(+), 31 deletions(-) > > > > diff --git a/drivers/perf/alibaba_uncore_drw_pmu.c b/drivers/perf/alibaba_uncore_drw_pmu.c > > index ac49d3b2dad6..74786a5dd6a2 100644 > > --- a/drivers/perf/alibaba_uncore_drw_pmu.c > > +++ b/drivers/perf/alibaba_uncore_drw_pmu.c > > @@ -221,7 +221,7 @@ static ssize_t ali_drw_pmu_cpumask_show(struct device *dev, > > { > > struct ali_drw_pmu *drw_pmu = to_ali_drw_pmu(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(drw_pmu->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(drw_pmu->cpu))); > > } > > > > static struct device_attribute ali_drw_pmu_cpumask_attr = > > diff --git a/drivers/perf/amlogic/meson_ddr_pmu_core.c b/drivers/perf/amlogic/meson_ddr_pmu_core.c > > index c1e755c356a3..f614aa3434a5 100644 > > --- a/drivers/perf/amlogic/meson_ddr_pmu_core.c > > +++ b/drivers/perf/amlogic/meson_ddr_pmu_core.c > > @@ -191,7 +191,7 @@ static ssize_t meson_ddr_perf_cpumask_show(struct device *dev, > > { > > struct ddr_pmu *pmu = dev_get_drvdata(dev); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); > > } > > > > static struct device_attribute meson_ddr_perf_cpumask_attr = > > diff --git a/drivers/perf/arm-cci.c b/drivers/perf/arm-cci.c > > index 1cc3214d6b6d..f0ef0a679e74 100644 > > --- a/drivers/perf/arm-cci.c > > +++ b/drivers/perf/arm-cci.c > > @@ -1351,7 +1351,7 @@ static ssize_t pmu_cpumask_attr_show(struct device *dev, > > struct pmu *pmu = dev_get_drvdata(dev); > > struct cci_pmu *cci_pmu = to_cci_pmu(pmu); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(cci_pmu->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(cci_pmu->cpu))); > > } > > > > static struct device_attribute pmu_cpumask_attr = > > diff --git a/drivers/perf/arm-ccn.c b/drivers/perf/arm-ccn.c > > index 8af3563fdf60..d5dcb4280434 100644 > > --- a/drivers/perf/arm-ccn.c > > +++ b/drivers/perf/arm-ccn.c > > @@ -538,7 +538,7 @@ static ssize_t arm_ccn_pmu_cpumask_show(struct device *dev, > > { > > struct arm_ccn *ccn = pmu_to_arm_ccn(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(ccn->dt.cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(ccn->dt.cpu))); > > } > > > > static struct device_attribute arm_ccn_pmu_cpumask_attr = > > diff --git a/drivers/perf/arm-cmn.c b/drivers/perf/arm-cmn.c > > index f5305c8fdca4..2187ba763b72 100644 > > --- a/drivers/perf/arm-cmn.c > > +++ b/drivers/perf/arm-cmn.c > > @@ -1326,7 +1326,7 @@ static ssize_t arm_cmn_cpumask_show(struct device *dev, > > { > > struct arm_cmn *cmn = to_cmn(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(cmn->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(cmn->cpu))); > > } > > > > static struct device_attribute arm_cmn_cpumask_attr = > > diff --git a/drivers/perf/arm-ni.c b/drivers/perf/arm-ni.c > > index 66858c65215d..03a1c6bf9223 100644 > > --- a/drivers/perf/arm-ni.c > > +++ b/drivers/perf/arm-ni.c > > @@ -239,7 +239,7 @@ static ssize_t arm_ni_cpumask_show(struct device *dev, > > { > > struct arm_ni *ni = cd_to_ni(pmu_to_cd(dev_get_drvdata(dev))); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(ni->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(ni->cpu))); > > } > > > > static struct device_attribute arm_ni_cpumask_attr = > > diff --git a/drivers/perf/arm_cspmu/arm_cspmu.c b/drivers/perf/arm_cspmu/arm_cspmu.c > > index 80fb314d5135..e6292021f653 100644 > > --- a/drivers/perf/arm_cspmu/arm_cspmu.c > > +++ b/drivers/perf/arm_cspmu/arm_cspmu.c > > @@ -305,7 +305,7 @@ static ssize_t arm_cspmu_cpumask_show(struct device *dev, > > default: > > return 0; > > } > > - return cpumap_print_to_pagebuf(true, buf, cpumask); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); > > } > > > > static struct attribute *arm_cspmu_cpumask_attrs[] = { > > diff --git a/drivers/perf/arm_dmc620_pmu.c b/drivers/perf/arm_dmc620_pmu.c > > index 4f6b196160f8..467147a05eec 100644 > > --- a/drivers/perf/arm_dmc620_pmu.c > > +++ b/drivers/perf/arm_dmc620_pmu.c > > @@ -237,8 +237,8 @@ static ssize_t dmc620_pmu_cpumask_show(struct device *dev, > > { > > struct dmc620_pmu *dmc620_pmu = to_dmc620_pmu(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, > > - cpumask_of(dmc620_pmu->irq->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", > > + cpumask_pr_args(cpumask_of(dmc620_pmu->irq->cpu))); > > } > > > > static struct device_attribute dmc620_pmu_cpumask_attr = > > diff --git a/drivers/perf/arm_dsu_pmu.c b/drivers/perf/arm_dsu_pmu.c > > index 32b0dd7c693b..bcbd19e075a5 100644 > > --- a/drivers/perf/arm_dsu_pmu.c > > +++ b/drivers/perf/arm_dsu_pmu.c > > @@ -157,7 +157,7 @@ static ssize_t dsu_pmu_cpumask_show(struct device *dev, > > default: > > return 0; > > } > > - return cpumap_print_to_pagebuf(true, buf, cpumask); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); > > } > > > > static struct attribute *dsu_pmu_format_attrs[] = { > > diff --git a/drivers/perf/arm_pmu.c b/drivers/perf/arm_pmu.c > > index 939bcbd433aa..51ab6cc52ca0 100644 > > --- a/drivers/perf/arm_pmu.c > > +++ b/drivers/perf/arm_pmu.c > > @@ -570,7 +570,7 @@ static ssize_t cpus_show(struct device *dev, > > struct device_attribute *attr, char *buf) > > { > > struct arm_pmu *armpmu = to_arm_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, &armpmu->supported_cpus); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&armpmu->supported_cpus)); > > } > > > > static DEVICE_ATTR_RO(cpus); > > diff --git a/drivers/perf/arm_smmuv3_pmu.c b/drivers/perf/arm_smmuv3_pmu.c > > index 621f02a7f43b..8ce34e6bb82b 100644 > > --- a/drivers/perf/arm_smmuv3_pmu.c > > +++ b/drivers/perf/arm_smmuv3_pmu.c > > @@ -537,7 +537,7 @@ static ssize_t smmu_pmu_cpumask_show(struct device *dev, > > { > > struct smmu_pmu *smmu_pmu = to_smmu_pmu(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(smmu_pmu->on_cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(smmu_pmu->on_cpu))); > > } > > > > static struct device_attribute smmu_pmu_cpumask_attr = > > diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c > > index dbd0da111639..9f786fd48cdd 100644 > > --- a/drivers/perf/arm_spe_pmu.c > > +++ b/drivers/perf/arm_spe_pmu.c > > @@ -343,7 +343,7 @@ static ssize_t cpumask_show(struct device *dev, > > { > > struct arm_spe_pmu *spe_pmu = dev_get_drvdata(dev); > > > > - return cpumap_print_to_pagebuf(true, buf, &spe_pmu->supported_cpus); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&spe_pmu->supported_cpus)); > > } > > static DEVICE_ATTR_RO(cpumask); > > > > diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c > > index 68a54d97d2a8..0735eb33f5f3 100644 > > --- a/drivers/perf/cxl_pmu.c > > +++ b/drivers/perf/cxl_pmu.c > > @@ -493,7 +493,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, > > { > > struct cxl_pmu_info *info = dev_get_drvdata(dev); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(info->on_cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(info->on_cpu))); > > } > > static DEVICE_ATTR_RO(cpumask); > > > > diff --git a/drivers/perf/dwc_pcie_pmu.c b/drivers/perf/dwc_pcie_pmu.c > > index 5385401fa9cf..291e776d6f6a 100644 > > --- a/drivers/perf/dwc_pcie_pmu.c > > +++ b/drivers/perf/dwc_pcie_pmu.c > > @@ -117,7 +117,7 @@ static ssize_t cpumask_show(struct device *dev, > > { > > struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pcie_pmu->on_cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pcie_pmu->on_cpu))); > > } > > static DEVICE_ATTR_RO(cpumask); > > > > diff --git a/drivers/perf/fsl_imx8_ddr_perf.c b/drivers/perf/fsl_imx8_ddr_perf.c > > index bcdf5575d71c..3760ebe02674 100644 > > --- a/drivers/perf/fsl_imx8_ddr_perf.c > > +++ b/drivers/perf/fsl_imx8_ddr_perf.c > > @@ -237,7 +237,7 @@ static ssize_t ddr_perf_cpumask_show(struct device *dev, > > { > > struct ddr_pmu *pmu = dev_get_drvdata(dev); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); > > } > > > > static struct device_attribute ddr_perf_cpumask_attr = > > diff --git a/drivers/perf/fsl_imx9_ddr_perf.c b/drivers/perf/fsl_imx9_ddr_perf.c > > index 7050b48c0467..6fee5eb5087a 100644 > > --- a/drivers/perf/fsl_imx9_ddr_perf.c > > +++ b/drivers/perf/fsl_imx9_ddr_perf.c > > @@ -159,7 +159,7 @@ static ssize_t ddr_perf_cpumask_show(struct device *dev, > > { > > struct ddr_pmu *pmu = dev_get_drvdata(dev); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); > > } > > > > static struct device_attribute ddr_perf_cpumask_attr = > > diff --git a/drivers/perf/fujitsu_uncore_pmu.c b/drivers/perf/fujitsu_uncore_pmu.c > > index c3c6f56474ad..a07877632d53 100644 > > --- a/drivers/perf/fujitsu_uncore_pmu.c > > +++ b/drivers/perf/fujitsu_uncore_pmu.c > > @@ -374,7 +374,7 @@ static ssize_t cpumask_show(struct device *dev, > > { > > struct uncore_pmu *uncorepmu = to_uncore_pmu(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(uncorepmu->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(uncorepmu->cpu))); > > } > > static DEVICE_ATTR_RO(cpumask); > > > > diff --git a/drivers/perf/hisilicon/hisi_pcie_pmu.c b/drivers/perf/hisilicon/hisi_pcie_pmu.c > > index c5394d007b61..0f55d871c67e 100644 > > --- a/drivers/perf/hisilicon/hisi_pcie_pmu.c > > +++ b/drivers/perf/hisilicon/hisi_pcie_pmu.c > > @@ -121,7 +121,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, c > > { > > struct hisi_pcie_pmu *pcie_pmu = to_pcie_pmu(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pcie_pmu->on_cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pcie_pmu->on_cpu))); > > } > > static DEVICE_ATTR_RO(cpumask); > > > > diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.c b/drivers/perf/hisilicon/hisi_uncore_pmu.c > > index de71dcf11653..0ff2fdf4b3e2 100644 > > --- a/drivers/perf/hisilicon/hisi_uncore_pmu.c > > +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.c > > @@ -56,7 +56,7 @@ static ssize_t hisi_associated_cpus_sysfs_show(struct device *dev, > > { > > struct hisi_pmu *hisi_pmu = to_hisi_pmu(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, &hisi_pmu->associated_cpus); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&hisi_pmu->associated_cpus)); > > } > > static DEVICE_ATTR(associated_cpus, 0444, hisi_associated_cpus_sysfs_show, NULL); > > > > diff --git a/drivers/perf/marvell_cn10k_ddr_pmu.c b/drivers/perf/marvell_cn10k_ddr_pmu.c > > index 72ac17efd846..8681e8715cb3 100644 > > --- a/drivers/perf/marvell_cn10k_ddr_pmu.c > > +++ b/drivers/perf/marvell_cn10k_ddr_pmu.c > > @@ -364,7 +364,7 @@ static ssize_t cn10k_ddr_perf_cpumask_show(struct device *dev, > > { > > struct cn10k_ddr_pmu *pmu = dev_get_drvdata(dev); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); > > } > > > > static struct device_attribute cn10k_ddr_perf_cpumask_attr = > > diff --git a/drivers/perf/marvell_cn10k_tad_pmu.c b/drivers/perf/marvell_cn10k_tad_pmu.c > > index 51ccb0befa05..54909d0031b7 100644 > > --- a/drivers/perf/marvell_cn10k_tad_pmu.c > > +++ b/drivers/perf/marvell_cn10k_tad_pmu.c > > @@ -258,7 +258,7 @@ static ssize_t tad_pmu_cpumask_show(struct device *dev, > > { > > struct tad_pmu *tad_pmu = to_tad_pmu(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(tad_pmu->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(tad_pmu->cpu))); > > } > > > > static DEVICE_ATTR(cpumask, 0444, tad_pmu_cpumask_show, NULL); > > diff --git a/drivers/perf/marvell_pem_pmu.c b/drivers/perf/marvell_pem_pmu.c > > index 29fbcd1848e4..cf1d8cdb1318 100644 > > --- a/drivers/perf/marvell_pem_pmu.c > > +++ b/drivers/perf/marvell_pem_pmu.c > > @@ -164,7 +164,7 @@ static ssize_t pem_perf_cpumask_show(struct device *dev, > > { > > struct pem_pmu *pmu = dev_get_drvdata(dev); > > > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(pmu->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(pmu->cpu))); > > } > > > > static struct device_attribute pem_perf_cpumask_attr = > > diff --git a/drivers/perf/nvidia_t410_c2c_pmu.c b/drivers/perf/nvidia_t410_c2c_pmu.c > > index 411987153ff3..bff875f4f625 100644 > > --- a/drivers/perf/nvidia_t410_c2c_pmu.c > > +++ b/drivers/perf/nvidia_t410_c2c_pmu.c > > @@ -658,7 +658,7 @@ static ssize_t nv_c2c_pmu_cpumask_show(struct device *dev, > > default: > > return 0; > > } > > - return cpumap_print_to_pagebuf(true, buf, cpumask); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); > > } > > > > #define NV_C2C_PMU_CPUMASK_ATTR(_name, _config) \ > > diff --git a/drivers/perf/nvidia_t410_cmem_latency_pmu.c b/drivers/perf/nvidia_t410_cmem_latency_pmu.c > > index acb8f5571522..6c8e41598ec1 100644 > > --- a/drivers/perf/nvidia_t410_cmem_latency_pmu.c > > +++ b/drivers/perf/nvidia_t410_cmem_latency_pmu.c > > @@ -501,7 +501,7 @@ static ssize_t cmem_lat_pmu_cpumask_show(struct device *dev, > > default: > > return 0; > > } > > - return cpumap_print_to_pagebuf(true, buf, cpumask); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask)); > > } > > > > #define NV_PMU_CPUMASK_ATTR(_name, _config) \ > > diff --git a/drivers/perf/qcom_l2_pmu.c b/drivers/perf/qcom_l2_pmu.c > > index ea8c85729937..c0c522b10b72 100644 > > --- a/drivers/perf/qcom_l2_pmu.c > > +++ b/drivers/perf/qcom_l2_pmu.c > > @@ -638,7 +638,7 @@ static ssize_t l2_cache_pmu_cpumask_show(struct device *dev, > > { > > struct l2cache_pmu *l2cache_pmu = to_l2cache_pmu(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, &l2cache_pmu->cpumask); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&l2cache_pmu->cpumask)); > > } > > > > static struct device_attribute l2_cache_pmu_cpumask_attr = > > diff --git a/drivers/perf/qcom_l3_pmu.c b/drivers/perf/qcom_l3_pmu.c > > index 66e6cabd6fff..c8d259dd1f80 100644 > > --- a/drivers/perf/qcom_l3_pmu.c > > +++ b/drivers/perf/qcom_l3_pmu.c > > @@ -663,7 +663,7 @@ static ssize_t cpumask_show(struct device *dev, > > { > > struct l3cache_pmu *l3pmu = to_l3cache_pmu(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, &l3pmu->cpumask); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&l3pmu->cpumask)); > > } > > > > static DEVICE_ATTR_RO(cpumask); > > diff --git a/drivers/perf/starfive_starlink_pmu.c b/drivers/perf/starfive_starlink_pmu.c > > index 964897c2baa9..222a0a34e211 100644 > > --- a/drivers/perf/starfive_starlink_pmu.c > > +++ b/drivers/perf/starfive_starlink_pmu.c > > @@ -131,7 +131,7 @@ cpumask_show(struct device *dev, struct device_attribute *attr, char *buf) > > { > > struct starlink_pmu *starlink_pmu = to_starlink_pmu(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, &starlink_pmu->cpumask); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&starlink_pmu->cpumask)); > > } > > > > static DEVICE_ATTR_RO(cpumask); > > diff --git a/drivers/perf/thunderx2_pmu.c b/drivers/perf/thunderx2_pmu.c > > index 6ed4707bd6bb..a69c02d2d874 100644 > > --- a/drivers/perf/thunderx2_pmu.c > > +++ b/drivers/perf/thunderx2_pmu.c > > @@ -254,7 +254,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, > > struct tx2_uncore_pmu *tx2_pmu; > > > > tx2_pmu = pmu_to_tx2_pmu(dev_get_drvdata(dev)); > > - return cpumap_print_to_pagebuf(true, buf, cpumask_of(tx2_pmu->cpu)); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpumask_of(tx2_pmu->cpu))); > > } > > static DEVICE_ATTR_RO(cpumask); > > > > diff --git a/drivers/perf/xgene_pmu.c b/drivers/perf/xgene_pmu.c > > index 33b5497bdc06..e9e4871db08d 100644 > > --- a/drivers/perf/xgene_pmu.c > > +++ b/drivers/perf/xgene_pmu.c > > @@ -595,7 +595,7 @@ static ssize_t cpumask_show(struct device *dev, > > { > > struct xgene_pmu_dev *pmu_dev = to_pmu_dev(dev_get_drvdata(dev)); > > > > - return cpumap_print_to_pagebuf(true, buf, &pmu_dev->parent->cpu); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(&pmu_dev->parent->cpu)); > > } > > > > static DEVICE_ATTR_RO(cpumask); > > diff --git a/kernel/events/core.c b/kernel/events/core.c > > index 7935d5663944..61689d348abd 100644 > > --- a/kernel/events/core.c > > +++ b/kernel/events/core.c > > @@ -12657,7 +12657,7 @@ static ssize_t cpumask_show(struct device *dev, struct device_attribute *attr, > > struct cpumask *mask = perf_scope_cpumask(pmu->scope); > > > > if (mask) > > - return cpumap_print_to_pagebuf(true, buf, mask); > > + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(mask)); > > return 0; > > } > > > > From robin.murphy at arm.com Fri May 29 05:24:02 2026 From: robin.murphy at arm.com (Robin Murphy) Date: Fri, 29 May 2026 13:24:02 +0100 Subject: [PATCH 01/16] psci: simplify hotplug_tests() In-Reply-To: <20260528183625.870813-2-ynorov@nvidia.com> References: <20260528183625.870813-1-ynorov@nvidia.com> <20260528183625.870813-2-ynorov@nvidia.com> Message-ID: <3a4e22fe-b8ce-4e62-9139-113e0cd4f16b@arm.com> On 2026-05-28 7:36 pm, Yury Norov wrote: > Switch to pr_info("... %pbl"), and drop the temporary buffer allocation. I would say this is simply an improvement in its own right, regardless of whether cpumap_print_to_pagebuf() deserves to be removed or not. For the change itself, FWIW, Reviewed-by: Robin Murphy > This prepares for removing cpumap_print_to_pagebuf(). > > Signed-off-by: Yury Norov > --- > drivers/firmware/psci/psci_checker.c | 14 ++------------ > 1 file changed, 2 insertions(+), 12 deletions(-) > > diff --git a/drivers/firmware/psci/psci_checker.c b/drivers/firmware/psci/psci_checker.c > index e67ba9891082..ecd745bb90bf 100644 > --- a/drivers/firmware/psci/psci_checker.c > +++ b/drivers/firmware/psci/psci_checker.c > @@ -186,7 +186,6 @@ static int hotplug_tests(void) > { > int i, nb_cpu_group, err = -ENOMEM; > cpumask_var_t offlined_cpus, *cpu_groups; > - char *page_buf; > > if (!alloc_cpumask_var(&offlined_cpus, GFP_KERNEL)) > return err; > @@ -194,10 +193,6 @@ static int hotplug_tests(void) > nb_cpu_group = alloc_init_cpu_groups(&cpu_groups); > if (nb_cpu_group < 0) > goto out_free_cpus; > - page_buf = (char *)__get_free_page(GFP_KERNEL); > - if (!page_buf) > - goto out_free_cpu_groups; > - > /* > * Of course the last CPU cannot be powered down and cpu_down() should > * refuse doing that. > @@ -210,16 +205,11 @@ static int hotplug_tests(void) > * off, the cpu group itself should shut down. > */ > for (i = 0; i < nb_cpu_group; ++i) { > - ssize_t len = cpumap_print_to_pagebuf(true, page_buf, > - cpu_groups[i]); > - /* Remove trailing newline. */ > - page_buf[len - 1] = '\0'; > - pr_info("Trying to turn off and on again group %d (CPUs %s)\n", > - i, page_buf); > + pr_info("Trying to turn off and on again group %d (CPUs %*pbl)\n", > + i, cpumask_pr_args(cpu_groups[i])); > err += down_and_up_cpus(cpu_groups[i], offlined_cpus); > } > > - free_page((unsigned long)page_buf); > out_free_cpu_groups: > free_cpu_groups(nb_cpu_group, &cpu_groups); > out_free_cpus: From jbrunet at baylibre.com Fri May 29 06:51:45 2026 From: jbrunet at baylibre.com (Jerome Brunet) Date: Fri, 29 May 2026 15:51:45 +0200 Subject: [GIT PULL] clk: meson: Amlogic clock changes for v7.2 Message-ID: <1j1peu5nny.fsf@starbuckisacylon.baylibre.com> Hi Stephen, Here are the Amlogic clock changes for v7.2. There is just a couple of minor DT bindings fixups. Nothing major. Please Pull. Cheers Jerome The following changes since commit 254f49634ee16a731174d2ae34bc50bd5f45e731: Linux 7.1-rc1 (2026-04-26 14:19:00 -0700) are available in the Git repository at: https://github.com/BayLibre/clk-meson.git tags/clk-meson-v7.2-1 for you to fetch changes up to ca89c88bcf69daca829044c638a8163d5ce47af0: dt-bindings: clock: amlogic: t7: Add missing mpll3 parent clock (2026-04-28 10:42:47 +0200) ---------------------------------------------------------------- Amlogic clock changes for v7.2 * Minor DT bindings fixes for the T7 clock controllers ---------------------------------------------------------------- Jian Hu (2): dt-bindings: clock: amlogic: Fix redundant hyphen in "amlogic,t7-gp1--pll" string. dt-bindings: clock: amlogic: t7: Add missing mpll3 parent clock .../bindings/clock/amlogic,t7-peripherals-clkc.yaml | 12 ++++++++---- .../devicetree/bindings/clock/amlogic,t7-pll-clkc.yaml | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) From mripard at kernel.org Fri May 29 07:04:46 2026 From: mripard at kernel.org (Maxime Ripard) Date: Fri, 29 May 2026 16:04:46 +0200 Subject: [PATCH RESEND v3 1/6] drm/connector: report IRQ_HPD events to drm_connector_oob_hotplug_event() In-Reply-To: References: <20260513-hpd-irq-events-v3-0-086857017f16@oss.qualcomm.com> <20260513-hpd-irq-events-v3-1-086857017f16@oss.qualcomm.com> <20260521-funny-astonishing-mackerel-cc5a01@penduick> Message-ID: <20260529-screeching-rugged-shellfish-4dcde3@houat> On Thu, May 21, 2026 at 03:05:11PM +0300, Dmitry Baryshkov wrote: > On Thu, May 21, 2026 at 09:47:29AM +0200, Maxime Ripard wrote: > > On Wed, May 13, 2026 at 09:23:21PM +0300, Dmitry Baryshkov wrote: > > > The DisplayPort standard defines a special kind of events called IRQ. > > > These events are used to notify DP Source about the events on the Sink > > > side. It is extremely important for DP MST handling, where the MST > > > events are reported through this IRQ. > > > > > > In case of the USB-C DP AltMode there is no actual HPD pulse, but the > > > events are ported through the bits in the AltMode VDOs. > > > > > > Extend the drm_connector_oob_hotplug_event() interface and report IRQ > > > events to the DisplayPort Sink drivers. > > > > > > Signed-off-by: Dmitry Baryshkov > > > --- > > > drivers/gpu/drm/drm_connector.c | 5 ++++- > > > drivers/usb/typec/altmodes/displayport.c | 15 +++++++++++---- > > > include/drm/drm_connector.h | 19 ++++++++++++++++++- > > > 3 files changed, 33 insertions(+), 6 deletions(-) > > > > > > diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c > > > index 47dc53c4a738..edee9daccd51 100644 > > > --- a/drivers/gpu/drm/drm_connector.c > > > +++ b/drivers/gpu/drm/drm_connector.c > > > @@ -3510,6 +3510,8 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) > > > * drm_connector_oob_hotplug_event - Report out-of-band hotplug event to connector > > > * @connector_fwnode: fwnode_handle to report the event on > > > * @status: hot plug detect logical state > > > + * @extra_status: additional information provided by the sink without changing > > > + * the HPD state (or in addition to such a change). > > > * > > > * On some hardware a hotplug event notification may come from outside the display > > > * driver / device. An example of this is some USB Type-C setups where the hardware > > > @@ -3520,7 +3522,8 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) > > > * a drm_connector reference through calling drm_connector_find_by_fwnode(). > > > */ > > > void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode, > > > - enum drm_connector_status status) > > > + enum drm_connector_status status, > > > + enum drm_connector_status_extra extra_status) > > > { > > > struct drm_connector *connector; > > > > > > diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c > > > index 35d9c3086990..7182a8e2e710 100644 > > > --- a/drivers/usb/typec/altmodes/displayport.c > > > +++ b/drivers/usb/typec/altmodes/displayport.c > > > @@ -189,7 +189,9 @@ static int dp_altmode_status_update(struct dp_altmode *dp) > > > } else { > > > drm_connector_oob_hotplug_event(dp->connector_fwnode, > > > hpd ? connector_status_connected : > > > - connector_status_disconnected); > > > + connector_status_disconnected, > > > + (hpd && irq_hpd) ? DRM_CONNECTOR_DP_IRQ_HPD : > > > + DRM_CONNECTOR_NO_EXTRA_STATUS); > > > > Since the extra status itself, and what the options mean, are DP specific, do we really want to > > extend drm_connector_oob_hotplug_event()? I think I'd prefer to have a DP specific variant, with its > > own set of parameters. > > I can try arguing that drm_connector_oob_hotplug_event() is DP-specific, > there are no other users for it, only the DP AltMode driver. > > Anyway, do you just mean new API here or new API and a new connector > callback? If drm_connector_oob_hotplug_event is truly only used for DP, then I don't mind keeping it as is but we should make it more obvious and document it, both in the function documentation, but also by having a better name for the extra status. drm_connector_dp_oob_status maybe? Maxime -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 273 bytes Desc: not available URL: From jerrysteve1101 at gmail.com Fri May 29 07:05:56 2026 From: jerrysteve1101 at gmail.com (Jun Yan) Date: Fri, 29 May 2026 22:05:56 +0800 Subject: [PATCH 0/3] arm64: dts: amlogic: meson-axg: NAND fix and PCIe PHY adjustment Message-ID: <20260529140605.1070764-1-jerrysteve1101@gmail.com> - Fix NAND pinctrl by adding missing nand_rb0 pin. - Disable unused pcie_phy to suppress probe warning. - Re-enable pcie_phy on S400 board to keep PCIe working. All changes have been tested on real hardware. Jun Yan (3): arm64: dts: amlogic: meson-axg: Add missing nand_rb0 pin to nand_all_pins arm64: dts: amlogic: meson-axg: Disable pcie_phy node by default arm64: dts: amlogic: meson-axg-s400: Enable pcie_phy arch/arm64/boot/dts/amlogic/meson-axg-s400.dts | 4 ++++ arch/arm64/boot/dts/amlogic/meson-axg.dtsi | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) -- 2.54.0 From jerrysteve1101 at gmail.com Fri May 29 07:05:57 2026 From: jerrysteve1101 at gmail.com (Jun Yan) Date: Fri, 29 May 2026 22:05:57 +0800 Subject: [PATCH 1/3] arm64: dts: amlogic: meson-axg: Add missing nand_rb0 pin to nand_all_pins In-Reply-To: <20260529140605.1070764-1-jerrysteve1101@gmail.com> References: <20260529140605.1070764-1-jerrysteve1101@gmail.com> Message-ID: <20260529140605.1070764-2-jerrysteve1101@gmail.com> The nand_all_pins pinctrl node was missing the nand_rb0 (ready/busy) pin description, which is required for NAND controller operation. Add it to the pinmux list. Fixes: be18d53c32b2 ("arm64: dts: amlogic: meson-axg: pinctrl node for NAND") Signed-off-by: Jun Yan --- arch/arm64/boot/dts/amlogic/meson-axg.dtsi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi index f1f53fd98ae2..b7a7f4fae7dc 100644 --- a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi +++ b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi @@ -481,7 +481,8 @@ mux { "nand_ale", "nand_cle", "nand_wen_clk", - "nand_ren_wr"; + "nand_ren_wr", + "nand_rb0"; function = "nand"; input-enable; bias-pull-up; -- 2.54.0 From jerrysteve1101 at gmail.com Fri May 29 07:05:58 2026 From: jerrysteve1101 at gmail.com (Jun Yan) Date: Fri, 29 May 2026 22:05:58 +0800 Subject: [PATCH 2/3] arm64: dts: amlogic: meson-axg: Disable pcie_phy node by default In-Reply-To: <20260529140605.1070764-1-jerrysteve1101@gmail.com> References: <20260529140605.1070764-1-jerrysteve1101@gmail.com> Message-ID: <20260529140605.1070764-3-jerrysteve1101@gmail.com> Set the pcie_phy node to "disabled" as it is not used on some boards and should be enabled per-board when necessary. This change suppresses the deferred probe warning: platform ff644000.phy: deferred probe pending: (reason unknown) Signed-off-by: Jun Yan --- arch/arm64/boot/dts/amlogic/meson-axg.dtsi | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi index b7a7f4fae7dc..41a75e3d286a 100644 --- a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi +++ b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi @@ -328,6 +328,7 @@ pcie_phy: phy at ff644000 { phys = <&mipi_pcie_analog_dphy>; phy-names = "analog"; #phy-cells = <0>; + status = "disabled"; }; pdm: audio-controller at ff632000 { -- 2.54.0 From jerrysteve1101 at gmail.com Fri May 29 07:05:59 2026 From: jerrysteve1101 at gmail.com (Jun Yan) Date: Fri, 29 May 2026 22:05:59 +0800 Subject: [PATCH 3/3] arm64: dts: amlogic: meson-axg-s400: Enable pcie_phy In-Reply-To: <20260529140605.1070764-1-jerrysteve1101@gmail.com> References: <20260529140605.1070764-1-jerrysteve1101@gmail.com> Message-ID: <20260529140605.1070764-4-jerrysteve1101@gmail.com> The meson-axg dtsi now disables pcie_phy by default, so enable it for the s400 board to support PCIe functionality. Signed-off-by: Jun Yan --- arch/arm64/boot/dts/amlogic/meson-axg-s400.dts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/amlogic/meson-axg-s400.dts b/arch/arm64/boot/dts/amlogic/meson-axg-s400.dts index 285c6ac1dd61..7ba249cc3d56 100644 --- a/arch/arm64/boot/dts/amlogic/meson-axg-s400.dts +++ b/arch/arm64/boot/dts/amlogic/meson-axg-s400.dts @@ -448,6 +448,10 @@ &pcieB { status = "okay"; }; +&pcie_phy { + status = "okay"; +}; + &pwm_ab { status = "okay"; pinctrl-0 = <&pwm_a_x20_pins>; -- 2.54.0 From sashiko-bot at kernel.org Fri May 29 07:27:47 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Fri, 29 May 2026 14:27:47 +0000 Subject: [PATCH 1/3] arm64: dts: amlogic: meson-axg: Add missing nand_rb0 pin to nand_all_pins In-Reply-To: <20260529140605.1070764-2-jerrysteve1101@gmail.com> References: <20260529140605.1070764-2-jerrysteve1101@gmail.com> Message-ID: <20260529142747.6184A1F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] eMMC boot failure due to BOOT_13 pinctrl conflict. Fix: Add status = "disabled" to the nfc node in meson-axg.dtsi. -- commit c218ae59948e8bab6c68fcfdc539ef678ec3bdba Author: Jun Yan arm64: dts: amlogic: meson-axg: Add missing nand_rb0 pin to nand_all_pins This commit updates the nand_all_pins pinctrl node to include the missing nand_rb0 pin for the meson-axg platform. This ensures the ready/busy pin is properly configured for NAND controller operation. > diff --git a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi > index f1f53fd98ae25..b7a7f4fae7dc2 100644 > --- a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi > +++ b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi [ ... ] > @@ -481,7 +481,8 @@ mux { > "nand_ale", > "nand_cle", > "nand_wen_clk", > - "nand_ren_wr"; > + "nand_ren_wr", > + "nand_rb0"; [Severity: High] This isn't a bug introduced by this patch, but adding this pin exposes a pre-existing issue where the nfc node unconditionally claims nand_all_pins. Since nand_rb0 and emmc_ds share the BOOT_13 pin, does this force a strict pinctrl collision between the NAND and eMMC nodes? If this collision causes the eMMC storage probe to fail and breaks the boot process, should we add status = "disabled" to the nfc node in meson-axg.dtsi to prevent this conflict when NAND is not actively used? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260529140605.1070764-1-jerrysteve1101 at gmail.com?part=1 From sashiko-bot at kernel.org Fri May 29 08:13:42 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Fri, 29 May 2026 15:13:42 +0000 Subject: [PATCH 3/3] arm64: dts: amlogic: meson-axg-s400: Enable pcie_phy In-Reply-To: <20260529140605.1070764-4-jerrysteve1101@gmail.com> References: <20260529140605.1070764-4-jerrysteve1101@gmail.com> Message-ID: <20260529151342.E13DA1F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] The commit enables `&pcie_phy` to support PCIe on the `meson-axg-s400` board, but fails to enable its required analog PHY dependency `&mipi_pcie_analog_dphy`, leaving PCIe completely non-functional. -- commit 655f261362c29dc56c1723f3469b32281d8fffb8 Author: Jun Yan arm64: dts: amlogic: meson-axg-s400: Enable pcie_phy This commit enables pcie_phy on the meson-axg-s400 board to support PCIe functionality, as it is now disabled by default in the meson-axg dtsi. > diff --git a/arch/arm64/boot/dts/amlogic/meson-axg-s400.dts b/arch/arm64/boot/dts/amlogic/meson-axg-s400.dts > index 285c6ac1dd613..7ba249cc3d569 100644 > --- a/arch/arm64/boot/dts/amlogic/meson-axg-s400.dts > +++ b/arch/arm64/boot/dts/amlogic/meson-axg-s400.dts > @@ -448,6 +448,10 @@ &pcieB { > status = "okay"; > }; > > +&pcie_phy { > + status = "okay"; > +}; [Severity: High] Does this node also need to enable the mipi_pcie_analog_dphy dependency? In meson-axg.dtsi, the pcie_phy node depends on mipi_pcie_analog_dphy via the property phys = <&mipi_pcie_analog_dphy>. Because the analog PHY is disabled by default, the core driver model does not instantiate its platform device and no PHY provider is registered. This causes devm_phy_get(dev, "analog") to return -EPROBE_DEFER during the probe of pcie_phy, which in turn causes the PCIe controllers (pcieA and pcieB) to infinitely defer probing as well. > + > &pwm_ab { > status = "okay"; > pinctrl-0 = <&pwm_a_x20_pins>; -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260529140605.1070764-1-jerrysteve1101 at gmail.com?part=3 From lukasz.luba at arm.com Fri May 29 08:17:33 2026 From: lukasz.luba at arm.com (Lukasz Luba) Date: Fri, 29 May 2026 16:17:33 +0100 Subject: [PATCH v4 06/10] thermal/of: Rename the devm_thermal_of_cooling_device_register() function In-Reply-To: <20260526140802.1059293-18-daniel.lezcano@oss.qualcomm.com> References: <20260526140802.1059293-12-daniel.lezcano@oss.qualcomm.com> <20260526140802.1059293-18-daniel.lezcano@oss.qualcomm.com> Message-ID: On 5/26/26 15:08, Daniel Lezcano wrote: > To clarify that the function operates on child nodes, rename: > > devm_thermal_of_cooling_device_register() > | > v > devm_thermal_of_child_cooling_device_register() > > Used the command: > > find . -type f -name '*.[ch]' -exec \ > sed -i 's/devm_thermal_of_cooling_device_register/\ > devm_thermal_of_child_cooling_device_register/g' {} \; > > Did not used clang-format-diff because it does not indent correctly > and checkpatch complained. Manually reindented to make checkpatch > happy > > This prepares for upcoming support of cooling devices identified by > an ID rather than device tree child nodes. > > No functional change. > > Signed-off-by: Daniel Lezcano > --- > drivers/hwmon/amc6821.c | 2 +- > drivers/hwmon/aspeed-pwm-tacho.c | 5 +++-- > drivers/hwmon/emc2305.c | 6 +++--- > drivers/hwmon/gpio-fan.c | 6 ++++-- > drivers/hwmon/max6650.c | 6 +++--- > drivers/hwmon/npcm750-pwm-fan.c | 6 ++++-- > drivers/hwmon/pwm-fan.c | 5 +++-- > drivers/hwmon/qnap-mcu-hwmon.c | 6 +++--- > drivers/hwmon/tc654.c | 5 +++-- > drivers/memory/tegra/tegra210-emc-core.c | 4 ++-- > drivers/soc/qcom/qcom_aoss.c | 2 +- > drivers/thermal/khadas_mcu_fan.c | 7 ++++--- > drivers/thermal/tegra/soctherm.c | 6 +++--- > drivers/thermal/thermal_of.c | 15 +++++++++------ > include/linux/thermal.h | 16 ++++++++-------- > 15 files changed, 54 insertions(+), 43 deletions(-) > Reviewed-by: Lukasz Luba From linusw at kernel.org Fri May 29 13:30:58 2026 From: linusw at kernel.org (Linus Walleij) Date: Fri, 29 May 2026 22:30:58 +0200 Subject: [PATCH v2 0/2] pinctrl: add support amlogic a9 In-Reply-To: <36cdfa84-1e83-4a44-a529-653d476778fd@amlogic.com> References: <20260507-a9-pinctrl-v2-0-49774feff2ef@amlogic.com> <36cdfa84-1e83-4a44-a529-653d476778fd@amlogic.com> Message-ID: On Fri, May 29, 2026 at 5:02?AM Xianwei Zhao wrote: > Hi Linus, > I don't see these patches in the for-next branch yet. Were they > intentionally left out? Hm I wonder what happened. I probably thought I applied it and didn't. I applied it now! Yours, Linus Walleij From devnull+linux-kernel-dev.aliel.fr at kernel.org Sat May 30 00:05:57 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Sat, 30 May 2026 09:05:57 +0200 Subject: [PATCH] firmware: meson: sm: add stub functions when CONFIG_MESON_SM is disabled Message-ID: <20260530-fix-missing-meson_sm-symbol-v1-1-3fb672b989d4@aliel.fr> From: Ronald Claveau After merging the thermal tree, linux-next build (arm_multi_v7 defconfig) failed like this: arm-linux-gnueabihf-ld: drivers/thermal/amlogic_thermal.o: in function `amlogic_thermal_probe_sm': /tmp/next/build/drivers/thermal/amlogic_thermal.c:196:(.text+0x2f4): undefined reference to `meson_sm_get' arm-linux-gnueabihf-ld: /tmp/next/build/drivers/thermal/amlogic_thermal.c:205:(.text+0x320): undefined reference to `meson_sm_get_thermal_calib' Add inline stub implementations of meson_sm_get() and meson_sm_get_thermal_calib() behind an #else guard so that drivers including this header can be compiled without CONFIG_MESON_SM . Fixes: b21d88de6918 ("thermal/drivers/amlogic: Add support for secure monitor calibration readout") Reported-by: Mark Brown Signed-off-by: Ronald Claveau --- include/linux/firmware/meson/meson_sm.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/firmware/meson/meson_sm.h index 3ebc2bd9a9760..813166ccedd1d 100644 --- a/include/linux/firmware/meson/meson_sm.h +++ b/include/linux/firmware/meson/meson_sm.h @@ -27,8 +27,25 @@ int meson_sm_call_write(struct meson_sm_firmware *fw, void *buffer, int meson_sm_call_read(struct meson_sm_firmware *fw, void *buffer, unsigned int bsize, unsigned int cmd_index, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4); + +#ifdef CONFIG_MESON_SM + struct meson_sm_firmware *meson_sm_get(struct device_node *firmware_node); int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, u32 *trim_info, u32 tsensor_id); +#else + +static inline struct meson_sm_firmware *meson_sm_get(struct device_node *firmware_node) +{ + return NULL; +} +static inline int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, + u32 *trim_info, u32 tsensor_id) +{ + return -EINVAL; +} + +#endif + #endif /* _MESON_SM_FW_H_ */ --- base-commit: 3929405c64376a8a54c794e8a4485023b108a97e change-id: 20260529-fix-missing-meson_sm-symbol-7776d0d9d760 Best regards, -- Ronald Claveau From daniel.lezcano at oss.qualcomm.com Sat May 30 00:15:18 2026 From: daniel.lezcano at oss.qualcomm.com (Daniel Lezcano) Date: Sat, 30 May 2026 09:15:18 +0200 Subject: [PATCH] firmware: meson: sm: add stub functions when CONFIG_MESON_SM is disabled In-Reply-To: <20260530-fix-missing-meson_sm-symbol-v1-1-3fb672b989d4@aliel.fr> References: <20260530-fix-missing-meson_sm-symbol-v1-1-3fb672b989d4@aliel.fr> Message-ID: <946af0af-6c4f-4e3f-9e8c-408b2c2b2a11@oss.qualcomm.com> On 5/30/26 09:05, Ronald Claveau via B4 Relay wrote: > From: Ronald Claveau > > After merging the thermal tree, linux-next build (arm_multi_v7 > defconfig) failed like this: > > arm-linux-gnueabihf-ld: drivers/thermal/amlogic_thermal.o: in function `amlogic_thermal_probe_sm': > /tmp/next/build/drivers/thermal/amlogic_thermal.c:196:(.text+0x2f4): undefined reference to `meson_sm_get' > arm-linux-gnueabihf-ld: /tmp/next/build/drivers/thermal/amlogic_thermal.c:205:(.text+0x320): undefined reference to `meson_sm_get_thermal_calib' > > Add inline stub implementations of meson_sm_get() and > meson_sm_get_thermal_calib() behind an #else guard so that drivers > including this header can be compiled without CONFIG_MESON_SM . > > Fixes: b21d88de6918 ("thermal/drivers/amlogic: Add support for secure monitor calibration readout") > Reported-by: Mark Brown > Signed-off-by: Ronald Claveau > --- Also added: | Reported-by: kernel test robot | Closes: https://lore.kernel.org/oe-kbuild-all/202605291530.en7aGn7w-lkp at intel.com/ and applied, thanks ! From linux.amoon at gmail.com Sat May 30 02:42:46 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Sat, 30 May 2026 15:12:46 +0530 Subject: [PATCH v6 0/8] media: meson: Fix memory leak in error path in vdec Message-ID: <20260530094326.11892-1-linux.amoon@gmail.com> v6: Changes The previous approach had some technical issues, so this new version takes a slightly different approach, I have fixed the DMA warnings found during basic testing. I have donse basic testing on the Odroid N2+ and found that the clocks are not enabling for decoder. It also seems some Mali GPU configurations are still missing. You can reproduce the test case using: mpv --hwdec=v4l2m2m Big_Buck_Bunny_1080_10s_30MB.mp4 Please let me know your feedback so we can discuss and address these points! Thanks -Anand V5: Changes [v5] https://lore.kernel.org/all/20260525095216.12078-2-linux.amoon at gmail.com/ Following chamges try to fix the memory leak reported by Sashiko New issues: - [High] The newly added error path in `vdec_start_streaming()` leaks `sess->priv` when `kthread_run()` fails. Pre-existing issues: - [Critical] Race condition between hardware power-on and `core->cur_sess` initialization leads to a NULL pointer dereference in the IRQ handler. - [High] Returning buffers for both source and destination queues upon single-queue failure orphans active queue buffers. - [High] Concurrent sessions can bypass the hardware exclusivity check, leading to simultaneous hardware programming. -- V4: Changes: v4: https://lore.kernel.org/all/20260521073449.10057-2-linux.amoon at gmail.com/ Following chamges try to fix the memory leak reported by Sashiko Pre-existing issues: - [Critical] The `sess->esparser_queue_work` work item is not canceled before freeing the session context, leading to a potential Use-After-Free vulnerability. - [High] The patch attempts to fix a memory leak reported by kmemleak, but misdiagnoses the root cause and leaves the primary memory leak (the V4L2 control handler) unresolved. - [High] The driver does not verify if `kthread_run()` returns an `ERR_PTR`, leading to a kernel panic when `kthread_stop()` is called. Thanks -Anand Anand Moon (8): media: meson: vdec: Fix memory leaks and lifetime of m2m device media: meson: vdec: Fix concurrent STREAMON / STREAMOFF race conditions media: meson: vdec: Handle kthread failure and free codec state media: meson: vdec: Condition buffer flushing on queue type in start_streaming media: meson: vdec: Cancel esparser work during teardown media: meson: vdec: Configure DMA mask and segment size in probe media: meson: vdec: Fix NULL pointer dereference in ISR handlers gpu: drm: meson: Fix DMA max segment size for DMABUF imports drivers/gpu/drm/meson/meson_drv.c | 2 + drivers/staging/media/meson/vdec/vdec.c | 179 +++++++++++++++++------- drivers/staging/media/meson/vdec/vdec.h | 4 +- 3 files changed, 136 insertions(+), 49 deletions(-) base-commit: f5e5d3509bffb95c6648eb9795f7f236852ae62d -- 2.50.1 From linux.amoon at gmail.com Sat May 30 02:42:47 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Sat, 30 May 2026 15:12:47 +0530 Subject: [PATCH v6 1/8] media: meson: vdec: Fix memory leaks and lifetime of m2m device In-Reply-To: <20260530094326.11892-1-linux.amoon@gmail.com> References: <20260530094326.11892-1-linux.amoon@gmail.com> Message-ID: <20260530094326.11892-2-linux.amoon@gmail.com> The driver was initializing the v4l2 m2m device instance per-session within vdec_open() and releasing it inside vdec_close(). This approach is faulty because the m2m device represents the hardware context and should persist across multiple open sessions. Fix this design flaw by shifting v4l2_m2m_init() to vdec_probe() and v4l2_m2m_release() to vdec_remove(). Correspondingly, move the m2m_dev pointer from struct amvdec_session to struct amvdec_core. Additionally, this patch addresses two critical resource leaks: 1. Adds a missing v4l2_ctrl_handler_free() in vdec_close() to clean up allocated control handlers upon session closure. 2. Introduces proper unwinding logic via a new 'err_fh_del' label in vdec_open() to ensure that file handles (v4l2_fh) are fully deregistered if subsequent session resource allocations fail. This was identified via kmemleak: unreferenced object 0xffff0000205d6878 (size 8): comm "v4l_id", pid 5289, jiffies 4294938580 hex dump (first 8 bytes): 40 d2 49 18 00 00 ff ff @.I..... backtrace (crc d3204599): kmemleak_alloc+0xc8/0xf0 __kvmalloc_node_noprof+0x60c/0x850 v4l2_ctrl_handler_init_class+0x1b4/0x2e8 [videodev] vdec_open+0x1f4/0x788 [meson_vdec] v4l2_open+0x144/0x460 [videodev] chrdev_open+0x1ac/0x500 do_dentry_open+0x3f0/0xfe8 vfs_open+0x68/0x320 do_open+0x2d8/0x9a8 path_openat+0x1d0/0x4f0 do_filp_open+0x190/0x380 do_sys_openat2+0xf8/0x1b0 __arm64_sys_openat+0x13c/0x1e8 invoke_syscall+0xdc/0x268 el0_svc_common.constprop.0+0x178/0x258 do_el0_svc+0x4c/0x70 Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- drivers/staging/media/meson/vdec/vdec.c | 33 ++++++++++++++----------- drivers/staging/media/meson/vdec/vdec.h | 4 +-- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 4b77ec1af5a7..4ffebba2341d 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -153,7 +153,7 @@ static void vdec_m2m_job_abort(void *priv) { struct amvdec_session *sess = priv; - v4l2_m2m_job_finish(sess->m2m_dev, sess->m2m_ctx); + v4l2_m2m_job_finish(sess->core->m2m_dev, sess->m2m_ctx); } static const struct v4l2_m2m_ops vdec_m2m_ops = { @@ -873,23 +873,16 @@ static int vdec_open(struct file *file) sess->core = core; - sess->m2m_dev = v4l2_m2m_init(&vdec_m2m_ops); - if (IS_ERR(sess->m2m_dev)) { - dev_err(dev, "Fail to v4l2_m2m_init\n"); - ret = PTR_ERR(sess->m2m_dev); - goto err_free_sess; - } - - sess->m2m_ctx = v4l2_m2m_ctx_init(sess->m2m_dev, sess, m2m_queue_init); + sess->m2m_ctx = v4l2_m2m_ctx_init(core->m2m_dev, sess, m2m_queue_init); if (IS_ERR(sess->m2m_ctx)) { dev_err(dev, "Fail to v4l2_m2m_ctx_init\n"); ret = PTR_ERR(sess->m2m_ctx); - goto err_m2m_release; + goto err_fh_del; } ret = vdec_init_ctrls(sess); if (ret) - goto err_m2m_release; + goto err_free_sess; sess->pixfmt_cap = formats[0].pixfmts_cap[0]; sess->fmt_out = &formats[0]; @@ -913,8 +906,8 @@ static int vdec_open(struct file *file) return 0; -err_m2m_release: - v4l2_m2m_release(sess->m2m_dev); +err_fh_del: + v4l2_fh_exit(&sess->fh); err_free_sess: kfree(sess); return ret; @@ -925,9 +918,9 @@ static int vdec_close(struct file *file) struct amvdec_session *sess = file_to_amvdec_session(file); v4l2_m2m_ctx_release(sess->m2m_ctx); - v4l2_m2m_release(sess->m2m_dev); v4l2_fh_del(&sess->fh, file); v4l2_fh_exit(&sess->fh); + v4l2_ctrl_handler_free(&sess->ctrl_handler); mutex_destroy(&sess->lock); mutex_destroy(&sess->bufs_recycle_lock); @@ -1057,10 +1050,17 @@ static int vdec_probe(struct platform_device *pdev) if (ret) return ret; + core->m2m_dev = v4l2_m2m_init(&vdec_m2m_ops); + if (IS_ERR(core->m2m_dev)) { + dev_err(dev, "Failed to initialize v4l2 m2m device\n"); + return PTR_ERR(core->m2m_dev); + } + ret = v4l2_device_register(dev, &core->v4l2_dev); if (ret) { dev_err(dev, "Couldn't register v4l2 device\n"); - return -ENOMEM; + ret = -ENOMEM; + goto err_m2m_release; } vdev = video_device_alloc(); @@ -1095,6 +1095,8 @@ static int vdec_probe(struct platform_device *pdev) err_vdev_release: video_device_release(vdev); v4l2_device_unregister(&core->v4l2_dev); +err_m2m_release: + v4l2_m2m_release(core->m2m_dev); return ret; } @@ -1104,6 +1106,7 @@ static void vdec_remove(struct platform_device *pdev) video_unregister_device(core->vdev_dec); v4l2_device_unregister(&core->v4l2_dev); + v4l2_m2m_release(core->m2m_dev); } static struct platform_driver meson_vdec_driver = { diff --git a/drivers/staging/media/meson/vdec/vdec.h b/drivers/staging/media/meson/vdec/vdec.h index 7a5d8e871d70..cc0cfafb8a95 100644 --- a/drivers/staging/media/meson/vdec/vdec.h +++ b/drivers/staging/media/meson/vdec/vdec.h @@ -63,6 +63,7 @@ struct amvdec_session; * @vdec_hevcf_clk: VDEC_HEVCF clock * @esparser_reset: RESET for the PARSER * @vdev_dec: video device for the decoder + * @m2m_dev: v4l2 m2m device * @v4l2_dev: v4l2 device * @cur_sess: current decoding session * @lock: video device lock @@ -87,6 +88,7 @@ struct amvdec_core { struct reset_control *esparser_reset; struct video_device *vdev_dec; + struct v4l2_m2m_dev *m2m_dev; struct v4l2_device v4l2_dev; struct amvdec_session *cur_sess; @@ -183,7 +185,6 @@ enum amvdec_status { * * @core: reference to the vdec core struct * @fh: v4l2 file handle - * @m2m_dev: v4l2 m2m device * @m2m_ctx: v4l2 m2m context * @ctrl_handler: V4L2 control handler * @ctrl_min_buf_capture: V4L2 control V4L2_CID_MIN_BUFFERS_FOR_CAPTURE @@ -230,7 +231,6 @@ struct amvdec_session { struct amvdec_core *core; struct v4l2_fh fh; - struct v4l2_m2m_dev *m2m_dev; struct v4l2_m2m_ctx *m2m_ctx; struct v4l2_ctrl_handler ctrl_handler; struct v4l2_ctrl *ctrl_min_buf_capture; -- 2.50.1 From linux.amoon at gmail.com Sat May 30 02:42:48 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Sat, 30 May 2026 15:12:48 +0530 Subject: [PATCH v6 2/8] media: meson: vdec: Fix concurrent STREAMON / STREAMOFF race conditions In-Reply-To: <20260530094326.11892-1-linux.amoon@gmail.com> References: <20260530094326.11892-1-linux.amoon@gmail.com> Message-ID: <20260530094326.11892-3-linux.amoon@gmail.com> The Meson VDEC driver?s start/stop streaming paths previously updated core->cur_sess and sess->status without synchronization, leaving a race window between concurrent STREAMON/STREAMOFF calls. Following change introduces proper locking discipline: - Hold core->lock when checking or updating core->cur_sess and sess->status in vdec_start_streaming(). - Snapshot sess->status under the lock in vdec_stop_streaming() to safely evaluate hardware state after releasing the mutex. - Ensure error unwind paths clear core->cur_sess and reset sess->status inside the lock. This prevents TOCTOU races, avoids data corruption when multiple sessions contend for the hardware, and ensures consistent session lifecycle management. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260525104345.C8D501F00A3C at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- drivers/staging/media/meson/vdec/vdec.c | 62 ++++++++++++++++++------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 4ffebba2341d..7233000e2232 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -286,11 +286,6 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) struct vb2_v4l2_buffer *buf; int ret; - if (core->cur_sess && core->cur_sess != sess) { - ret = -EBUSY; - goto bufs_done; - } - if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) sess->streamon_out = 1; else @@ -308,9 +303,29 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) } if (sess->status == STATUS_RUNNING || - sess->status == STATUS_NEEDS_RESUME || - sess->status == STATUS_INIT) + sess->status == STATUS_NEEDS_RESUME) + return 0; + + /* + * Secure the core hardware lock before checking availability + * and updating session states to prevent STREAMON race conditions. + */ + mutex_lock(&core->lock); + if (core->cur_sess && core->cur_sess != sess) { + mutex_unlock(&core->lock); + ret = -EBUSY; + goto bufs_done; + } + + /* If already half-initialized, do not re-initialize */ + if (sess->status == STATUS_INIT) { + mutex_unlock(&core->lock); return 0; + } + + sess->status = STATUS_INIT; + core->cur_sess = sess; + mutex_unlock(&core->lock); sess->vififo_size = SIZE_VIFIFO; sess->vififo_vaddr = @@ -341,8 +356,6 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) sess->recycle_thread = kthread_run(vdec_recycle_thread, sess, "vdec_recycle"); - sess->status = STATUS_INIT; - core->cur_sess = sess; schedule_work(&sess->esparser_queue_work); return 0; @@ -350,6 +363,12 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) dma_free_coherent(sess->core->dev, sess->vififo_size, sess->vififo_vaddr, sess->vififo_paddr); bufs_done: + mutex_lock(&core->lock); + if (core->cur_sess == sess) + core->cur_sess = NULL; + sess->status = STATUS_STOPPED; + mutex_unlock(&core->lock); + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) @@ -399,10 +418,23 @@ static void vdec_stop_streaming(struct vb2_queue *q) struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; struct amvdec_core *core = sess->core; struct vb2_v4l2_buffer *buf; + enum amvdec_status old_status; - if (sess->status == STATUS_RUNNING || - sess->status == STATUS_INIT || - (sess->status == STATUS_NEEDS_RESUME && + /* + * Safely snapshot the status and clear the hardware owner inside + * the mutex to prevent data races with concurrent STREAMON requests. + */ + mutex_lock(&core->lock); + old_status = sess->status; + if (core->cur_sess == sess) + core->cur_sess = NULL; + sess->status = STATUS_STOPPED; + mutex_unlock(&core->lock); + + /* Evaluate the hardware state using our snapshot */ + if (old_status == STATUS_RUNNING || + old_status == STATUS_INIT || + (old_status == STATUS_NEEDS_RESUME && (!sess->streamon_out || !sess->streamon_cap))) { if (vdec_codec_needs_recycle(sess)) kthread_stop(sess->recycle_thread); @@ -415,8 +447,6 @@ static void vdec_stop_streaming(struct vb2_queue *q) vdec_reset_bufs_recycle(sess); kfree(sess->priv); sess->priv = NULL; - core->cur_sess = NULL; - sess->status = STATUS_STOPPED; } if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { @@ -425,8 +455,8 @@ static void vdec_stop_streaming(struct vb2_queue *q) sess->streamon_out = 0; } else { - /* Drain remaining refs if was still running */ - if (sess->status >= STATUS_RUNNING && codec_ops->drain) + /* Drain remaining refs if was still running using the snapshot */ + if (old_status >= STATUS_RUNNING && codec_ops->drain) codec_ops->drain(sess); while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) -- 2.50.1 From linux.amoon at gmail.com Sat May 30 02:42:49 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Sat, 30 May 2026 15:12:49 +0530 Subject: [PATCH v6 3/8] media: meson: vdec: Handle kthread failure and free codec state In-Reply-To: <20260530094326.11892-1-linux.amoon@gmail.com> References: <20260530094326.11892-1-linux.amoon@gmail.com> Message-ID: <20260530094326.11892-4-linux.amoon@gmail.com> vdec_start_streaming() launches a recycle thread when required by the codec. If kthread_run() fails, the previous error path only powered off the hardware, leaving sess->priv and codec state allocated. This caused a permanent leak of the codec context and associated DMA buffers. Fix this by introducing a dedicated err_cleanup path: - Call codec_ops->stop() to release the codec context and clear sess->priv. - Power off the hardware before freeing buffers to avoid DMA faults. - Free canvas IDs explicitly and set sess->vififo_vaddr = NULL after dma_free_coherent() to guard against double?free in fallback stop paths. - Reset core->cur_sess and sess->status to prevent stale references. Following change closes the memory leak on kthread_run() failure and ensures robust cleanup of codec resources in both error and stop paths. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- drivers/staging/media/meson/vdec/vdec.c | 26 ++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 7233000e2232..8a5bf1a96830 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -32,6 +32,8 @@ struct dummy_buf { /* 16 MiB for parsed bitstream swap exchange */ #define SIZE_VIFIFO SZ_16M +static void vdec_free_canvas(struct amvdec_session *sess); + static u32 get_output_size(u32 width, u32 height) { return ALIGN(width * height, SZ_64K); @@ -352,16 +354,31 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) sess->sequence_cap = 0; sess->sequence_out = 0; - if (vdec_codec_needs_recycle(sess)) + if (vdec_codec_needs_recycle(sess)) { sess->recycle_thread = kthread_run(vdec_recycle_thread, sess, "vdec_recycle"); + if (IS_ERR(sess->recycle_thread)) { + ret = PTR_ERR(sess->recycle_thread); + sess->recycle_thread = NULL; + goto err_cleanup; + } + } schedule_work(&sess->esparser_queue_work); return 0; +err_cleanup: + vdec_free_canvas(sess); + vdec_poweroff(sess); + if (codec_ops && codec_ops->stop && sess->priv) { + codec_ops->stop(sess); + kfree(sess->priv); + sess->priv = NULL; + } vififo_free: dma_free_coherent(sess->core->dev, sess->vififo_size, sess->vififo_vaddr, sess->vififo_paddr); + sess->vififo_vaddr = NULL; bufs_done: mutex_lock(&core->lock); if (core->cur_sess == sess) @@ -441,8 +458,11 @@ static void vdec_stop_streaming(struct vb2_queue *q) vdec_poweroff(sess); vdec_free_canvas(sess); - dma_free_coherent(sess->core->dev, sess->vififo_size, - sess->vififo_vaddr, sess->vififo_paddr); + if (sess->vififo_vaddr) { + dma_free_coherent(sess->core->dev, sess->vififo_size, + sess->vififo_vaddr, + sess->vififo_paddr); + } vdec_reset_timestamps(sess); vdec_reset_bufs_recycle(sess); kfree(sess->priv); -- 2.50.1 From linux.amoon at gmail.com Sat May 30 02:42:50 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Sat, 30 May 2026 15:12:50 +0530 Subject: [PATCH v6 4/8] media: meson: vdec: Condition buffer flushing on queue type in start_streaming In-Reply-To: <20260530094326.11892-1-linux.amoon@gmail.com> References: <20260530094326.11892-1-linux.amoon@gmail.com> Message-ID: <20260530094326.11892-5-linux.amoon@gmail.com> When vdec_start_streaming() fails, the error path clears buffers from both the source and destination queues unconditionally. If one queue was already streaming successfully from a prior invocation, flushing its buffers behind its back leaves videobuf2 deadlocked waiting for completions. Fix this by only sweeping buffers from the specific queue type container that failed to initialize. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- drivers/staging/media/meson/vdec/vdec.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 8a5bf1a96830..698a95566ad2 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -386,15 +386,15 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) sess->status = STATUS_STOPPED; mutex_unlock(&core->lock); - while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) - v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); - while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) - v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); - - if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { sess->streamon_out = 0; - else + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); + } else { sess->streamon_cap = 0; + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); + } return ret; } -- 2.50.1 From linux.amoon at gmail.com Sat May 30 02:42:51 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Sat, 30 May 2026 15:12:51 +0530 Subject: [PATCH v6 5/8] media: meson: vdec: Cancel esparser work during teardown In-Reply-To: <20260530094326.11892-1-linux.amoon@gmail.com> References: <20260530094326.11892-1-linux.amoon@gmail.com> Message-ID: <20260530094326.11892-6-linux.amoon@gmail.com> The esparser workqueue could remain active during error unwind, streaming stop, or device close, leading to use?after?free when work items accessed freed session memory. Fix this by explicitly cancelling the work in all teardown paths: - Call cancel_work_sync(&sess->esparser_queue_work) in vdec_start_streaming() error unwind, vdec_stop_streaming(), and vdec_close(). - Ensure the workqueue is drained before releasing session state and buffers. - Move codec_ops->drain() evaluation earlier in stop_streaming() using the status snapshot, so draining occurs before buffer cleanup. Following change prevents dangling work execution, eliminates use?after?free hazards, and ensures orderly teardown of decoder resources. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- drivers/staging/media/meson/vdec/vdec.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 698a95566ad2..4884ee04b352 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -380,6 +380,8 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) sess->vififo_vaddr, sess->vififo_paddr); sess->vififo_vaddr = NULL; bufs_done: + cancel_work_sync(&sess->esparser_queue_work); + mutex_lock(&core->lock); if (core->cur_sess == sess) core->cur_sess = NULL; @@ -437,6 +439,8 @@ static void vdec_stop_streaming(struct vb2_queue *q) struct vb2_v4l2_buffer *buf; enum amvdec_status old_status; + cancel_work_sync(&sess->esparser_queue_work); + /* * Safely snapshot the status and clear the hardware owner inside * the mutex to prevent data races with concurrent STREAMON requests. @@ -448,7 +452,11 @@ static void vdec_stop_streaming(struct vb2_queue *q) sess->status = STATUS_STOPPED; mutex_unlock(&core->lock); - /* Evaluate the hardware state using our snapshot */ + if (q->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (old_status >= STATUS_RUNNING && codec_ops->drain) + codec_ops->drain(sess); + } + if (old_status == STATUS_RUNNING || old_status == STATUS_INIT || (old_status == STATUS_NEEDS_RESUME && @@ -472,16 +480,10 @@ static void vdec_stop_streaming(struct vb2_queue *q) if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR); - sess->streamon_out = 0; } else { - /* Drain remaining refs if was still running using the snapshot */ - if (old_status >= STATUS_RUNNING && codec_ops->drain) - codec_ops->drain(sess); - while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR); - sess->streamon_cap = 0; } } @@ -967,6 +969,8 @@ static int vdec_close(struct file *file) { struct amvdec_session *sess = file_to_amvdec_session(file); + cancel_work_sync(&sess->esparser_queue_work); + v4l2_m2m_ctx_release(sess->m2m_ctx); v4l2_fh_del(&sess->fh, file); v4l2_fh_exit(&sess->fh); -- 2.50.1 From linux.amoon at gmail.com Sat May 30 02:42:52 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Sat, 30 May 2026 15:12:52 +0530 Subject: [PATCH v6 6/8] media: meson: vdec: Configure DMA mask and segment size in probe In-Reply-To: <20260530094326.11892-1-linux.amoon@gmail.com> References: <20260530094326.11892-1-linux.amoon@gmail.com> Message-ID: <20260530094326.11892-7-linux.amoon@gmail.com> The vdec probe routine did not set explicit DMA constraints, leaving the driver dependent on platform defaults. This could cause allocation failures or fragmented buffer handling on systems with stricter DMA limits. Fix this by: - Setting a 64 bit coherent DMA mask with dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)). - Configuring the maximum contiguous segment size to UINT_MAX via vb2_dma_contig_set_max_seg_size(). This aligns the driver with common DMA setup practices and guarantees large buffer allocations work reliably across platforms. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- drivers/staging/media/meson/vdec/vdec.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index 4884ee04b352..f99335effe17 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -1064,6 +1064,15 @@ static int vdec_probe(struct platform_device *pdev) if (IS_ERR(core->canvas)) return PTR_ERR(core->canvas); + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set DMA mask\n"); + + ret = vb2_dma_contig_set_max_seg_size(dev, UINT_MAX); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set DMA max segment size\n"); + of_id = of_match_node(vdec_dt_match, dev->of_node); core->platform = of_id->data; -- 2.50.1 From linux.amoon at gmail.com Sat May 30 02:42:53 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Sat, 30 May 2026 15:12:53 +0530 Subject: [PATCH v6 7/8] media: meson: vdec: Fix NULL pointer dereference in ISR handlers In-Reply-To: <20260530094326.11892-1-linux.amoon@gmail.com> References: <20260530094326.11892-1-linux.amoon@gmail.com> Message-ID: <20260530094326.11892-8-linux.amoon@gmail.com> The hard interrupt handler (vdec_isr) and the threaded interrupt handler (vdec_threaded_isr) directly read core->cur_sess without synchronization or validation. If a streaming teardown concurrently clears core->cur_sess to NULL while an interrupt is being processed, a NULL pointer dereference occurs when accessing the session fields or codec operations. Fix this race condition by using READ_ONCE() to obtain a stable, atomic snapshot of core->cur_sess. Check if the returned session pointer is NULL, and return IRQ_NONE immediately if the session has already been torn down. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") Signed-off-by: Anand Moon --- drivers/staging/media/meson/vdec/vdec.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c index f99335effe17..3897c75b19c8 100644 --- a/drivers/staging/media/meson/vdec/vdec.c +++ b/drivers/staging/media/meson/vdec/vdec.c @@ -996,17 +996,36 @@ static const struct v4l2_file_operations vdec_fops = { static irqreturn_t vdec_isr(int irq, void *data) { struct amvdec_core *core = data; - struct amvdec_session *sess = core->cur_sess; + struct amvdec_session *sess; + irqreturn_t ret = IRQ_HANDLED; + + /* + * Use READ_ONCE to secure an atomic snapshot of the pointer, + * protecting against concurrent clearing during streaming + * teardowns. + */ + sess = READ_ONCE(core->cur_sess); + if (!sess) + return IRQ_NONE; sess->last_irq_jiffies = get_jiffies_64(); + ret = sess->fmt_out->codec_ops->isr(sess); - return sess->fmt_out->codec_ops->isr(sess); + return ret; } static irqreturn_t vdec_threaded_isr(int irq, void *data) { struct amvdec_core *core = data; - struct amvdec_session *sess = core->cur_sess; + struct amvdec_session *sess; + + /* + * Prevent late-stage threaded interrupts from dereferencing a NULL + * session. + */ + sess = READ_ONCE(core->cur_sess); + if (!sess) + return IRQ_NONE; return sess->fmt_out->codec_ops->threaded_isr(sess); } -- 2.50.1 From linux.amoon at gmail.com Sat May 30 02:42:54 2026 From: linux.amoon at gmail.com (Anand Moon) Date: Sat, 30 May 2026 15:12:54 +0530 Subject: [PATCH v6 8/8] gpu: drm: meson: Fix DMA max segment size for DMABUF imports In-Reply-To: <20260530094326.11892-1-linux.amoon@gmail.com> References: <20260530094326.11892-1-linux.amoon@gmail.com> Message-ID: <20260530094326.11892-9-linux.amoon@gmail.com> When using hardware-accelerated video decoding via v4l2m2m-copy with mpv or similar players, the video decoder driver (`meson_vdec`) exports contiguous memory allocations as DMABUFs. When these buffers are subsequently imported by the display controller driver (`meson-drm`) for rendering via the GPU or compositor, the DMA API throws constraint validation warnings. - Call dma_set_max_seg_size(dev, UINT_MAX) to allow large scatter?gather segments. - Ensures the DRM core and canvas allocations can handle full sized buffers without hitting DMA?API warnings. This aligns the driver with common DMA setup practices and avoids failures on platforms with strict segment limits. Cc: Nicolas Dufresne Signed-off-by: Anand Moon --- drivers/gpu/drm/meson/meson_drv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c index 49ff9f1f16d3..8570add8b831 100644 --- a/drivers/gpu/drm/meson/meson_drv.c +++ b/drivers/gpu/drm/meson/meson_drv.c @@ -247,6 +247,8 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) goto free_drm; } + dma_set_max_seg_size(dev, UINT_MAX); + ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd1); if (ret) goto free_drm; -- 2.50.1 From sashiko-bot at kernel.org Sat May 30 02:55:43 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 30 May 2026 09:55:43 +0000 Subject: [PATCH v6 1/8] media: meson: vdec: Fix memory leaks and lifetime of m2m device In-Reply-To: <20260530094326.11892-2-linux.amoon@gmail.com> References: <20260530094326.11892-2-linux.amoon@gmail.com> Message-ID: <20260530095544.997391F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Use-After-Free of `m2m_dev` and `core` on driver unbind. - [Medium] Logically backwards error unwinding in `vdec_open()` introduces dead code and fails to fix a resource leak. -- commit 1e540207d92a3242e7aac789684a1ad3c201c64e Author: Anand Moon media: meson: vdec: Fix memory leaks and lifetime of m2m device This commit shifts the v4l2_m2m_init() call to vdec_probe() and releases it in vdec_remove() to address a design flaw where the m2m device was bound to the session. It also attempts to resolve resource leaks in the driver's error handling paths. Cc: Nicolas Dufresne Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260520045905.6ACBA1F000E9 at smtp.kernel.org/#t Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c [ ... ] > @@ -873,23 +873,16 @@ static int vdec_open(struct file *file) > > sess->core = core; > > - sess->m2m_dev = v4l2_m2m_init(&vdec_m2m_ops); > - if (IS_ERR(sess->m2m_dev)) { > - dev_err(dev, "Fail to v4l2_m2m_init\n"); > - ret = PTR_ERR(sess->m2m_dev); > - goto err_free_sess; > - } > - > - sess->m2m_ctx = v4l2_m2m_ctx_init(sess->m2m_dev, sess, m2m_queue_init); > + sess->m2m_ctx = v4l2_m2m_ctx_init(core->m2m_dev, sess, m2m_queue_init); > if (IS_ERR(sess->m2m_ctx)) { > dev_err(dev, "Fail to v4l2_m2m_ctx_init\n"); > ret = PTR_ERR(sess->m2m_ctx); > - goto err_m2m_release; > + goto err_fh_del; [Severity: Medium] Is this error unwinding logic in the correct order? Jumps to err_fh_del here will call v4l2_fh_exit() on sess->fh before it has been initialized further down in vdec_open(). > } > > ret = vdec_init_ctrls(sess); > if (ret) > - goto err_m2m_release; > + goto err_free_sess; [Severity: Medium] Does skipping err_fh_del here lead to a memory leak? If vdec_init_ctrls() fails, jumping directly to err_free_sess bypasses freeing the successfully allocated sess->m2m_ctx. [ ... ] > @@ -925,9 +918,9 @@ static int vdec_close(struct file *file) > struct amvdec_session *sess = file_to_amvdec_session(file); > > v4l2_m2m_ctx_release(sess->m2m_ctx); [Severity: High] Can this result in a use-after-free if the user holds the file descriptor open after the driver is unbound? During driver unbind, vdec_remove() explicitly frees core->m2m_dev and devres cleans up the amvdec_core structure. If a user subsequently closes an open file descriptor, vdec_close() calls v4l2_m2m_ctx_release(), which will dereference the freed m2m_dev pointer inside v4l2_m2m_cancel_job(). Additionally, any pending jobs may trigger vdec_m2m_job_abort(), which dereferences the freed sess->core and sess->core->m2m_dev. > - v4l2_m2m_release(sess->m2m_dev); > v4l2_fh_del(&sess->fh, file); > v4l2_fh_exit(&sess->fh); > + v4l2_ctrl_handler_free(&sess->ctrl_handler); > > mutex_destroy(&sess->lock); > mutex_destroy(&sess->bufs_recycle_lock); [ ... ] > @@ -1104,6 +1106,7 @@ static void vdec_remove(struct platform_device *pdev) > > video_unregister_device(core->vdev_dec); > v4l2_device_unregister(&core->v4l2_dev); > + v4l2_m2m_release(core->m2m_dev); > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260530094326.11892-1-linux.amoon at gmail.com?part=1 From sashiko-bot at kernel.org Sat May 30 03:08:41 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 30 May 2026 10:08:41 +0000 Subject: [PATCH v6 2/8] media: meson: vdec: Fix concurrent STREAMON / STREAMOFF race conditions In-Reply-To: <20260530094326.11892-3-linux.amoon@gmail.com> References: <20260530094326.11892-3-linux.amoon@gmail.com> Message-ID: <20260530100841.9CEBA1F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Premature clearing of `core->cur_sess` causes concurrent hardware accesses. - [High] Unconditionally setting `sess->status = STATUS_STOPPED` during `STREAMOFF` breaks dynamic resolution changes (DRC). -- commit bb4b11ce2f674461952a82e3ffa257bb828dacb8 Author: Anand Moon media: meson: vdec: Fix concurrent STREAMON / STREAMOFF race conditions The Meson VDEC driver?s start/stop streaming paths previously updated core->cur_sess and sess->status without synchronization, leaving a race window between concurrent STREAMON/STREAMOFF calls. This patch introduces proper locking discipline to prevent TOCTOU races and ensure consistent session lifecycle management. Closes: https://lore.kernel.org/all/20260525104345.C8D501F00A3C at smtp.kernel.org/ > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index 4ffebba2341d4..7233000e22323 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c [ ... ] > @@ -399,10 +418,23 @@ static void vdec_stop_streaming(struct vb2_queue *q) > struct amvdec_codec_ops *codec_ops = sess->fmt_out->codec_ops; > struct amvdec_core *core = sess->core; > struct vb2_v4l2_buffer *buf; > + enum amvdec_status old_status; > > - if (sess->status == STATUS_RUNNING || > - sess->status == STATUS_INIT || > - (sess->status == STATUS_NEEDS_RESUME && > + /* > + * Safely snapshot the status and clear the hardware owner inside > + * the mutex to prevent data races with concurrent STREAMON requests. > + */ > + mutex_lock(&core->lock); > + old_status = sess->status; > + if (core->cur_sess == sess) > + core->cur_sess = NULL; [Severity: High] Does prematurely clearing core->cur_sess here open a race window for concurrent hardware accesses? Since the lock is dropped immediately after this block, another session can call vdec_start_streaming(), acquire the lock, claim core->cur_sess, and call vdec_poweron(). Meanwhile, the old session continues executing vdec_stop_streaming() and reaches vdec_poweroff(sess), which calls vdec_wait_inactive(sess) and can sleep for up to 50ms while the hardware shuts down. Does this result in vdec_poweron() and vdec_poweroff() executing concurrently on the same hardware without synchronization? (Note: A later commit in this series, "media: meson: vdec: Fix NULL pointer dereference in ISR handlers", addresses an ISR dereference during this window, but it does not appear to resolve this underlying concurrent initialization and teardown race.) > + sess->status = STATUS_STOPPED; [Severity: High] Does unconditionally setting sess->status = STATUS_STOPPED break dynamic resolution changes (DRC)? During DRC, the hardware sets sess->status = STATUS_NEEDS_RESUME, and userspace responds by stopping the capture queue (STREAMOFF). If we overwrite sess->status to STATUS_STOPPED here, the subsequent STREAMON from userspace will check this in vdec_start_streaming(): if (sess->status == STATUS_NEEDS_RESUME && q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && sess->changed_format) { codec_ops->resume(sess); sess->status = STATUS_RUNNING; return 0; } Because the status is now STATUS_STOPPED, does this bypass the fast resume path and fall through to a full re-initialization? This appears to cause the reallocation of the 16MB vififo DMA buffer (leaking the original 16MB allocation which was never freed) and spawn a duplicate recycle_thread (leaking the running thread). > + mutex_unlock(&core->lock); > + > + /* Evaluate the hardware state using our snapshot */ > + if (old_status == STATUS_RUNNING || > + old_status == STATUS_INIT || > + (old_status == STATUS_NEEDS_RESUME && > (!sess->streamon_out || !sess->streamon_cap))) { > if (vdec_codec_needs_recycle(sess)) > kthread_stop(sess->recycle_thread); -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260530094326.11892-1-linux.amoon at gmail.com?part=2 From sashiko-bot at kernel.org Sat May 30 03:25:53 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 30 May 2026 10:25:53 +0000 Subject: [PATCH v6 3/8] media: meson: vdec: Handle kthread failure and free codec state In-Reply-To: <20260530094326.11892-4-linux.amoon@gmail.com> References: <20260530094326.11892-4-linux.amoon@gmail.com> Message-ID: <20260530102554.313521F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: New issues: - [Critical] Double-free of DMA buffers in the new err_cleanup path due to a redundant codec_ops->stop(sess) call. Pre-existing issues: - [Critical] Clearing the hardware ownership token `core->cur_sess` before executing `vdec_poweroff(sess)` creates a critical data race with concurrent sessions. - [High] The concurrent locking patch destroys the STATUS_NEEDS_RESUME state, leading to massive memory leaks during dynamic resolution changes. -- commit fd226e8b0810c7234d383bfb2d7ea8c103056049 Author: Anand Moon media: meson: vdec: Handle kthread failure and free codec state vdec_start_streaming() launches a recycle thread when required by the codec. If kthread_run() fails, the previous error path only powered off the hardware, leaving sess->priv and codec state allocated. This caused a permanent leak of the codec context and associated DMA buffers. Fix this by introducing a dedicated err_cleanup path: - Call codec_ops->stop() to release the codec context and clear sess->priv. - Power off the hardware before freeing buffers to avoid DMA faults. - Free canvas IDs explicitly and set sess->vififo_vaddr = NULL after dma_free_coherent() to guard against double?free in fallback stop paths. - Reset core->cur_sess and sess->status to prevent stale references. Following change closes the memory leak on kthread_run() failure and ensures robust cleanup of codec resources in both error and stop paths. Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ Fixes: 3e7f51bd9607 ("media: meson: add v4l2 m2m video decoder driver") > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index 7233000e22323..8a5bf1a96830c 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c [ ... ] > @@ -352,16 +354,31 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) > > sess->sequence_cap = 0; > sess->sequence_out = 0; > - if (vdec_codec_needs_recycle(sess)) > + if (vdec_codec_needs_recycle(sess)) { > sess->recycle_thread = kthread_run(vdec_recycle_thread, sess, > "vdec_recycle"); > + if (IS_ERR(sess->recycle_thread)) { > + ret = PTR_ERR(sess->recycle_thread); > + sess->recycle_thread = NULL; > + goto err_cleanup; > + } > + } > > schedule_work(&sess->esparser_queue_work); > return 0; > > +err_cleanup: > + vdec_free_canvas(sess); > + vdec_poweroff(sess); > + if (codec_ops && codec_ops->stop && sess->priv) { > + codec_ops->stop(sess); [Severity: Critical] Does this explicitly call codec_ops->stop() a second time, resulting in a double-free of the codec DMA memory? Looking at vdec_poweroff() called just above, it internally delegates to vdec_ops->stop(), which invokes codec_ops->stop(). Codec stop callbacks free their DMA buffers via dma_free_coherent() but do not set the pointers to NULL. If it is called again here, wouldn't it attempt to free them a second time? > + kfree(sess->priv); > + sess->priv = NULL; > + } > vififo_free: > dma_free_coherent(sess->core->dev, sess->vififo_size, > sess->vififo_vaddr, sess->vififo_paddr); > + sess->vififo_vaddr = NULL; > bufs_done: [ ... ] > @@ -441,8 +458,11 @@ static void vdec_stop_streaming(struct vb2_queue *q) > > vdec_poweroff(sess); [Severity: Critical] This is a pre-existing issue, but could clearing the hardware ownership token before executing vdec_poweroff() create a data race with concurrent sessions? Looking earlier in vdec_stop_streaming(), core->cur_sess = NULL is set inside the mutex, but vdec_poweroff(sess) is executed here after the mutex is dropped: mutex_lock(&core->lock); if (core->cur_sess == sess) core->cur_sess = NULL; mutex_unlock(&core->lock); ... vdec_poweroff(sess); This allows a concurrent vdec_start_streaming() from another context to acquire the lock, claim ownership, and call vdec_poweron(). Since vdec_poweron() and vdec_poweroff() would execute concurrently on different sessions, is it possible they will read and write to the exact same global hardware registers and corrupt the shared hardware state? > vdec_free_canvas(sess); > - dma_free_coherent(sess->core->dev, sess->vififo_size, > - sess->vififo_vaddr, sess->vififo_paddr); > + if (sess->vififo_vaddr) { > + dma_free_coherent(sess->core->dev, sess->vififo_size, > + sess->vififo_vaddr, > + sess->vififo_paddr); > + } > vdec_reset_timestamps(sess); > vdec_reset_bufs_recycle(sess); > kfree(sess->priv); [Severity: High] This isn't a bug introduced by this patch, but does the recent change to concurrent locking destroy the STATUS_NEEDS_RESUME state? Earlier in vdec_stop_streaming(), sess->status is unconditionally overwritten to STATUS_STOPPED inside the mutex lock: mutex_lock(&core->lock); old_status = sess->status; sess->status = STATUS_STOPPED; mutex_unlock(&core->lock); When the CAPTURE queue is stopped during a source resolution change, the hardware cleanup block is intentionally skipped because old_status is STATUS_NEEDS_RESUME. However, when vdec_start_streaming() is later called to resume the CAPTURE queue, it expects sess->status == STATUS_NEEDS_RESUME. Since it was forced to STATUS_STOPPED, will the resume check fail and cause the driver to incorrectly fall through to full initialization, executing dma_alloc_coherent() over the existing sess->vififo_vaddr and leaking memory? -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260530094326.11892-1-linux.amoon at gmail.com?part=3 From sashiko-bot at kernel.org Sat May 30 03:43:54 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 30 May 2026 10:43:54 +0000 Subject: [PATCH v6 4/8] media: meson: vdec: Condition buffer flushing on queue type in start_streaming In-Reply-To: <20260530094326.11892-5-linux.amoon@gmail.com> References: <20260530094326.11892-5-linux.amoon@gmail.com> Message-ID: <20260530104354.E32EB1F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: New issues: - [Critical] Double-free of codec DMA buffers in the error path of `vdec_start_streaming`. - [Critical] During a dynamic resolution change, STREAMOFF bypasses hardware cleanup and leads to a critical Use-After-Free upon session closure. Pre-existing issues: - [High] The `sess->priv` context and codec DMA memory are permanently leaked if `vdec_poweron(sess)` fails. -- commit 8f59207c5628689c8af221e3f815a3651ccc803f Author: Anand Moon media: meson: vdec: Condition buffer flushing on queue type in start_streaming When vdec_start_streaming() fails, the error path clears buffers from both the source and destination queues unconditionally. If one queue was already streaming successfully from a prior invocation, flushing its buffers behind its back leaves videobuf2 deadlocked waiting for completions. Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index 8a5bf1a96830c..698a95566ad27 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c > @@ -386,15 +386,15 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) > sess->status = STATUS_STOPPED; > mutex_unlock(&core->lock); [Severity: Critical] Can this error path cause a double-free of the codec DMA buffers? Looking at the error handling above this diff in vdec_start_streaming(): drivers/staging/media/meson/vdec/vdec.c:vdec_start_streaming() { [ ... ] err_cleanup: vdec_free_canvas(sess); vdec_poweroff(sess); if (codec_ops && codec_ops->stop && sess->priv) { codec_ops->stop(sess); [ ... ] } When vdec_poweroff(sess) is called, it cascades to __vdec_1_stop(), which calls codec_ops->stop(sess). The cleanup block then explicitly calls codec_ops->stop(sess) a second time. If the codec stop function (such as codec_h264_stop) frees the DMA memory allocations without nullifying the pointers, could passing the exact same DMA addresses to dma_free_coherent() a second time result in a double-free and heap corruption? [Severity: High] This is a pre-existing issue, but does this bypass the cleanup of the session context and permanently leak memory? During vdec_poweron(sess), the driver allocates the sess->priv context and codec DMA buffers. If a subsequent initialization step fails, vdec_poweron propagates the error, and vdec_start_streaming() jumps directly to vififo_free: drivers/staging/media/meson/vdec/vdec.c:vdec_start_streaming() { [ ... ] ret = vdec_poweron(sess); if (ret) goto vififo_free; [ ... ] err_cleanup: [ ... ] kfree(sess->priv); [ ... ] vififo_free: [ ... ] bufs_done: [ ... ] sess->status = STATUS_STOPPED; } This bypasses the err_cleanup: label and the kfree(sess->priv) cleanup. Because the session status is then explicitly set to STATUS_STOPPED right here at bufs_done, won't later calls to vdec_stop_streaming() evaluate old_status == STATUS_STOPPED and skip the entire cleanup block, permanently leaking the memory? [Severity: Critical] Does the newly introduced locking fix in vdec_stop_streaming() lead to a Use-After-Free during a dynamic resolution change? When the decoder detects a resolution change, it sets sess->status to STATUS_NEEDS_RESUME. When userspace handles this by calling STREAMOFF on the capture queue (the first call), vdec_stop_streaming() unconditionally sets the status to STATUS_STOPPED at the start of the function: drivers/staging/media/meson/vdec/vdec.c:vdec_stop_streaming() { [ ... ] mutex_lock(&core->lock); old_status = sess->status; [ ... ] sess->status = STATUS_STOPPED; mutex_unlock(&core->lock); if (old_status == STATUS_RUNNING || old_status == STATUS_INIT || (old_status == STATUS_NEEDS_RESUME && (!sess->streamon_out || !sess->streamon_cap))) { [ ... ] } Because sess->streamon_cap is only cleared at the end of the function, both streams are still active on the first STREAMOFF, so the hardware cleanup condition evaluates to false. When the second STREAMOFF is called on the output queue, old_status is now STATUS_STOPPED, so cleanup is skipped again. Since cleanup is completely bypassed, vififo memory and the recycle_thread are leaked. If the user then calls STREAMON, won't it spawn a duplicate recycle_thread, overwriting the old pointer? When the session is eventually closed, could vdec_close() free the session structure while the orphaned thread continues executing, resulting in a Use-After-Free? > - while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) > - v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); > - while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) > - v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); > - > - if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) > + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { > sess->streamon_out = 0; > - else > + while ((buf = v4l2_m2m_src_buf_remove(sess->m2m_ctx))) > + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); > + } else { > sess->streamon_cap = 0; > + while ((buf = v4l2_m2m_dst_buf_remove(sess->m2m_ctx))) > + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); > + } > > return ret; > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260530094326.11892-1-linux.amoon at gmail.com?part=4 From sashiko-bot at kernel.org Sat May 30 03:59:45 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 30 May 2026 10:59:45 +0000 Subject: [PATCH v6 5/8] media: meson: vdec: Cancel esparser work during teardown In-Reply-To: <20260530094326.11892-6-linux.amoon@gmail.com> References: <20260530094326.11892-6-linux.amoon@gmail.com> Message-ID: <20260530105945.95F941F00898@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: New issues: - [High] Calling `cancel_work_sync(&sess->esparser_queue_work)` while holding the session lock (`sess->lock`) causes a guaranteed AB-BA deadlock if the `esparser_queue_all_src` work is executing. Pre-existing issues: - [Critical] Prematurely clearing the hardware owner `core->cur_sess` introduces a critical race condition that can corrupt hardware state and disable clocks for concurrent sessions. -- commit 20f0925bb191ee052dd2907764b22c2e4a352074 Author: Anand Moon media: meson: vdec: Cancel esparser work during teardown This commit adds cancel_work_sync() calls during streaming stop, error unwind, and device close. It aims to prevent use-after-free hazards by ensuring the esparser workqueue is drained before releasing session state and buffers. Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index 698a95566ad27..4884ee04b3529 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c > @@ -380,6 +380,8 @@ static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) > sess->vififo_vaddr, sess->vififo_paddr); > sess->vififo_vaddr = NULL; > bufs_done: > + cancel_work_sync(&sess->esparser_queue_work); > + [Severity: High] Does this code introduce a deadlock? The V4L2 core acquires the queue lock (sess->lock) before calling vdec_start_streaming() or vdec_stop_streaming(). If cancel_work_sync() is called here while the ioctl thread holds sess->lock, and the worker thread is currently executing: drivers/staging/media/meson/vdec/esparser.c:esparser_queue_all_src() { ... mutex_lock(&sess->lock); ... Could the worker thread block indefinitely waiting for sess->lock, while the ioctl thread blocks indefinitely in cancel_work_sync() waiting for the worker to complete? > mutex_lock(&core->lock); > if (core->cur_sess == sess) > core->cur_sess = NULL; [ ... ] > @@ -437,6 +439,8 @@ static void vdec_stop_streaming(struct vb2_queue *q) > struct vb2_v4l2_buffer *buf; > enum amvdec_status old_status; > > + cancel_work_sync(&sess->esparser_queue_work); > + > /* > * Safely snapshot the status and clear the hardware owner inside > * the mutex to prevent data races with concurrent STREAMON requests. > @@ -448,7 +452,11 @@ static void vdec_stop_streaming(struct vb2_queue *q) > sess->status = STATUS_STOPPED; > mutex_unlock(&core->lock); [Severity: Critical] This isn't a bug introduced by this patch, but does prematurely clearing core->cur_sess here introduce a race condition? Since core->cur_sess is set to NULL under core->lock, but the hardware teardown via vdec_poweroff() executes later after releasing the lock, could a concurrent STREAMON from a different session acquire core->cur_sess and execute vdec_poweron()? This sequence would cause vdec_poweroff() of the old session and vdec_poweron() of the new session to execute simultaneously, potentially leading to conflicting hardware accesses and disabling clocks for the concurrent session. > - /* Evaluate the hardware state using our snapshot */ > + if (q->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { > + if (old_status >= STATUS_RUNNING && codec_ops->drain) > + codec_ops->drain(sess); > + } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260530094326.11892-1-linux.amoon at gmail.com?part=5 From sashiko-bot at kernel.org Sat May 30 04:10:22 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 30 May 2026 11:10:22 +0000 Subject: [PATCH v6 6/8] media: meson: vdec: Configure DMA mask and segment size in probe In-Reply-To: <20260530094326.11892-7-linux.amoon@gmail.com> References: <20260530094326.11892-7-linux.amoon@gmail.com> Message-ID: <20260530111022.9C6D71F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: New issues: - [Critical] Applying a 64-bit DMA mask causes silent physical address truncation and memory corruption, as the hardware and driver only support 32-bit addresses. Pre-existing issues: - [High] The vdec driver stores a raw pointer to the `meson_canvas` device without linking the devices or holding a reference, leading to a Use-After-Free if the canvas provider unbinds. -- commit 895ef453d9c75df04fd40367102f8eafce76e013 Author: Anand Moon media: meson: vdec: Configure DMA mask and segment size in probe The vdec probe routine did not set explicit DMA constraints, leaving the driver dependent on platform defaults. This could cause allocation failures or fragmented buffer handling on systems with stricter DMA limits. This change configures a 64-bit DMA mask and sets the maximum contiguous segment size to UINT_MAX. Closes: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index 4884ee04b3529..f99335effe176 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c > @@ -1064,6 +1064,15 @@ static int vdec_probe(struct platform_device *pdev) > if (IS_ERR(core->canvas)) > return PTR_ERR(core->canvas); [Severity: High] This isn't a bug introduced by this patch, but can we experience a use-after-free here if the canvas provider unbinds? Looking at meson_canvas_get(), the reference to the provider device is dropped immediately before returning the internal canvas pointer: drivers/soc/amlogic/meson-canvas.c:meson_canvas_get() { ... put_device(&canvas_pdev->dev); return canvas; } Since the vdec driver stores this pointer in core->canvas but does not establish a device link or hold a module reference, what prevents the canvas memory from being freed by devres while the vdec driver is still using it? > > + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); [Severity: Critical] Will setting a 64-bit DMA mask cause silent physical address truncation? The Meson VDEC hardware natively uses 32-bit registers for DMA addresses, and the driver's helper functions strictly accept 32-bit values: drivers/staging/media/meson/vdec/vdec_helpers.h: void amvdec_write_dos(struct amvdec_core *core, u32 reg, u32 val); If the DMA API allocates memory buffers at physical addresses above 4GB, their 64-bit addresses will be implicitly truncated to 32 bits when passed to the hardware registers. Can this lead to the hardware reading or writing to incorrect physical memory locations in the lower 4GB? > + if (ret) > + return dev_err_probe(dev, ret, "Failed to set DMA mask\n"); > + > + ret = vb2_dma_contig_set_max_seg_size(dev, UINT_MAX); > + if (ret) > + return dev_err_probe(dev, ret, > + "Failed to set DMA max segment size\n"); > + > of_id = of_match_node(vdec_dt_match, dev->of_node); > core->platform = of_id->data; -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260530094326.11892-1-linux.amoon at gmail.com?part=6 From sashiko-bot at kernel.org Sat May 30 04:23:43 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 30 May 2026 11:23:43 +0000 Subject: [PATCH v6 7/8] media: meson: vdec: Fix NULL pointer dereference in ISR handlers In-Reply-To: <20260530094326.11892-8-linux.amoon@gmail.com> References: <20260530094326.11892-8-linux.amoon@gmail.com> Message-ID: <20260530112343.E33051F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: New issues: - [Critical] Freeing `core->m2m_dev` in `vdec_remove` causes a use-after-free vulnerability when userspace keeps file descriptors open. - [High] Returning IRQ_NONE without clearing the hardware interrupt status when `core->cur_sess` is NULL leaves the level-triggered hardware interrupt unacknowledged, causing an interrupt storm. - [High] Bailing out early in the ISR prevents updating `sess->last_irq_jiffies`, completely bypassing the hardware quiescence wait. - [High] Prematurely releasing `core->lock` and clearing `core->cur_sess` in `vdec_stop_streaming()` allows another session to start streaming concurrently while the hardware is still being powered off. Pre-existing issues: - [High] The patch fails to resolve the underlying race condition, leaving a Time-of-Check to Time-of-Use (TOCTOU) vulnerability that causes Use-After-Free and bus aborts. -- commit c640aebabd0e51f1ec94916e63bc19e2fc312eb7 Author: Anand Moon media: meson: vdec: Fix NULL pointer dereference in ISR handlers This commit attempts to fix a NULL pointer dereference in the vdec interrupt handlers by using READ_ONCE() to take an atomic snapshot of core->cur_sess. If the session has already been torn down and the pointer is NULL, it returns IRQ_NONE to prevent dereferencing it. Link: https://lore.kernel.org/all/20260521090944.F35401F00A3D at smtp.kernel.org/ > diff --git a/drivers/staging/media/meson/vdec/vdec.c b/drivers/staging/media/meson/vdec/vdec.c > index f99335effe176..3897c75b19c8f 100644 > --- a/drivers/staging/media/meson/vdec/vdec.c > +++ b/drivers/staging/media/meson/vdec/vdec.c > @@ -996,17 +996,36 @@ static const struct v4l2_file_operations vdec_fops = { > static irqreturn_t vdec_isr(int irq, void *data) > { > struct amvdec_core *core = data; > - struct amvdec_session *sess = core->cur_sess; > + struct amvdec_session *sess; > + irqreturn_t ret = IRQ_HANDLED; > + > + /* > + * Use READ_ONCE to secure an atomic snapshot of the pointer, > + * protecting against concurrent clearing during streaming > + * teardowns. > + */ > + sess = READ_ONCE(core->cur_sess); > + if (!sess) > + return IRQ_NONE; [Severity: High] Can returning IRQ_NONE here cause an interrupt storm? In vdec_stop_streaming(), core->cur_sess is set to NULL before the hardware is actually stopped. If an active hardware interrupt fires during this window, the handler observes a NULL sess and returns IRQ_NONE early. Because the interrupt is not cleared in the hardware registers, the level-triggered interrupt remains asserted. Will this trigger an infinite interrupt loop until the kernel's spurious interrupt detector permanently disables the IRQ line? [Severity: High] Does bailing out early prevent updating sess->last_irq_jiffies? The teardown sequence calls vdec_wait_inactive(), which sleeps until 50ms have elapsed since sess->last_irq_jiffies. By returning early when core->cur_sess is NULL, the handler no longer updates this timestamp for trailing interrupts. Could vdec_wait_inactive() observe a stale timestamp and return immediately instead of waiting for the hardware to quiesce? This might allow the driver to forcefully disable device clocks while the hardware is still actively processing. > > sess->last_irq_jiffies = get_jiffies_64(); > + ret = sess->fmt_out->codec_ops->isr(sess); > > - return sess->fmt_out->codec_ops->isr(sess); > + return ret; > } > > static irqreturn_t vdec_threaded_isr(int irq, void *data) > { > struct amvdec_core *core = data; > - struct amvdec_session *sess = core->cur_sess; > + struct amvdec_session *sess; > + > + /* > + * Prevent late-stage threaded interrupts from dereferencing a NULL > + * session. > + */ > + sess = READ_ONCE(core->cur_sess); [Severity: High] This is a pre-existing issue, but does using READ_ONCE() here fully resolve the underlying race condition? If the thread is preempted after passing the !sess check, could vdec_stop_streaming() concurrently clear core->cur_sess, disable hardware clocks via vdec_poweroff(), and free sess->priv? When the thread resumes, it would dereference the freed sess->priv and access disabled hardware. Should the driver use synchronize_irq() to ensure in-flight handlers have completed before tearing down session resources? > + if (!sess) > + return IRQ_NONE; > > return sess->fmt_out->codec_ops->threaded_isr(sess); > } [Severity: High] Does prematurely releasing core->lock and clearing core->cur_sess in vdec_stop_streaming() allow another session to start streaming concurrently while the hardware is still being powered off? vdec_stop_streaming() acquires core->lock to set core->cur_sess = NULL, but then releases the lock before stopping the hardware via vdec_poweroff(). Could a concurrent vdec_start_streaming() on a different session successfully claim core->cur_sess, release the lock, and call vdec_poweron(), breaking hardware exclusion? [Severity: Critical] Does freeing core->m2m_dev in vdec_remove() cause a use-after-free vulnerability when userspace keeps file descriptors open? The driver allocates a shared m2m_dev in vdec_probe() and explicitly frees it via v4l2_m2m_release(core->m2m_dev) in vdec_remove(). However, vdec_remove() is executed on device unbind, which does not wait for userspace to close active file descriptors. If a user holds an open file descriptor when the device is unbound, m2m_dev is freed immediately. When the user later closes the file descriptor, vdec_close() calls v4l2_m2m_ctx_release(sess->m2m_ctx), which internally dereferences the already-freed m2m_dev. -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260530094326.11892-1-linux.amoon at gmail.com?part=7 From sashiko-bot at kernel.org Sat May 30 04:35:34 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sat, 30 May 2026 11:35:34 +0000 Subject: [PATCH v6 8/8] gpu: drm: meson: Fix DMA max segment size for DMABUF imports In-Reply-To: <20260530094326.11892-9-linux.amoon@gmail.com> References: <20260530094326.11892-9-linux.amoon@gmail.com> Message-ID: <20260530113535.1D3611F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: Pre-existing issues: - [High] Dereferencing a NULL `priv` pointer on the `devm_kzalloc` error path. - [High] Missing `drm_bridge_remove()` on encoder probe error paths leaves a dangling bridge in the global DRM list, leading to a Use-After-Free. - [High] Prematurely dropping the canvas device reference without a device link causes a Use-After-Free if the canvas driver is unbound. -- commit a3547c6366bfd30d17cb91e687b8ed0c53c24146 Author: Anand Moon gpu: drm: meson: Fix DMA max segment size for DMABUF imports When using hardware-accelerated video decoding via v4l2m2m-copy with mpv or similar players, the video decoder driver (meson_vdec) exports contiguous memory allocations as DMABUFs. When these buffers are subsequently imported by the display controller driver (meson-drm) for rendering via the GPU or compositor, the DMA API throws constraint validation warnings. > diff --git a/drivers/gpu/drm/meson/meson_drv.c b/drivers/gpu/drm/meson/meson_drv.c > index 49ff9f1f16d32..8570add8b8315 100644 > --- a/drivers/gpu/drm/meson/meson_drv.c > +++ b/drivers/gpu/drm/meson/meson_drv.c > @@ -247,6 +247,8 @@ static int meson_drv_bind_master(struct device *dev, bool has_components) > goto free_drm; > } [Severity: High] This is a pre-existing issue, but does the error path in meson_drv_bind_master() cause a NULL pointer dereference? If devm_kzalloc() fails earlier in this function: drivers/gpu/drm/meson/meson_drv.c:meson_drv_bind_master() { ... priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) { ret = -ENOMEM; goto free_drm; } ... } It jumps to the free_drm label with priv set to NULL. The cleanup path sequentially calls meson_encoder_dsi_remove(priv), meson_encoder_hdmi_remove(priv), and meson_encoder_cvbs_remove(priv). Can this cause a kernel panic when these functions unconditionally dereference priv, such as here in meson_encoder_dsi_remove()? drivers/gpu/drm/meson/meson_encoder_dsi.c:meson_encoder_dsi_remove() { ... if (priv->encoders[MESON_ENC_DSI]) { ... } [Severity: High] This is a pre-existing issue, but does meson_canvas_get() leave the system vulnerable to a use-after-free? Looking at meson_canvas_get() in drivers/soc/amlogic/meson-canvas.c, the device reference is dropped after extracting the canvas pointer: drivers/soc/amlogic/meson-canvas.c:meson_canvas_get() { ... canvas = dev_get_drvdata(&canvas_pdev->dev); put_device(&canvas_pdev->dev); if (!canvas) ... } Since no device_link_add() is used to track the dependency between the DRM driver and the canvas driver, the driver core permits unbinding the amlogic-canvas driver. Would doing so free its devm-managed memory and leave meson-drm with a dangling pointer that causes a use-after-free when dereferenced later, such as when locking canvas->lock in meson_canvas_alloc()? [Severity: High] This is a pre-existing issue, but does meson_encoder_cvbs_probe() leak a bridge reference on error paths? In drivers/gpu/drm/meson/meson_encoder_cvbs.c, the bridge is added globally: drivers/gpu/drm/meson/meson_encoder_cvbs.c:meson_encoder_cvbs_probe() { ... drm_bridge_add(&meson_encoder_cvbs->bridge); meson_encoder_cvbs->priv = priv; /* Encoder */ ret = drm_simple_encoder_init(priv->drm, &meson_encoder_cvbs->encoder, DRM_MODE_ENCODER_TVDAC); if (ret) return dev_err_probe(priv->dev, ret, "Failed to init CVBS encoder\n"); ... } If a subsequent initialization step fails, the function returns an error without calling drm_bridge_remove(). The master cleanup function fails to remove it because priv->encoders[MESON_ENC_CVBS] is only populated upon successful probe completion. During devres teardown, the reference count drops to 1, but the bridge is not freed. Can this leave the bridge accessible in the global bridge_list with dangling pointers to the freed priv structure, leading to memory corruption if accessed by other DRM subsystems? > > + dma_set_max_seg_size(dev, UINT_MAX); > + > ret = meson_canvas_alloc(priv->canvas, &priv->canvas_id_osd1); > if (ret) > goto free_drm; -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260530094326.11892-1-linux.amoon at gmail.com?part=8 From mripard at kernel.org Sat May 30 06:59:05 2026 From: mripard at kernel.org (Maxime Ripard) Date: Sat, 30 May 2026 15:59:05 +0200 Subject: [PATCH 00/76] drm/bridge: Convert all reset users to create_state Message-ID: <20260530-drm-no-more-bridge-reset-v1-0-875d828d31bc@kernel.org> Hi, All the bridges use reset to create a blank state only and don't use it to reset the hardware at all. This is what the new atomic_create_state is exactly supposed to be doing, so we can convert all existing bridge users to it, and remove the reset hook and helpers. Let me know what you think, Maxime Signed-off-by: Maxime Ripard --- Maxime Ripard (76): drm/atomic-state-helper: Rename __drm_atomic_helper_bridge_reset() drm/atomic-state-helper: Reorder __drm_atomic_helper_bridge_state_init() arguments drm/atomic-state-helper: Drop memset from __drm_atomic_helper_bridge_state_init() drm/bridge: Add new atomic_create_state callback drm/atomic-state-helper: Add drm_atomic_helper_bridge_create_state() drm/bridge: adv7511: Switch to atomic_create_state drm/bridge: analogix_dp: Switch to atomic_create_state drm/bridge: anx7625: Switch to atomic_create_state drm/bridge: chipone-icn6211: Switch to atomic_create_state drm/bridge: display-connector: Switch to atomic_create_state drm/bridge: fsl-ldb: Switch to atomic_create_state drm/bridge: imx8mp-hdmi-pvi: Switch to atomic_create_state drm/bridge: imx8qm-ldb: Switch to atomic_create_state drm/bridge: imx8qxp-ldb: Switch to atomic_create_state drm/bridge: imx8qxp-pixel-combiner: Switch to atomic_create_state drm/bridge: imx8qxp-pixel-link: Switch to atomic_create_state drm/bridge: imx8qxp-pxl2dpi: Switch to atomic_create_state drm/bridge: inno-hdmi: Switch to atomic_create_state drm/bridge: ite-it6263: Switch to atomic_create_state drm/bridge: ite-it6505: Switch to atomic_create_state drm/bridge: ite-it66121: Switch to atomic_create_state drm/bridge: lontium-lt9211: Switch to atomic_create_state drm/bridge: lontium-lt9611: Switch to atomic_create_state drm/bridge: lvds-codec: Switch to atomic_create_state drm/bridge: nwl-dsi: Switch to atomic_create_state drm/bridge: panel: Switch to atomic_create_state drm/bridge: parade-ps8640: Switch to atomic_create_state drm/bridge: samsung-dsim: Switch to atomic_create_state drm/bridge: sii902x: Switch to atomic_create_state drm/bridge: ssd2825: Switch to atomic_create_state drm/bridge: dw-dp: Switch to atomic_create_state drm/bridge: dw-hdmi-qp: Switch to atomic_create_state drm/bridge: dw-hdmi: Switch to atomic_create_state drm/bridge: dw-mipi-dsi: Switch to atomic_create_state drm/bridge: dw-mipi-dsi2: Switch to atomic_create_state drm/bridge: tc358762: Switch to atomic_create_state drm/bridge: tc358767: Switch to atomic_create_state drm/bridge: tc358768: Switch to atomic_create_state drm/bridge: tc358775: Switch to atomic_create_state drm/bridge: ti-dlpc3433: Switch to atomic_create_state drm/bridge: ti-sn65dsi83: Switch to atomic_create_state drm/bridge: ti-sn65dsi86: Switch to atomic_create_state drm/bridge: ti-tdp158: Switch to atomic_create_state drm/bridge: ti-tfp410: Switch to atomic_create_state drm/imx: parallel-display: Switch to atomic_create_state drm/ingenic: Switch to atomic_create_state drm/mediatek: dp: Switch to atomic_create_state drm/mediatek: dpi: Switch to atomic_create_state drm/mediatek: dsi: Switch to atomic_create_state drm/mediatek: hdmi: Switch to atomic_create_state drm/mediatek: hdmi_v2: Switch to atomic_create_state drm/meson: encoder_cvbs: Switch to atomic_create_state drm/meson: encoder_dsi: Switch to atomic_create_state drm/meson: encoder_hdmi: Switch to atomic_create_state drm/msm: dp: Switch to atomic_create_state drm/msm: hdmi: Switch to atomic_create_state drm/omap: hdmi4: Switch to atomic_create_state drm/omap: hdmi5: Switch to atomic_create_state drm/renesas: rcar-du: lvds: Switch to atomic_create_state drm/renesas: rcar-du: mipi_dsi: Switch to atomic_create_state drm/renesas: rz-du: mipi_dsi: Switch to atomic_create_state drm/rockchip: cdn-dp: Switch to atomic_create_state drm/rockchip: rk3066_hdmi: Switch to atomic_create_state drm/rockchip: lvds: Switch to atomic_create_state drm/stm: lvds: Switch to atomic_create_state drm/tests: bridge: Switch to atomic_create_state drm/tidss: encoder: Switch to atomic_create_state drm/tidss: oldi: Switch to atomic_create_state drm/vc4: dsi: Switch to atomic_create_state drm/verisilicon: Switch to atomic_create_state drm/xlnx: zynqmp_dp: Switch to atomic_create_state drm/atomic-state-helper: Remove drm_atomic_helper_bridge_reset() drm/bridge: cdns-dsi: Use __drm_atomic_helper_bridge_state_init() drm/bridge: cdns-dsi: Switch to atomic_create_state drm/bridge: cdns-mhdp8546: Switch to atomic_create_state drm/bridge: Remove atomic_reset support drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 2 +- drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 2 +- drivers/gpu/drm/bridge/analogix/anx7625.c | 2 +- drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 9 +++--- .../gpu/drm/bridge/cadence/cdns-mhdp8546-core.c | 8 +++--- drivers/gpu/drm/bridge/chipone-icn6211.c | 2 +- drivers/gpu/drm/bridge/display-connector.c | 2 +- drivers/gpu/drm/bridge/fsl-ldb.c | 2 +- drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pvi.c | 2 +- drivers/gpu/drm/bridge/imx/imx8qm-ldb.c | 2 +- drivers/gpu/drm/bridge/imx/imx8qxp-ldb.c | 2 +- .../gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c | 2 +- drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c | 2 +- drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c | 2 +- drivers/gpu/drm/bridge/inno-hdmi.c | 2 +- drivers/gpu/drm/bridge/ite-it6263.c | 2 +- drivers/gpu/drm/bridge/ite-it6505.c | 2 +- drivers/gpu/drm/bridge/ite-it66121.c | 2 +- drivers/gpu/drm/bridge/lontium-lt9211.c | 2 +- drivers/gpu/drm/bridge/lontium-lt9611.c | 2 +- drivers/gpu/drm/bridge/lvds-codec.c | 2 +- drivers/gpu/drm/bridge/nwl-dsi.c | 2 +- drivers/gpu/drm/bridge/panel.c | 2 +- drivers/gpu/drm/bridge/parade-ps8640.c | 2 +- drivers/gpu/drm/bridge/samsung-dsim.c | 2 +- drivers/gpu/drm/bridge/sii902x.c | 2 +- drivers/gpu/drm/bridge/ssd2825.c | 2 +- drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2 +- drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 2 +- drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 2 +- drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c | 2 +- drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c | 2 +- drivers/gpu/drm/bridge/tc358762.c | 2 +- drivers/gpu/drm/bridge/tc358767.c | 4 +-- drivers/gpu/drm/bridge/tc358768.c | 2 +- drivers/gpu/drm/bridge/tc358775.c | 2 +- drivers/gpu/drm/bridge/ti-dlpc3433.c | 2 +- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 2 +- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 2 +- drivers/gpu/drm/bridge/ti-tdp158.c | 2 +- drivers/gpu/drm/bridge/ti-tfp410.c | 2 +- drivers/gpu/drm/drm_atomic_state_helper.c | 33 ++++++++++++---------- drivers/gpu/drm/drm_bridge.c | 4 +-- drivers/gpu/drm/imx/ipuv3/parallel-display.c | 2 +- drivers/gpu/drm/ingenic/ingenic-drm-drv.c | 2 +- drivers/gpu/drm/mediatek/mtk_dp.c | 2 +- drivers/gpu/drm/mediatek/mtk_dpi.c | 2 +- drivers/gpu/drm/mediatek/mtk_dsi.c | 2 +- drivers/gpu/drm/mediatek/mtk_hdmi.c | 2 +- drivers/gpu/drm/mediatek/mtk_hdmi_v2.c | 2 +- drivers/gpu/drm/meson/meson_encoder_cvbs.c | 2 +- drivers/gpu/drm/meson/meson_encoder_dsi.c | 2 +- drivers/gpu/drm/meson/meson_encoder_hdmi.c | 2 +- drivers/gpu/drm/msm/dp/dp_drm.c | 4 +-- drivers/gpu/drm/msm/hdmi/hdmi_bridge.c | 2 +- drivers/gpu/drm/omapdrm/dss/hdmi4.c | 2 +- drivers/gpu/drm/omapdrm/dss/hdmi5.c | 2 +- drivers/gpu/drm/renesas/rcar-du/rcar_lvds.c | 2 +- drivers/gpu/drm/renesas/rcar-du/rcar_mipi_dsi.c | 2 +- drivers/gpu/drm/renesas/rz-du/rzg2l_mipi_dsi.c | 2 +- drivers/gpu/drm/rockchip/cdn-dp-core.c | 2 +- drivers/gpu/drm/rockchip/rk3066_hdmi.c | 2 +- drivers/gpu/drm/rockchip/rockchip_lvds.c | 2 +- drivers/gpu/drm/stm/lvds.c | 2 +- drivers/gpu/drm/tests/drm_bridge_test.c | 2 +- drivers/gpu/drm/tidss/tidss_encoder.c | 2 +- drivers/gpu/drm/tidss/tidss_oldi.c | 2 +- drivers/gpu/drm/vc4/vc4_dsi.c | 2 +- drivers/gpu/drm/verisilicon/vs_bridge.c | 4 +-- drivers/gpu/drm/xlnx/zynqmp_dp.c | 2 +- include/drm/drm_atomic_state_helper.h | 6 ++-- include/drm/drm_bridge.h | 33 ++++++++-------------- 72 files changed, 111 insertions(+), 120 deletions(-) --- base-commit: 21fcb222f0d1e1c9f5b04c09e9fb3408e13a0264 change-id: 20260530-drm-no-more-bridge-reset-ca20d5e22740 Best regards, -- Maxime Ripard From mripard at kernel.org Sat May 30 06:59:57 2026 From: mripard at kernel.org (Maxime Ripard) Date: Sat, 30 May 2026 15:59:57 +0200 Subject: [PATCH 52/76] drm/meson: encoder_cvbs: Switch to atomic_create_state In-Reply-To: <20260530-drm-no-more-bridge-reset-v1-0-875d828d31bc@kernel.org> References: <20260530-drm-no-more-bridge-reset-v1-0-875d828d31bc@kernel.org> Message-ID: <20260530-drm-no-more-bridge-reset-v1-52-875d828d31bc@kernel.org> The drm_bridge_funcs.atomic_reset callback and its drm_atomic_helper_bridge_reset() helper are deprecated. Switch to the atomic_create_state callback and its drm_atomic_helper_bridge_create_state() counterpart. Signed-off-by: Maxime Ripard --- To: Neil Armstrong To: Kevin Hilman Cc: Jerome Brunet Cc: Martin Blumenstingl Cc: dri-devel at lists.freedesktop.org Cc: linux-amlogic at lists.infradead.org Cc: linux-arm-kernel at lists.infradead.org --- drivers/gpu/drm/meson/meson_encoder_cvbs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/meson/meson_encoder_cvbs.c b/drivers/gpu/drm/meson/meson_encoder_cvbs.c index 8b26a0031cde..22cacb1660c4 100644 --- a/drivers/gpu/drm/meson/meson_encoder_cvbs.c +++ b/drivers/gpu/drm/meson/meson_encoder_cvbs.c @@ -213,11 +213,11 @@ static const struct drm_bridge_funcs meson_encoder_cvbs_bridge_funcs = { .atomic_enable = meson_encoder_cvbs_atomic_enable, .atomic_disable = meson_encoder_cvbs_atomic_disable, .atomic_check = meson_encoder_cvbs_atomic_check, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, - .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_create_state = drm_atomic_helper_bridge_create_state, }; int meson_encoder_cvbs_probe(struct meson_drm *priv) { struct drm_device *drm = priv->drm; -- 2.54.0 From mripard at kernel.org Sat May 30 06:59:58 2026 From: mripard at kernel.org (Maxime Ripard) Date: Sat, 30 May 2026 15:59:58 +0200 Subject: [PATCH 53/76] drm/meson: encoder_dsi: Switch to atomic_create_state In-Reply-To: <20260530-drm-no-more-bridge-reset-v1-0-875d828d31bc@kernel.org> References: <20260530-drm-no-more-bridge-reset-v1-0-875d828d31bc@kernel.org> Message-ID: <20260530-drm-no-more-bridge-reset-v1-53-875d828d31bc@kernel.org> The drm_bridge_funcs.atomic_reset callback and its drm_atomic_helper_bridge_reset() helper are deprecated. Switch to the atomic_create_state callback and its drm_atomic_helper_bridge_create_state() counterpart. Signed-off-by: Maxime Ripard --- To: Neil Armstrong To: Kevin Hilman Cc: Jerome Brunet Cc: Martin Blumenstingl Cc: dri-devel at lists.freedesktop.org Cc: linux-amlogic at lists.infradead.org Cc: linux-arm-kernel at lists.infradead.org --- drivers/gpu/drm/meson/meson_encoder_dsi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/meson/meson_encoder_dsi.c b/drivers/gpu/drm/meson/meson_encoder_dsi.c index c1f4685073bb..3e422b612f74 100644 --- a/drivers/gpu/drm/meson/meson_encoder_dsi.c +++ b/drivers/gpu/drm/meson/meson_encoder_dsi.c @@ -94,11 +94,11 @@ static const struct drm_bridge_funcs meson_encoder_dsi_bridge_funcs = { .attach = meson_encoder_dsi_attach, .atomic_enable = meson_encoder_dsi_atomic_enable, .atomic_disable = meson_encoder_dsi_atomic_disable, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, - .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_create_state = drm_atomic_helper_bridge_create_state, }; int meson_encoder_dsi_probe(struct meson_drm *priv) { struct meson_encoder_dsi *meson_encoder_dsi; -- 2.54.0 From mripard at kernel.org Sat May 30 06:59:59 2026 From: mripard at kernel.org (Maxime Ripard) Date: Sat, 30 May 2026 15:59:59 +0200 Subject: [PATCH 54/76] drm/meson: encoder_hdmi: Switch to atomic_create_state In-Reply-To: <20260530-drm-no-more-bridge-reset-v1-0-875d828d31bc@kernel.org> References: <20260530-drm-no-more-bridge-reset-v1-0-875d828d31bc@kernel.org> Message-ID: <20260530-drm-no-more-bridge-reset-v1-54-875d828d31bc@kernel.org> The drm_bridge_funcs.atomic_reset callback and its drm_atomic_helper_bridge_reset() helper are deprecated. Switch to the atomic_create_state callback and its drm_atomic_helper_bridge_create_state() counterpart. Signed-off-by: Maxime Ripard --- To: Neil Armstrong To: Kevin Hilman Cc: Jerome Brunet Cc: Martin Blumenstingl Cc: dri-devel at lists.freedesktop.org Cc: linux-amlogic at lists.infradead.org Cc: linux-arm-kernel at lists.infradead.org --- drivers/gpu/drm/meson/meson_encoder_hdmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c index 55c0601df3c6..0c7a72cb514a 100644 --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c @@ -364,11 +364,11 @@ static const struct drm_bridge_funcs meson_encoder_hdmi_bridge_funcs = { .atomic_disable = meson_encoder_hdmi_atomic_disable, .atomic_get_input_bus_fmts = meson_encoder_hdmi_get_inp_bus_fmts, .atomic_check = meson_encoder_hdmi_atomic_check, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, - .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_create_state = drm_atomic_helper_bridge_create_state, }; int meson_encoder_hdmi_probe(struct meson_drm *priv) { struct meson_encoder_hdmi *meson_encoder_hdmi; -- 2.54.0 From lkp at intel.com Sat May 30 11:04:16 2026 From: lkp at intel.com (kernel test robot) Date: Sun, 31 May 2026 02:04:16 +0800 Subject: [PATCH] firmware: meson: sm: add stub functions when CONFIG_MESON_SM is disabled In-Reply-To: <20260530-fix-missing-meson_sm-symbol-v1-1-3fb672b989d4@aliel.fr> References: <20260530-fix-missing-meson_sm-symbol-v1-1-3fb672b989d4@aliel.fr> Message-ID: <202605310154.bmdMBZHJ-lkp@intel.com> Hi Ronald, kernel test robot noticed the following build errors: [auto build test ERROR on 3929405c64376a8a54c794e8a4485023b108a97e] url: https://github.com/intel-lab-lkp/linux/commits/Ronald-Claveau-via-B4-Relay/firmware-meson-sm-add-stub-functions-when-CONFIG_MESON_SM-is-disabled/20260530-150837 base: 3929405c64376a8a54c794e8a4485023b108a97e patch link: https://lore.kernel.org/r/20260530-fix-missing-meson_sm-symbol-v1-1-3fb672b989d4%40aliel.fr patch subject: [PATCH] firmware: meson: sm: add stub functions when CONFIG_MESON_SM is disabled config: arm64-allmodconfig (https://download.01.org/0day-ci/archive/20260531/202605310154.bmdMBZHJ-lkp at intel.com/config) compiler: clang version 19.1.7 (https://github.com/llvm/llvm-project cd708029e0b2869e80abe31ddb175f7c35361f90) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260531/202605310154.bmdMBZHJ-lkp at intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot | Closes: https://lore.kernel.org/oe-kbuild-all/202605310154.bmdMBZHJ-lkp at intel.com/ All errors (new ones prefixed by >>): >> drivers/firmware/meson/meson_sm.c:233:27: error: redefinition of 'meson_sm_get' 233 | struct meson_sm_firmware *meson_sm_get(struct device_node *sm_node) | ^ include/linux/firmware/meson/meson_sm.h:39:41: note: previous definition is here 39 | static inline struct meson_sm_firmware *meson_sm_get(struct device_node *firmware_node) | ^ >> drivers/firmware/meson/meson_sm.c:258:5: error: redefinition of 'meson_sm_get_thermal_calib' 258 | int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, u32 *trim_info, | ^ include/linux/firmware/meson/meson_sm.h:43:19: note: previous definition is here 43 | static inline int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, | ^ 2 errors generated. vim +/meson_sm_get +233 drivers/firmware/meson/meson_sm.c 2c4ddb215521d5 Carlo Caione 2016-08-27 225 8cde3c2153e8f5 Carlo Caione 2019-07-31 226 /** 8cde3c2153e8f5 Carlo Caione 2019-07-31 227 * meson_sm_get - get pointer to meson_sm_firmware structure. 8cde3c2153e8f5 Carlo Caione 2019-07-31 228 * 8cde3c2153e8f5 Carlo Caione 2019-07-31 229 * @sm_node: Pointer to the secure-monitor Device Tree node. 8cde3c2153e8f5 Carlo Caione 2019-07-31 230 * 8cde3c2153e8f5 Carlo Caione 2019-07-31 231 * Return: NULL is the secure-monitor device is not ready. 8cde3c2153e8f5 Carlo Caione 2019-07-31 232 */ 8cde3c2153e8f5 Carlo Caione 2019-07-31 @233 struct meson_sm_firmware *meson_sm_get(struct device_node *sm_node) 8cde3c2153e8f5 Carlo Caione 2019-07-31 234 { 8cde3c2153e8f5 Carlo Caione 2019-07-31 235 struct platform_device *pdev = of_find_device_by_node(sm_node); 8ece3173f87df0 Johan Hovold 2025-07-25 236 struct meson_sm_firmware *fw; 8cde3c2153e8f5 Carlo Caione 2019-07-31 237 8cde3c2153e8f5 Carlo Caione 2019-07-31 238 if (!pdev) 8cde3c2153e8f5 Carlo Caione 2019-07-31 239 return NULL; 8cde3c2153e8f5 Carlo Caione 2019-07-31 240 8ece3173f87df0 Johan Hovold 2025-07-25 241 fw = platform_get_drvdata(pdev); 8ece3173f87df0 Johan Hovold 2025-07-25 242 8ece3173f87df0 Johan Hovold 2025-07-25 243 put_device(&pdev->dev); 8ece3173f87df0 Johan Hovold 2025-07-25 244 8ece3173f87df0 Johan Hovold 2025-07-25 245 return fw; 8cde3c2153e8f5 Carlo Caione 2019-07-31 246 } 8cde3c2153e8f5 Carlo Caione 2019-07-31 247 EXPORT_SYMBOL_GPL(meson_sm_get); 8cde3c2153e8f5 Carlo Caione 2019-07-31 248 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 249 /** 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 250 * meson_sm_get_thermal_calib - Read thermal sensor calibration data. 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 251 * @fw: Pointer to secure-monitor firmware. 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 252 * @trim_info: Pointer to store the returned calibration data. 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 253 * @tsensor_id: Sensor index to identify which sensor's calibration data 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 254 * to retrieve 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 255 * 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 256 * Return: 0 on success, negative error code on failure. 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 257 */ 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 @258 int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, u32 *trim_info, 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 259 u32 tsensor_id) 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 260 { 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 261 return meson_sm_call(fw, SM_THERMAL_CALIB_READ, trim_info, tsensor_id, 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 262 0, 0, 0, 0); 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 263 } 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 264 EXPORT_SYMBOL_GPL(meson_sm_get_thermal_calib); 1c5cb7391ef5f8 Ronald Claveau 2026-04-24 265 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki From namanarora029 at gmail.com Sun May 31 00:35:26 2026 From: namanarora029 at gmail.com (Naman Arora) Date: Sun, 31 May 2026 13:05:26 +0530 Subject: [PATCH 0/6] drm: Open-code drm_simple_encoder_init() in several drivers Message-ID: <20260531073532.8609-1-namanarora029@gmail.com> drm_simple_encoder_init() is a thin wrapper around drm_encoder_init() that provides a simple destroy-only encoder funcs struct. This series removes the dependency on drm_simple_kms_helper in six drivers by open-coding the encoder initialization directly. Each patch adds a static drm_encoder_funcs struct with a destroy callback and replaces drm_simple_encoder_init() with drm_encoder_init(). The drm_simple_kms_helper.h include is removed where it is no longer needed. Drivers converted in this series: - fsl-dcu - tidss - virtio - meson (encoder_cvbs, encoder_hdmi, encoder_dsi) Naman Arora (6): drm/fsl-dcu: Open-code drm_simple_encoder_init() drm/tidss: Open-code drm_simple_encoder_init() drm/virtio: Open-code drm_simple_encoder_init() drm/meson: Open-code drm_simple_encoder_init() in encoder_cvbs drm/meson: Open-code drm_simple_encoder_init() in encoder_hdmi drm/meson: Open-code drm_simple_encoder_init() in encoder_dsi drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c | 10 +++++++--- drivers/gpu/drm/meson/meson_encoder_cvbs.c | 10 +++++++--- drivers/gpu/drm/meson/meson_encoder_dsi.c | 10 +++++++--- drivers/gpu/drm/meson/meson_encoder_hdmi.c | 10 +++++++--- drivers/gpu/drm/tidss/tidss_encoder.c | 10 +++++++--- drivers/gpu/drm/virtio/virtgpu_display.c | 8 ++++++-- 6 files changed, 41 insertions(+), 17 deletions(-) -- 2.20.1 From namanarora029 at gmail.com Sun May 31 00:35:27 2026 From: namanarora029 at gmail.com (Naman Arora) Date: Sun, 31 May 2026 13:05:27 +0530 Subject: [PATCH 1/6] drm/fsl-dcu: Open-code drm_simple_encoder_init() In-Reply-To: <20260531073532.8609-1-namanarora029@gmail.com> References: <20260531073532.8609-1-namanarora029@gmail.com> Message-ID: <20260531073532.8609-2-namanarora029@gmail.com> The helper drm_simple_encoder_init() is a thin wrapper around drm_encoder_init() with a simple destroy-only encoder funcs struct. Remove the dependency on drm_simple_kms_helper by open-coding the encoder initialization directly in the driver. Signed-off-by: Naman Arora --- drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c index 84eff7519..a16c6013e 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c @@ -14,11 +14,14 @@ #include #include #include -#include #include "fsl_dcu_drm_drv.h" #include "fsl_tcon.h" +static const struct drm_encoder_funcs fsl_dcu_drm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + int fsl_dcu_drm_encoder_create(struct fsl_dcu_drm_device *fsl_dev, struct drm_crtc *crtc) { @@ -31,8 +34,9 @@ int fsl_dcu_drm_encoder_create(struct fsl_dcu_drm_device *fsl_dev, if (fsl_dev->tcon) fsl_tcon_bypass_enable(fsl_dev->tcon); - ret = drm_simple_encoder_init(fsl_dev->drm, encoder, - DRM_MODE_ENCODER_LVDS); + ret = drm_encoder_init(fsl_dev->drm, encoder, + &fsl_dcu_drm_encoder_funcs, + DRM_MODE_ENCODER_LVDS, NULL); if (ret < 0) return ret; -- 2.20.1 From namanarora029 at gmail.com Sun May 31 00:35:28 2026 From: namanarora029 at gmail.com (Naman Arora) Date: Sun, 31 May 2026 13:05:28 +0530 Subject: [PATCH 2/6] drm/tidss: Open-code drm_simple_encoder_init() In-Reply-To: <20260531073532.8609-1-namanarora029@gmail.com> References: <20260531073532.8609-1-namanarora029@gmail.com> Message-ID: <20260531073532.8609-3-namanarora029@gmail.com> The helper drm_simple_encoder_init() is a thin wrapper around drm_encoder_init() with a simple destroy-only encoder funcs struct. Remove the dependency on drm_simple_kms_helper by open-coding the encoder initialization directly in the driver. Signed-off-by: Naman Arora --- drivers/gpu/drm/tidss/tidss_encoder.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/tidss/tidss_encoder.c b/drivers/gpu/drm/tidss/tidss_encoder.c index 81a04f767..4d73a271c 100644 --- a/drivers/gpu/drm/tidss/tidss_encoder.c +++ b/drivers/gpu/drm/tidss/tidss_encoder.c @@ -13,7 +13,6 @@ #include #include #include -#include #include "tidss_crtc.h" #include "tidss_drv.h" @@ -81,6 +80,10 @@ static const struct drm_bridge_funcs tidss_bridge_funcs = { .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, }; +static const struct drm_encoder_funcs tidss_drm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + int tidss_encoder_create(struct tidss_device *tidss, struct drm_bridge *next_bridge, u32 encoder_type, u32 possible_crtcs) @@ -95,8 +98,9 @@ int tidss_encoder_create(struct tidss_device *tidss, if (IS_ERR(t_enc)) return PTR_ERR(t_enc); - ret = drm_simple_encoder_init(&tidss->ddev, &t_enc->encoder, - encoder_type); + ret = drm_encoder_init(&tidss->ddev, &t_enc->encoder, + &tidss_drm_encoder_funcs, + encoder_type, NULL); if (ret) return ret; -- 2.20.1 From namanarora029 at gmail.com Sun May 31 00:35:29 2026 From: namanarora029 at gmail.com (Naman Arora) Date: Sun, 31 May 2026 13:05:29 +0530 Subject: [PATCH 3/6] drm/virtio: Open-code drm_simple_encoder_init() In-Reply-To: <20260531073532.8609-1-namanarora029@gmail.com> References: <20260531073532.8609-1-namanarora029@gmail.com> Message-ID: <20260531073532.8609-4-namanarora029@gmail.com> The helper drm_simple_encoder_init() is a thin wrapper around drm_encoder_init() with a simple destroy-only encoder funcs struct. Remove the dependency on drm_simple_kms_helper by open-coding the encoder initialization directly in the driver. Signed-off-by: Naman Arora --- drivers/gpu/drm/virtio/virtgpu_display.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/virtio/virtgpu_display.c b/drivers/gpu/drm/virtio/virtgpu_display.c index f1dae9569..5b99cce17 100644 --- a/drivers/gpu/drm/virtio/virtgpu_display.c +++ b/drivers/gpu/drm/virtio/virtgpu_display.c @@ -32,7 +32,6 @@ #include #include #include -#include #include #include @@ -271,6 +270,10 @@ static const struct drm_connector_funcs virtio_gpu_connector_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; +static const struct drm_encoder_funcs virtio_gpu_drm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + static int vgdev_output_init(struct virtio_gpu_device *vgdev, int index) { struct drm_device *dev = vgdev->ddev; @@ -306,7 +309,8 @@ static int vgdev_output_init(struct virtio_gpu_device *vgdev, int index) if (vgdev->has_edid) drm_connector_attach_edid_property(connector); - drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_VIRTUAL); + drm_encoder_init(dev, encoder, &virtio_gpu_drm_encoder_funcs, + DRM_MODE_ENCODER_VIRTUAL, NULL); drm_encoder_helper_add(encoder, &virtio_gpu_enc_helper_funcs); encoder->possible_crtcs = 1 << index; -- 2.20.1 From namanarora029 at gmail.com Sun May 31 00:46:25 2026 From: namanarora029 at gmail.com (Naman Arora) Date: Sun, 31 May 2026 13:16:25 +0530 Subject: [PATCH 4/6] drm/meson: Open-code drm_simple_encoder_init() in encoder_cvbs In-Reply-To: <20260531073532.8609-1-namanarora029@gmail.com> References: <20260531073532.8609-1-namanarora029@gmail.com> Message-ID: <20260531074627.8936-1-namanarora029@gmail.com> The helper drm_simple_encoder_init() is a thin wrapper around drm_encoder_init() with a simple destroy-only encoder funcs struct. Remove the dependency on drm_simple_kms_helper by open-coding the encoder initialization directly in the driver. Signed-off-by: Naman Arora --- drivers/gpu/drm/meson/meson_encoder_cvbs.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_encoder_cvbs.c b/drivers/gpu/drm/meson/meson_encoder_cvbs.c index 41071d6e0..496100ba2 100644 --- a/drivers/gpu/drm/meson/meson_encoder_cvbs.c +++ b/drivers/gpu/drm/meson/meson_encoder_cvbs.c @@ -18,7 +18,6 @@ #include #include #include -#include #include "meson_registers.h" #include "meson_vclk.h" @@ -218,6 +217,10 @@ static const struct drm_bridge_funcs meson_encoder_cvbs_bridge_funcs = { .atomic_reset = drm_atomic_helper_bridge_reset, }; +static const struct drm_encoder_funcs meson_encoder_cvbs_drm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + int meson_encoder_cvbs_probe(struct meson_drm *priv) { struct drm_device *drm = priv->drm; @@ -257,8 +260,9 @@ int meson_encoder_cvbs_probe(struct meson_drm *priv) meson_encoder_cvbs->priv = priv; /* Encoder */ - ret = drm_simple_encoder_init(priv->drm, &meson_encoder_cvbs->encoder, - DRM_MODE_ENCODER_TVDAC); + ret = drm_encoder_init(priv->drm, &meson_encoder_cvbs->encoder, + &meson_encoder_cvbs_drm_encoder_funcs, + DRM_MODE_ENCODER_TVDAC, NULL); if (ret) return dev_err_probe(priv->dev, ret, "Failed to init CVBS encoder\n"); -- 2.20.1 From namanarora029 at gmail.com Sun May 31 00:46:26 2026 From: namanarora029 at gmail.com (Naman Arora) Date: Sun, 31 May 2026 13:16:26 +0530 Subject: [PATCH 5/6] drm/meson: Open-code drm_simple_encoder_init() in encoder_hdmi In-Reply-To: <20260531074627.8936-1-namanarora029@gmail.com> References: <20260531073532.8609-1-namanarora029@gmail.com> <20260531074627.8936-1-namanarora029@gmail.com> Message-ID: <20260531074627.8936-2-namanarora029@gmail.com> The helper drm_simple_encoder_init() is a thin wrapper around drm_encoder_init() with a simple destroy-only encoder funcs struct. Remove the dependency on drm_simple_kms_helper by open-coding the encoder initialization directly in the driver. Signed-off-by: Naman Arora --- drivers/gpu/drm/meson/meson_encoder_hdmi.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c index 1abb0572b..0a0ec34e3 100644 --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c @@ -24,7 +24,6 @@ #include #include #include -#include #include #include @@ -369,6 +368,10 @@ static const struct drm_bridge_funcs meson_encoder_hdmi_bridge_funcs = { .atomic_reset = drm_atomic_helper_bridge_reset, }; +static const struct drm_encoder_funcs meson_encoder_hdmi_drm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + int meson_encoder_hdmi_probe(struct meson_drm *priv) { struct meson_encoder_hdmi *meson_encoder_hdmi; @@ -407,8 +410,9 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) meson_encoder_hdmi->priv = priv; /* Encoder */ - ret = drm_simple_encoder_init(priv->drm, &meson_encoder_hdmi->encoder, - DRM_MODE_ENCODER_TMDS); + ret = drm_encoder_init(priv->drm, &meson_encoder_hdmi->encoder, + &meson_encoder_hdmi_drm_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); if (ret) { dev_err_probe(priv->dev, ret, "Failed to init HDMI encoder\n"); goto err_put_node; -- 2.20.1 From namanarora029 at gmail.com Sun May 31 00:46:27 2026 From: namanarora029 at gmail.com (Naman Arora) Date: Sun, 31 May 2026 13:16:27 +0530 Subject: [PATCH 6/6] drm/meson: Open-code drm_simple_encoder_init() in encoder_dsi In-Reply-To: <20260531074627.8936-1-namanarora029@gmail.com> References: <20260531073532.8609-1-namanarora029@gmail.com> <20260531074627.8936-1-namanarora029@gmail.com> Message-ID: <20260531074627.8936-3-namanarora029@gmail.com> The helper drm_simple_encoder_init() is a thin wrapper around drm_encoder_init() with a simple destroy-only encoder funcs struct. Remove the dependency on drm_simple_kms_helper by open-coding the encoder initialization directly in the driver. Signed-off-by: Naman Arora --- drivers/gpu/drm/meson/meson_encoder_dsi.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/meson/meson_encoder_dsi.c b/drivers/gpu/drm/meson/meson_encoder_dsi.c index eba246791..e14da1f71 100644 --- a/drivers/gpu/drm/meson/meson_encoder_dsi.c +++ b/drivers/gpu/drm/meson/meson_encoder_dsi.c @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -99,6 +98,10 @@ static const struct drm_bridge_funcs meson_encoder_dsi_bridge_funcs = { .atomic_reset = drm_atomic_helper_bridge_reset, }; +static const struct drm_encoder_funcs meson_encoder_dsi_drm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + int meson_encoder_dsi_probe(struct meson_drm *priv) { struct meson_encoder_dsi *meson_encoder_dsi; @@ -133,8 +136,9 @@ int meson_encoder_dsi_probe(struct meson_drm *priv) meson_encoder_dsi->priv = priv; /* Encoder */ - ret = drm_simple_encoder_init(priv->drm, &meson_encoder_dsi->encoder, - DRM_MODE_ENCODER_DSI); + ret = drm_encoder_init(priv->drm, &meson_encoder_dsi->encoder, + &meson_encoder_dsi_drm_encoder_funcs, + DRM_MODE_ENCODER_DSI, NULL); if (ret) return dev_err_probe(priv->dev, ret, "Failed to init DSI encoder\n"); -- 2.20.1 From devnull+linux-kernel-dev.aliel.fr at kernel.org Sun May 31 00:51:37 2026 From: devnull+linux-kernel-dev.aliel.fr at kernel.org (Ronald Claveau via B4 Relay) Date: Sun, 31 May 2026 09:51:37 +0200 Subject: [PATCH v2] firmware: meson: sm: add stub functions when CONFIG_MESON_SM is disabled Message-ID: <20260531-fix-missing-meson_sm-symbol-v2-1-1def8c3c169f@aliel.fr> From: Ronald Claveau After merging the thermal tree, linux-next build (arm_multi_v7 defconfig) failed like this: arm-linux-gnueabihf-ld: drivers/thermal/amlogic_thermal.o: in function `amlogic_thermal_probe_sm': /tmp/next/build/drivers/thermal/amlogic_thermal.c:196:(.text+0x2f4): undefined reference to `meson_sm_get' arm-linux-gnueabihf-ld: /tmp/next/build/drivers/thermal/amlogic_thermal.c:205:(.text+0x320): undefined reference to `meson_sm_get_thermal_calib' Add inline stub implementations of meson_sm_get() and meson_sm_get_thermal_calib() behind an #else guard so that drivers including this header can be compiled without CONFIG_MESON_SM . Fixes: b21d88de6918 ("thermal/drivers/amlogic: Add support for secure monitor calibration readout") Closes: https://lore.kernel.org/oe-kbuild-all/202605291530.en7aGn7w-lkp at intel.com/ Reported-by: Mark Brown Reported-by: kernel test robot Signed-off-by: Ronald Claveau --- Changes in v2: - Replace #ifdef CONFIG_MESON_SM by #if IS_ENABLED(CONFIG_MESON_SM) to cover builtin and module in config. - Add missing trailers for kernel test robot. - Link to v1: https://lore.kernel.org/r/20260530-fix-missing-meson_sm-symbol-v1-1-3fb672b989d4 at aliel.fr --- include/linux/firmware/meson/meson_sm.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/firmware/meson/meson_sm.h index 3ebc2bd9a9760..baecdaf263f41 100644 --- a/include/linux/firmware/meson/meson_sm.h +++ b/include/linux/firmware/meson/meson_sm.h @@ -27,8 +27,25 @@ int meson_sm_call_write(struct meson_sm_firmware *fw, void *buffer, int meson_sm_call_read(struct meson_sm_firmware *fw, void *buffer, unsigned int bsize, unsigned int cmd_index, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4); + +#if IS_ENABLED(CONFIG_MESON_SM) + struct meson_sm_firmware *meson_sm_get(struct device_node *firmware_node); int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, u32 *trim_info, u32 tsensor_id); +#else + +static inline struct meson_sm_firmware *meson_sm_get(struct device_node *firmware_node) +{ + return NULL; +} +static inline int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, + u32 *trim_info, u32 tsensor_id) +{ + return -EINVAL; +} + +#endif + #endif /* _MESON_SM_FW_H_ */ --- base-commit: 3929405c64376a8a54c794e8a4485023b108a97e change-id: 20260529-fix-missing-meson_sm-symbol-7776d0d9d760 Best regards, -- Ronald Claveau From sashiko-bot at kernel.org Sun May 31 00:57:01 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sun, 31 May 2026 07:57:01 +0000 Subject: [PATCH 2/6] drm/tidss: Open-code drm_simple_encoder_init() In-Reply-To: <20260531073532.8609-3-namanarora029@gmail.com> References: <20260531073532.8609-3-namanarora029@gmail.com> Message-ID: <20260531075701.ECC9B1F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] Use-After-Free on driver unbind or probe failure due to mixed devm/drmm lifecycles for the combined encoder/bridge structure. -- commit b7421f2a0634d4a6488478cb165139b91f9afc7d Author: Naman Arora drm/tidss: Open-code drm_simple_encoder_init() The helper drm_simple_encoder_init() is a thin wrapper around drm_encoder_init() with a simple destroy-only encoder funcs struct. Remove the dependency on drm_simple_kms_helper by open-coding the encoder initialization directly in the driver. > diff --git a/drivers/gpu/drm/tidss/tidss_encoder.c b/drivers/gpu/drm/tidss/tidss_encoder.c > index 34db8d2a3792e..1841dc14c739b 100644 > --- a/drivers/gpu/drm/tidss/tidss_encoder.c > +++ b/drivers/gpu/drm/tidss/tidss_encoder.c [ ... ] > @@ -95,8 +98,9 @@ int tidss_encoder_create(struct tidss_device *tidss, > if (IS_ERR(t_enc)) > return PTR_ERR(t_enc); > > - ret = drm_simple_encoder_init(&tidss->ddev, &t_enc->encoder, > - encoder_type); > + ret = drm_encoder_init(&tidss->ddev, &t_enc->encoder, > + &tidss_drm_encoder_funcs, > + encoder_type, NULL); [Severity: High] This is a pre-existing issue, but could this mix of devm and drm lifecycles lead to a use-after-free during driver unbind or probe failure? The t_enc struct is allocated using devm_drm_bridge_alloc(), giving it an initial refcount of 1. Here, drm_encoder_init() registers the embedded t_enc->encoder. Later in this function, drm_bridge_attach() will increase the refcount to 2. During driver unbind, devres LIFO cleanup executes the bridge release action, calling drm_bridge_put() and dropping the refcount to 1. Next, the drm_device release action triggers drm_mode_config_cleanup(), which calls drm_encoder_cleanup(). Inside drm_encoder_cleanup(), calling drm_bridge_detach() drops the final refcount to 0, causing t_enc to be freed. Immediately after that, drm_encoder_cleanup() accesses the now-freed encoder structure. A similar issue appears to happen if drm_bridge_attach() fails during probe, as devres would free t_enc, leaving a dangling pointer for drm_mode_config_cleanup() to trip over. > if (ret) > return ret; > -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260531074627.8936-1-namanarora029 at gmail.com?part=2 From sashiko-bot at kernel.org Sun May 31 01:00:54 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sun, 31 May 2026 08:00:54 +0000 Subject: [PATCH 3/6] drm/virtio: Open-code drm_simple_encoder_init() In-Reply-To: <20260531073532.8609-4-namanarora029@gmail.com> References: <20260531073532.8609-4-namanarora029@gmail.com> Message-ID: <20260531080055.032E81F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] This problem wasn't introduced by this patch, but the return value of drm_encoder_init() is ignored, meaning failures during initialization go undetected and could lead to a kernel panic or oops. -- commit 2aea64f30defed1555346f1f9a6d96e4b1c548c9 Author: Naman Arora drm/virtio: Open-code drm_simple_encoder_init() This commit replaces the drm_simple_encoder_init() wrapper with an open-coded drm_encoder_init() in the virtio driver to remove the dependency on the drm_simple_kms_helper. > diff --git a/drivers/gpu/drm/virtio/virtgpu_display.c b/drivers/gpu/drm/virtio/virtgpu_display.c > --- a/drivers/gpu/drm/virtio/virtgpu_display.c > +++ b/drivers/gpu/drm/virtio/virtgpu_display.c [ ... ] > @@ -306,7 +309,8 @@ static int vgdev_output_init(struct virtio_gpu_device *vgdev, int index) > if (vgdev->has_edid) > drm_connector_attach_edid_property(connector); > > - drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_VIRTUAL); > + drm_encoder_init(dev, encoder, &virtio_gpu_drm_encoder_funcs, > + DRM_MODE_ENCODER_VIRTUAL, NULL); [Severity: High] This isn't a bug introduced by this patch, but should the return value of drm_encoder_init() be checked in vgdev_output_init() here? If drm_encoder_init() fails (for example, returning -ENOMEM if allocating the encoder name fails), the error is ignored. Does this allow the code to proceed and attach an incompletely initialized encoder to the connector, potentially leading to a NULL pointer dereference or crash later when the DRM core interacts with it? > drm_encoder_helper_add(encoder, &virtio_gpu_enc_helper_funcs); > encoder->possible_crtcs = 1 << index; > -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260531074627.8936-1-namanarora029 at gmail.com?part=3 From sashiko-bot at kernel.org Sun May 31 01:08:42 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sun, 31 May 2026 08:08:42 +0000 Subject: [PATCH 4/6] drm/meson: Open-code drm_simple_encoder_init() in encoder_cvbs In-Reply-To: <20260531074627.8936-1-namanarora029@gmail.com> References: <20260531074627.8936-1-namanarora029@gmail.com> Message-ID: <20260531080842.CAAFB1F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] Use-after-free during drm_encoder_cleanup() due to bridge refcount dropping to 0. -- commit e742cfdccd21fa2f8380ad2c61f49c99edf808e1 Author: Naman Arora drm/meson: Open-code drm_simple_encoder_init() in encoder_cvbs The helper drm_simple_encoder_init() is replaced with an open-coded drm_encoder_init() to remove the dependency on drm_simple_kms_helper. A simple destroy-only encoder funcs struct is added. > diff --git a/drivers/gpu/drm/meson/meson_encoder_cvbs.c b/drivers/gpu/drm/meson/meson_encoder_cvbs.c > index 8b26a0031cde0..9797e3f57e0be 100644 > --- a/drivers/gpu/drm/meson/meson_encoder_cvbs.c > +++ b/drivers/gpu/drm/meson/meson_encoder_cvbs.c [ ... ] > @@ -257,8 +260,9 @@ int meson_encoder_cvbs_probe(struct meson_drm *priv) > meson_encoder_cvbs->priv = priv; > > /* Encoder */ > - ret = drm_simple_encoder_init(priv->drm, &meson_encoder_cvbs->encoder, > - DRM_MODE_ENCODER_TVDAC); > + ret = drm_encoder_init(priv->drm, &meson_encoder_cvbs->encoder, > + &meson_encoder_cvbs_drm_encoder_funcs, > + DRM_MODE_ENCODER_TVDAC, NULL); [Severity: High] This is a pre-existing issue, but does tying the encoder cleanup directly to drm_encoder_cleanup() lead to a use-after-free during device unbind? If the driver is unbound while userspace holds an open DRM device reference, the platform unbind will drop the devm reference to the meson_encoder_cvbs bridge container. The bridge is then kept alive only by the attach reference. When userspace finally closes the file descriptor, drm_mode_config_cleanup() calls the encoder's destroy hook, which maps to drm_encoder_cleanup(). Inside drm_encoder_cleanup(), it drops the final bridge reference: drm_encoder.c:drm_encoder_cleanup() { ... list_for_each_entry_safe(bridge, next, &tmplist, chain_node) drm_bridge_detach(bridge); ... This frees the meson_encoder_cvbs container. However, drm_encoder_cleanup() continues to access the embedded encoder: drm_mode_object_unregister(dev, &encoder->base); kfree(encoder->name); list_del(&encoder->head); ... memset(encoder, 0, sizeof(*encoder)); } Could this sequence corrupt memory when it accesses the already freed encoder structure? > if (ret) > return dev_err_probe(priv->dev, ret, > "Failed to init CVBS encoder\n"); -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260531074627.8936-1-namanarora029 at gmail.com?part=4 From sashiko-bot at kernel.org Sun May 31 01:17:42 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sun, 31 May 2026 08:17:42 +0000 Subject: [PATCH 5/6] drm/meson: Open-code drm_simple_encoder_init() in encoder_hdmi In-Reply-To: <20260531074627.8936-2-namanarora029@gmail.com> References: <20260531074627.8936-2-namanarora029@gmail.com> Message-ID: <20260531081742.E7BDB1F00898@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: Pre-existing issues: - [High] The `drm_encoder` is embedded in a `devm`-allocated structure but is never explicitly removed from the DRM device's encoder list on unbind, leading to a use-after-free. - [High] Memory leak and dangling bridge pointer on probe error paths due to missing `drm_bridge_remove()`. -- commit b2fc3d021ba4d558d56f29caf06c9583a6e5c48f Author: Naman Arora drm/meson: Open-code drm_simple_encoder_init() in encoder_hdmi The helper drm_simple_encoder_init() is a thin wrapper around drm_encoder_init() with a simple destroy-only encoder funcs struct. Remove the dependency on drm_simple_kms_helper by open-coding the encoder initialization directly in the driver. > diff --git a/drivers/gpu/drm/meson/meson_encoder_hdmi.c b/drivers/gpu/drm/meson/meson_encoder_hdmi.c > index 55c0601df3c62..0da46af7371ea 100644 > --- a/drivers/gpu/drm/meson/meson_encoder_hdmi.c > +++ b/drivers/gpu/drm/meson/meson_encoder_hdmi.c [ ... ] > @@ -407,8 +410,9 @@ int meson_encoder_hdmi_probe(struct meson_drm *priv) > meson_encoder_hdmi->priv = priv; > > /* Encoder */ > - ret = drm_simple_encoder_init(priv->drm, &meson_encoder_hdmi->encoder, > - DRM_MODE_ENCODER_TMDS); > + ret = drm_encoder_init(priv->drm, &meson_encoder_hdmi->encoder, > + &meson_encoder_hdmi_drm_encoder_funcs, > + DRM_MODE_ENCODER_TMDS, NULL); [Severity: High] This is a pre-existing issue, but can this lead to a use-after-free when unbinding the device? The meson_encoder_hdmi structure, which embeds the drm_encoder, is allocated via devm_drm_bridge_alloc(). This means its memory is freed when the device unbinds. However, meson_encoder_hdmi_remove() fails to call drm_encoder_cleanup(), leaving the encoder linked in the DRM device's mode_config.encoder_list. If a userspace process keeps the DRM device file descriptor open, the DRM device outlives the driver binding. When the DRM device is finally destroyed, drm_mode_config_cleanup() iterates over the encoder_list and calls the destroy hook on the already freed encoder: drivers/gpu/drm/meson/meson_encoder_hdmi.c:meson_encoder_hdmi_remove() { if (priv->encoders[MESON_ENC_HDMI]) { meson_encoder_hdmi = priv->encoders[MESON_ENC_HDMI]; drm_bridge_remove(&meson_encoder_hdmi->bridge); ... } } Is it necessary to explicitly remove the encoder from the list during remove? > if (ret) { > dev_err_probe(priv->dev, ret, "Failed to init HDMI encoder\n"); > goto err_put_node; [Severity: High] This is a pre-existing issue, but does this error path leak the bridge and leave a dangling pointer in the global bridge list? Earlier in meson_encoder_hdmi_probe(), drm_bridge_add() increments the bridge's reference count and adds it to the global bridge_list: drm_bridge_add(&meson_encoder_hdmi->bridge); If the initialization steps fail here and jump to err_put_node, the code returns without calling drm_bridge_remove(). The devm cleanup only drops the initial reference taken by devm_drm_bridge_alloc(). This leaves the bridge in the global list indefinitely. Furthermore, because the parent meson_drm structure is subsequently freed by devm upon probe failure, meson_encoder_hdmi->priv becomes a dangling pointer. If another driver searches the list via of_drm_find_and_get_bridge() and calls an op on this bridge, it could dereference the freed priv structure, causing a use-after-free crash. Should drm_bridge_remove() be called on this and other error paths? > } -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260531074627.8936-1-namanarora029 at gmail.com?part=5 From sashiko-bot at kernel.org Sun May 31 01:25:46 2026 From: sashiko-bot at kernel.org (sashiko-bot at kernel.org) Date: Sun, 31 May 2026 08:25:46 +0000 Subject: [PATCH 6/6] drm/meson: Open-code drm_simple_encoder_init() in encoder_dsi In-Reply-To: <20260531074627.8936-3-namanarora029@gmail.com> References: <20260531074627.8936-3-namanarora029@gmail.com> Message-ID: <20260531082546.D98F81F00893@smtp.kernel.org> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: Pre-existing issues: - [High] The encoder atomic enable callback uses a sleeping polling function, which violates DRM atomic context rules. -- commit 7905b259e0fff97b6803c2360bf97ff0dc74dae2 Author: Naman Arora drm/meson: Open-code drm_simple_encoder_init() in encoder_dsi The helper drm_simple_encoder_init() is a thin wrapper around drm_encoder_init() with a simple destroy-only encoder funcs struct. Remove the dependency on drm_simple_kms_helper by open-coding the encoder initialization directly in the driver. > diff --git a/drivers/gpu/drm/meson/meson_encoder_dsi.c b/drivers/gpu/drm/meson/meson_encoder_dsi.c > index c1f4685073bbc..bbb5b65cb0d94 100644 > --- a/drivers/gpu/drm/meson/meson_encoder_dsi.c > +++ b/drivers/gpu/drm/meson/meson_encoder_dsi.c [ ... ] > @@ -99,6 +98,10 @@ static const struct drm_bridge_funcs meson_encoder_dsi_bridge_funcs = { > .atomic_reset = drm_atomic_helper_bridge_reset, > }; [Severity: High] This is a pre-existing issue, but does this bridge configuration cause a sleep in atomic context? Looking at the atomic_enable path for this bridge in drivers/gpu/drm/meson/meson_encoder_dsi.c: meson_encoder_dsi_atomic_enable() meson_encl_load_gamma() meson_encl_set_gamma_table() In drivers/gpu/drm/meson/meson_venc.c, meson_encl_set_gamma_table() calls: readl_relaxed_poll_timeout(..., 10, 10000) The readl_relaxed_poll_timeout macro expands to usleep_range when a non-zero delay is provided (like 10 here), which will cause the thread to sleep. Since bridge atomic_enable callbacks execute in an atomic context where sleeping is strictly forbidden, could this cause kernel warnings (might_sleep) or potential deadlocks if locks are held? Should this code path use a non-sleeping polling mechanism such as readl_relaxed_poll_timeout_atomic instead? > +static const struct drm_encoder_funcs meson_encoder_dsi_drm_encoder_funcs = { > + .destroy = drm_encoder_cleanup, > +}; > + > int meson_encoder_dsi_probe(struct meson_drm *priv) > { -- Sashiko AI review ? https://sashiko.dev/#/patchset/20260531074627.8936-1-namanarora029 at gmail.com?part=6 From daniel.lezcano at oss.qualcomm.com Sun May 31 01:41:33 2026 From: daniel.lezcano at oss.qualcomm.com (Daniel Lezcano) Date: Sun, 31 May 2026 10:41:33 +0200 Subject: [PATCH v2] firmware: meson: sm: add stub functions when CONFIG_MESON_SM is disabled In-Reply-To: <20260531-fix-missing-meson_sm-symbol-v2-1-1def8c3c169f@aliel.fr> References: <20260531-fix-missing-meson_sm-symbol-v2-1-1def8c3c169f@aliel.fr> Message-ID: <8c11a91b-cfbf-4b9c-a46f-907989bf8305@oss.qualcomm.com> On 5/31/26 09:51, Ronald Claveau via B4 Relay wrote: > From: Ronald Claveau > > After merging the thermal tree, linux-next build (arm_multi_v7 > defconfig) failed like this: > > arm-linux-gnueabihf-ld: drivers/thermal/amlogic_thermal.o: in function `amlogic_thermal_probe_sm': > /tmp/next/build/drivers/thermal/amlogic_thermal.c:196:(.text+0x2f4): undefined reference to `meson_sm_get' > arm-linux-gnueabihf-ld: /tmp/next/build/drivers/thermal/amlogic_thermal.c:205:(.text+0x320): undefined reference to `meson_sm_get_thermal_calib' > > Add inline stub implementations of meson_sm_get() and > meson_sm_get_thermal_calib() behind an #else guard so that drivers > including this header can be compiled without CONFIG_MESON_SM . > > Fixes: b21d88de6918 ("thermal/drivers/amlogic: Add support for secure monitor calibration readout") > Closes: https://lore.kernel.org/oe-kbuild-all/202605291530.en7aGn7w-lkp at intel.com/ > Reported-by: Mark Brown > Reported-by: kernel test robot > Signed-off-by: Ronald Claveau > --- > Changes in v2: > - Replace #ifdef CONFIG_MESON_SM by #if IS_ENABLED(CONFIG_MESON_SM) > to cover builtin and module in config. > - Add missing trailers for kernel test robot. > - Link to v1: https://lore.kernel.org/r/20260530-fix-missing-meson_sm-symbol-v1-1-3fb672b989d4 at aliel.fr > --- > include/linux/firmware/meson/meson_sm.h | 17 +++++++++++++++++ > 1 file changed, 17 insertions(+) > > diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/firmware/meson/meson_sm.h > index 3ebc2bd9a9760..baecdaf263f41 100644 > --- a/include/linux/firmware/meson/meson_sm.h > +++ b/include/linux/firmware/meson/meson_sm.h > @@ -27,8 +27,25 @@ int meson_sm_call_write(struct meson_sm_firmware *fw, void *buffer, > int meson_sm_call_read(struct meson_sm_firmware *fw, void *buffer, > unsigned int bsize, unsigned int cmd_index, u32 arg0, > u32 arg1, u32 arg2, u32 arg3, u32 arg4); > + > +#if IS_ENABLED(CONFIG_MESON_SM) > + > struct meson_sm_firmware *meson_sm_get(struct device_node *firmware_node); > int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, u32 *trim_info, > u32 tsensor_id); Do you really want to compile meson_sm as a module ? From linux-kernel-dev at aliel.fr Sun May 31 10:49:55 2026 From: linux-kernel-dev at aliel.fr (Ronald Claveau) Date: Sun, 31 May 2026 19:49:55 +0200 Subject: [PATCH v2] firmware: meson: sm: add stub functions when CONFIG_MESON_SM is disabled In-Reply-To: <8c11a91b-cfbf-4b9c-a46f-907989bf8305@oss.qualcomm.com> References: <20260531-fix-missing-meson_sm-symbol-v2-1-1def8c3c169f@aliel.fr> <8c11a91b-cfbf-4b9c-a46f-907989bf8305@oss.qualcomm.com> Message-ID: <12d74340-0213-4f7e-a494-10ce147844ad@aliel.fr> On 5/31/26 10:41 AM, Daniel Lezcano wrote: > On 5/31/26 09:51, Ronald Claveau via B4 Relay wrote: >> From: Ronald Claveau >> >> After merging the thermal tree, linux-next build (arm_multi_v7 >> defconfig) failed like this: >> >> arm-linux-gnueabihf-ld: drivers/thermal/amlogic_thermal.o: in function >> `amlogic_thermal_probe_sm': >> /tmp/next/build/drivers/thermal/amlogic_thermal.c:196:(.text+0x2f4): >> undefined reference to `meson_sm_get' >> arm-linux-gnueabihf-ld: /tmp/next/build/drivers/thermal/ >> amlogic_thermal.c:205:(.text+0x320): undefined reference to >> `meson_sm_get_thermal_calib' >> >> Add inline stub implementations of meson_sm_get() and >> meson_sm_get_thermal_calib() behind an #else guard so that drivers >> including this header can be compiled without CONFIG_MESON_SM . >> >> Fixes: b21d88de6918 ("thermal/drivers/amlogic: Add support for secure >> monitor calibration readout") >> Closes: https://lore.kernel.org/oe-kbuild-all/202605291530.en7aGn7w- >> lkp at intel.com/ >> Reported-by: Mark Brown >> Reported-by: kernel test robot >> Signed-off-by: Ronald Claveau >> --- >> Changes in v2: >> - Replace #ifdef CONFIG_MESON_SM by #if IS_ENABLED(CONFIG_MESON_SM) >> ?? to cover builtin and module in config. >> - Add missing trailers for kernel test robot. >> - Link to v1: https://lore.kernel.org/r/20260530-fix-missing-meson_sm- >> symbol-v1-1-3fb672b989d4 at aliel.fr >> --- >> ? include/linux/firmware/meson/meson_sm.h | 17 +++++++++++++++++ >> ? 1 file changed, 17 insertions(+) >> >> diff --git a/include/linux/firmware/meson/meson_sm.h b/include/linux/ >> firmware/meson/meson_sm.h >> index 3ebc2bd9a9760..baecdaf263f41 100644 >> --- a/include/linux/firmware/meson/meson_sm.h >> +++ b/include/linux/firmware/meson/meson_sm.h >> @@ -27,8 +27,25 @@ int meson_sm_call_write(struct meson_sm_firmware >> *fw, void *buffer, >> ? int meson_sm_call_read(struct meson_sm_firmware *fw, void *buffer, >> ???????????????? unsigned int bsize, unsigned int cmd_index, u32 arg0, >> ???????????????? u32 arg1, u32 arg2, u32 arg3, u32 arg4); >> + >> +#if IS_ENABLED(CONFIG_MESON_SM) >> + >> ? struct meson_sm_firmware *meson_sm_get(struct device_node >> *firmware_node); >> ? int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, u32 >> *trim_info, >> ???????????????????? u32 tsensor_id); > > Do you really want to compile meson_sm as a module ? > > > I don't want, but if I send a patch to change tristate to bool, it will raise a warning because 'm' is not valid. If it is accepted to ignore this warning, I can send another patch. -- Best regards, Ronald From daniel.lezcano at oss.qualcomm.com Sun May 31 12:58:17 2026 From: daniel.lezcano at oss.qualcomm.com (Daniel Lezcano) Date: Sun, 31 May 2026 21:58:17 +0200 Subject: [PATCH v2] firmware: meson: sm: add stub functions when CONFIG_MESON_SM is disabled In-Reply-To: <12d74340-0213-4f7e-a494-10ce147844ad@aliel.fr> References: <20260531-fix-missing-meson_sm-symbol-v2-1-1def8c3c169f@aliel.fr> <8c11a91b-cfbf-4b9c-a46f-907989bf8305@oss.qualcomm.com> <12d74340-0213-4f7e-a494-10ce147844ad@aliel.fr> Message-ID: <0c5033f7-1535-49e8-a502-426e54333496@oss.qualcomm.com> On 5/31/26 19:49, Ronald Claveau wrote: > On 5/31/26 10:41 AM, Daniel Lezcano wrote: >> On 5/31/26 09:51, Ronald Claveau via B4 Relay wrote: >>> From: Ronald Claveau [ ... ] >>> +#if IS_ENABLED(CONFIG_MESON_SM) >>> + >>> ? struct meson_sm_firmware *meson_sm_get(struct device_node >>> *firmware_node); >>> ? int meson_sm_get_thermal_calib(struct meson_sm_firmware *fw, u32 >>> *trim_info, >>> ???????????????????? u32 tsensor_id); >> >> Do you really want to compile meson_sm as a module ? >> >> >> > > I don't want, but if I send a patch to change tristate to bool, it will > raise a warning because 'm' is not valid. > If it is accepted to ignore this warning, I can send another patch. Sorry, I don't get your point :/ 'm' is a config option, make menuconfig should set it to yes or not set. Compiling the firmware as a module means it must be loaded before the amlogic thermal driver, right ? Where is the dependency declared in the module ? If the sm_meson is in the platform, it should be selected as part of the platform's component. No need to have an option for that, no ?