* Re: [PATCH v3 00/13] This series adds support for the MediaTek MT7927 (Filogic 380) combo
From: Sean Wang @ 2026-03-26 5:56 UTC (permalink / raw)
To: Javier Tia
Cc: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
Ming Yen Hsieh, Deren Wu, linux-wireless, linux-kernel,
linux-arm-kernel, linux-mediatek, Marcin FM,
Cristian-Florin Radoi, George Salukvadze, Evgeny Kapusta,
Samu Toljamo, Ariel Rosenfeld, Chapuis Dario,
Thibaut François, 张旭涵
In-Reply-To: <20260325-mt7927-wifi-support-v2-v3-0-5ca66c97a755@jetm.me>
Hi, Javier
Sorry for the late reply. This version already looks better than v1
overall, and the patch order is cleaner. I still think a few areas
could be refined further, as this would also be a good opportunity to
refactor the code a bit so mt7927 support fits more naturally and
future chips are easier to handle as well.
On Wed, Mar 25, 2026 at 5:11 PM Javier Tia <floss@jetm.me> wrote:
>
> WiFi 7 + BT 5.4 module to the mt7925 driver. The MT7927 uses PCI ID
> 14c3:7927 (some hardware reports 14c3:6639) and shares the mt7925
> firmware interface but requires different DMA ring layout, IRQ mapping,
> chip initialization, and power management handling.
>
> Tested hardware:
> - ASUS ROG Crosshair X870E Hero (BT 0489:e13a, WiFi 14c3:6639)
> - ASUS ProArt X870E-Creator WiFi (BT 0489:e13a / 13d3:3588, WiFi 14c3:6639)
> - ASUS ROG Strix X870-I (WiFi 14c3:7927)
> - ASUS ROG Strix X870-F Gaming WiFi (BT 0489:e13a, WiFi 14c3:7927)
> - ASUS ROG Strix X870E-E (BT 13d3:3588, WiFi 14c3:7927)
> - Gigabyte X870E Aorus Master X3D (BT 0489:e10f, WiFi 14c3:7927)
> - Gigabyte Z790 AORUS MASTER X (BT 0489:e10f, WiFi 14c3:7927)
> - Gigabyte Z790 AORUS ELITE X WiFi7 (BT 0489:e10f, WiFi 14c3:7927)
> - MSI MEG X870E ACE MAX (BT 0489:e110, WiFi 14c3:7927)
> - Lenovo Legion Pro 7 16ARX9 (BT 0489:e0fa, WiFi 14c3:7927)
> - Lenovo Legion Pro 7 16AFR10H (BT 0489:e0fa, WiFi 14c3:7927)
> - TP-Link Archer TBE550E PCIe (BT 0489:e116, WiFi 14c3:7927)
> - EDUP EP-MT7927BE M.2 card (WiFi 14c3:7927)
> - Foxconn/Azurewave M.2 modules (WiFi 14c3:6639)
> - AMD RZ738 reference design (WiFi 14c3:0738)
>
> Tested on Arch Linux, CachyOS, EndeavourOS, Fedora (Bazzite), NixOS,
> openSUSE Tumbleweed, and Ubuntu across kernels 6.13-6.19.
>
> What works:
> - WiFi 7 with EHT 320MHz on 2.4/5/6 GHz bands
> - 320MHz data path verified at 841 Mbps (iperf3 -t30 -P8)
> - PCIe initialization with CBTOP remap and MT7927-specific DMA layout
> - System suspend/resume (S3)
> - DBDC (dual-band concurrent) mode
> - Explicit band_idx assignment for stable 5GHz/6GHz operation
> - ASPM and runtime PM disabled for MT7927 (see below)
>
> Known limitations (planned as follow-up series):
> - Runtime PM: disabled for MT7927 because the combo chip shares a
> CONNINFRA power domain between WiFi (PCIe) and BT (USB).
> SET_OWN/CLR_OWN transitions on the LPCTL register crash the BT
> firmware, requiring a full power cycle to recover. PM enablement
> will be addressed once safe power state transitions are determined.
> - mac_reset returns -EOPNOTSUPP (DMA recovery not yet implemented)
> - MLO (Multi-Link Operation): tested working on 5GHz+2.4GHz STR
> (776 Mbps) but requires additional patches for link lifetime
> and error handling. Sean Wang's series [1] addresses these;
> MLO support will be submitted as a follow-up on top of that.
> - TX retransmissions: elevated retry rate on all bands, firmware-side
> rate adaptation issue not addressable in the driver.
>
> [1] https://lore.kernel.org/linux-wireless/20260306232238.2039675-1-sean.wang@kernel.org/
>
> Patches 1-5 add generic 320MHz EHT support (no MT7927 references).
> Patches 6-7 introduce MT7927 chip ID helpers and firmware paths.
> Patch 8 adds per-chip IRQ map handling.
This part patch 1-8 looks good overall. My only concern was a possible
regression on mt7925, but now that the incorrect mt7925 320 MHz
support has been fixed in the version, this should be fine.
> Patch 9 introduces mt792x_dma_config struct for chip-specific DMA.
> Patch 10 combines CBTOP remap, chip init, DBDC, CNM, and mac_reset.
> Patch 11 adds mt7925_band_idx() helper for stable 5/6 GHz operation.
I am still thinking a bit more about patches 9-11. My current
preference is to introduce the generic layer first, and then migrate
the mt7925 and mt7927-specific parts on top of it.
I will handle this part on my side, since I want to carefully compare
the vendor driver with your changes first, and make sure the mt7925
side is solid before moving the mt7927-related changes forward. For
the mt7927-related work, I will make sure your contribution, as well
as the work from the other volunteers, is properly preserved when I
carry this forward.
> Patch 12 disables ASPM and runtime PM for MT7927.
> Patch 13 enables the MT7927 PCI device table entries.
>
> The WiFi firmware path and filename in linux-firmware have not been
> finalized yet. The driver currently requests mediatek/mt6639/ (the
> mobile SoC codename), but this may change based on the linux-firmware
> review.
Similar to the BT side, I would prefer to have a dedicated Linux
firmware for mt7927 WiFi. I am not sure yet whether the current
changes will work seamlessly with that, so we can revisit it later and
see what further adjustments are needed.
>
> Changes since v2 (suggested by Sean Wang):
> - Fixed is_320mhz_supported() to check for MT7927 only, not the
> entire mt7925 family. MT7925 does not support 320MHz (patch 5).
> - Dropped phy_cap_info[7] 320MHz additions (NON_OFDMA_UL_MU_MIMO
> and MU_BEAMFORMER) to keep capabilities conservative (patch 5).
> - Disabled runtime PM for MT7927 (patch 12). The combo chip shares a
> CONNINFRA power domain between WiFi and BT; SET_OWN/CLR_OWN
> transitions crash BT firmware. Discovered via user reports of BT
> lockups after enabling power_save=1 (Reported-by: Nitin Gurram).
> - Added tested hardware: MSI MEG X870E ACE MAX, Gigabyte Z790 AORUS
> ELITE X WiFi7, Lenovo Legion Pro 7 16AFR10H.
>
> Changes since v1 (suggested by Sean Wang):
> - Reorganized from 18 patches into 13 across 8 logical groups
> - Common 320MHz patches first, chip-specific changes later
> - Introduced mt792x_dma_config struct to reuse mt7925_dma_init()
> instead of duplicating as mt7927_dma_init()
> - Replaced is_mt7927() with is_320mhz_supported() in common patches
> - Added mt7925_band_idx() helper replacing scattered if/else patterns
> - Renamed MT7927-specific registers with MT7927_ prefix
> - Added PCI ID 0x0738 for AMD RZ738 hardware
> - Moved GLO_CFG_EXT1 register address into dma_config struct to
> eliminate is_mt7927() from shared mt792x_dma.c
>
> Link to v2: https://lore.kernel.org/linux-wireless/20260319-mt7927-wifi-support-v2-v2-0-d627a7fad70d@jetm.me/
> Link to v1: https://lore.kernel.org/linux-wireless/20260306-mt7927-wifi-support-v1-0-c77e7445511d@jetm.me/
>
> Assisted-by: Claude (Anthropic)
> Signed-off-by: Javier Tia <floss@jetm.me>
> ---
> Javier Tia (13):
> wifi: mt76: mt7925: fix stale pointer comparisons in change_vif_links
> wifi: mt76: mt7925: add 320MHz bandwidth to bss_rlm_tlv
> wifi: mt76: mt7925: handle 320MHz bandwidth in RXV and TXS
> wifi: mt76: mt7925: populate EHT 320MHz MCS map in sta_rec
> wifi: mt76: mt7925: advertise EHT 320MHz capabilities for 6GHz band
> wifi: mt76: mt7925: add MT7927 chip ID helpers
> wifi: mt76: mt7925: add MT7927 firmware paths
> wifi: mt76: mt7925: use irq_map for chip-specific interrupt handling
> wifi: mt76: mt7925: add chip-specific DMA configuration
> wifi: mt76: mt7925: add MT7927 hardware initialization
> wifi: mt76: mt7925: fix band_idx for stable 5GHz/6GHz operation
> wifi: mt76: mt7925: disable ASPM and runtime PM for MT7927
> wifi: mt76: mt7925: enable MT7927 PCI device IDs
>
> drivers/net/wireless/mediatek/mt76/mt76_connac.h | 13 +-
> drivers/net/wireless/mediatek/mt76/mt7925/init.c | 19 +-
> drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 9 +
> drivers/net/wireless/mediatek/mt76/mt7925/main.c | 61 ++++-
> drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 55 +++-
> drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h | 7 +
> drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 277 +++++++++++++++++++--
> .../net/wireless/mediatek/mt76/mt7925/pci_mac.c | 14 +-
> .../net/wireless/mediatek/mt76/mt7925/pci_mcu.c | 20 +-
> drivers/net/wireless/mediatek/mt76/mt792x.h | 27 ++
> drivers/net/wireless/mediatek/mt76/mt792x_dma.c | 68 ++---
> drivers/net/wireless/mediatek/mt76/mt792x_regs.h | 33 +++
> 12 files changed, 528 insertions(+), 75 deletions(-)
> ---
> base-commit: 9ac76f3d0bb2940db3a9684d596b9c8f301ef315
> change-id: 20260319-mt7927-wifi-support-v2-e89d779b28f4
>
> Best regards,
> --
> Javier Tia <floss@jetm.me>
>
>
^ permalink raw reply
* [PATCH] ASoC: imx-rpmsg: Add DSD format support with dynamic DAI format switching
From: Chancel Liu @ 2026-03-26 5:56 UTC (permalink / raw)
To: festevam, nicoleotsuka, lgirdwood, broonie, perex, tiwai,
Frank.Li, s.hauer, kernel, linux-sound, linuxppc-dev, imx,
linux-arm-kernel, linux-kernel
Add hw_params callback to dynamically switch DAI format between I2S
and PDM based on audio stream format. When DSD formats are detected,
the DAI format is switched to PDM mode.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/fsl/imx-rpmsg.c | 48 +++++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c
index 76a8e68c1b62..40e0043cfe15 100644
--- a/sound/soc/fsl/imx-rpmsg.c
+++ b/sound/soc/fsl/imx-rpmsg.c
@@ -30,6 +30,53 @@ static const struct snd_soc_dapm_widget imx_rpmsg_dapm_widgets[] = {
SND_SOC_DAPM_MIC("Main MIC", NULL),
};
+static int imx_rpmsg_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ snd_pcm_format_t format = params_format(params);
+ struct device *dev = rtd->card->dev;
+ unsigned int fmt = rtd->dai_link->dai_fmt;
+ bool format_is_dsd = false;
+ int ret;
+
+ switch (format) {
+ case SNDRV_PCM_FORMAT_DSD_U8:
+ case SNDRV_PCM_FORMAT_DSD_U16_LE:
+ case SNDRV_PCM_FORMAT_DSD_U16_BE:
+ case SNDRV_PCM_FORMAT_DSD_U32_LE:
+ case SNDRV_PCM_FORMAT_DSD_U32_BE:
+ format_is_dsd = true;
+ break;
+ default:
+ format_is_dsd = false;
+ break;
+ }
+
+ if (format_is_dsd)
+ fmt = (rtd->dai_link->dai_fmt & ~SND_SOC_DAIFMT_FORMAT_MASK) |
+ SND_SOC_DAIFMT_PDM;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret && ret != -ENOTSUPP) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops imx_rpmsg_ops = {
+ .hw_params = imx_rpmsg_hw_params,
+};
+
static int imx_rpmsg_late_probe(struct snd_soc_card *card)
{
struct imx_rpmsg *data = snd_soc_card_get_drvdata(card);
@@ -135,6 +182,7 @@ static int imx_rpmsg_probe(struct platform_device *pdev)
data->dai.dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBC_CFC;
+ data->dai.ops = &imx_rpmsg_ops;
/*
* i.MX rpmsg sound cards work on codec slave mode. MCLK will be
--
2.50.1
^ permalink raw reply related
* Re: [PATCH v2 0/5] powervr: MT8173 GPU support
From: Icenowy Zheng @ 2026-03-26 5:56 UTC (permalink / raw)
To: Chen-Yu Tsai
Cc: Stephen Boyd, Matthias Brugger, AngeloGioacchino Del Regno,
Frank Binns, Matt Coster, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, linux-clk,
devicetree, linux-mediatek, dri-devel, linux-arm-kernel,
linux-kernel
In-Reply-To: <42fb6b7dfc6a9d66649e0a7040cdafc1289ddf63.camel@icenowy.me>
在 2026-03-25三的 17:11 +0800,Icenowy Zheng写道:
> 在 2026-03-25三的 16:30 +0800,Icenowy Zheng写道:
> > 在 2026-03-25三的 16:08 +0800,Chen-Yu Tsai写道:
> > > On Wed, Mar 25, 2026 at 4:04 PM Icenowy Zheng <uwu@icenowy.me>
> > > wrote:
> > > >
> > > > 在 2026-03-25三的 15:19 +0800,Chen-Yu Tsai写道:
> > > > > Hi everyone,
> > > > >
> > > > > This is v2 of my MT8173 PowerVR GPU support series.
> > > > >
> > > > > Changes since v1:
> > > > > - Adapted to changed DT bindings
> > > > > - Dropped driver change
> > > > > - Use same power domain for "a" and "b" GPU power domains
> > > > >
> > > > > This update was requested by Icenowy.
> > > > >
> > > > >
> > > > > This series enables the PowerVR GPU found in the MT8173 SoC,
> > > > > found in
> > > > > some Chromebooks.
> > > > >
> > > > > This version is different from the initial powervr driver
> > > > > submission
> > > > > [1]
> > > > > in that it splits out the GPU glue layer support out of the
> > > > > powervr
> > > > > driver and into a separate clock and power domain driver. The
> > > > > glue
> > > > > code
> > > > > is otherwise the same, and also the same as found in the
> > > > > ChromeOS
> > > > > kernels, with some extra comments and macro names added where
> > > > > possible.
> > > > >
> > > > > Patch 1 adds a binding for the glue layer, called mfgtop. The
> > > > > glue
> > > > > layer
> > > > > contains clock and power controls for the GPU.
> > > > >
> > > > > Patch 2 adds a driver for the glue layer.
> > > > >
> > > > > Patch 3 adds an entry for the MT8173 GPU and 6XT series to
> > > > > the
> > > > > PowerVR
> > > > > binding.
> > > > >
> > > > > Patch 4 adds an entry for the PowerVR 6XT series GPU to the
> > > > > driver.
> > > > >
> > > > > Patch 5 corrects the clock for the GPU (called MFG) power
> > > > > domain.
> > > > >
> > > > > Patch 6 adds device nodes for the GPU and glue layer to the
> > > > > MT8173
> > > > > dtsi
> > > > > file.
> > > > >
> > > > > Patch 2 and 6 depend on patch 1 to build. I suppose some
> > > > > common
> > > > > immutable tree would be needed from the MediaTek maintainers.
> > > > >
> > > > > The kernel driver successfully probes the hardware and loads
> > > > > the
> > > > > "rogue_4.40.2.51_v1.fw" firmware provided by Imagination
> > > > > Technologies
> > > > > [2].
> > > > > Userspace was tested with Mesa 24.0.8 from Debian Trixie
> > > > > rebuilt
> > > > > with
> > > > > the powervr vulkan driver enabled. `vulkaninfo` gives some
> > > > > information
> > > > > about the GPU (attached at the end), but running the
> > > > > `triangle`
> > > > > example
> > > > > from the Sascha Willems demos [3] with -DUSE_D2D_WSI=ON as
> > > > > recommended [4]
> > > > > failed with:
> > > > >
> > > > > Can't find a display and a display mode!
> > > >
> > > > I think when using D2D the demos want width and height to be
> > > > explicitly
> > > > specified, otherwise it seems to hardcode 1280x720.
> > > >
> > > > If you're using an elm, could you try to add `-w 1920 -h 1080`
> > > > or
> > > > for
> > > > hana `-w 1366 -h 768` ?
> > >
> > > I only did the basic `vulkaninfo` test this time around. To do
> > > anything
> > > interesting probably requires the Mesa 26.1 release.
> > >
> > > PVR_I_WANT_A_BROKEN_VULKAN_DRIVER=1 \
> > > vkmark --winsys kms -D b81f54f8568deb0fb70a6a1ed845b65d
> > >
> > > just reports "Error: Device specified by uuid is not available"
> >
> > I am very sorry to tell you that, when I run Sascha's demo with
> > Mesa
> > main, I got GPU lost immediately...
> >
> > ```
> > [ 441.509433] powervr 13000000.gpu: [drm] *ERROR* GPU device lost
> > ```
The device lost message seems to be some bug of the open source KMD or
the firmware.
When I disable runtime power management of the 13000000.gpu device,
this message does not appear, instead the following message appears and
the GPU can continue to accept jobs:
```
powervr 13000000.gpu: [drm] Received unknown FWCCB command 2abc0070
```
>
> However, Zink on PowerVR works, and on Lichee Pi 4A I also got
> `VK_ERROR_DEVICE_LOST` when running Sascha's demos (although the
> kernel
> does not report device lost).
>
> I bet it's a regression on the VK_KHR_display code.
I'm now sure that it's a regression, it's now tracked at [1].
[1] https://gitlab.freedesktop.org/mesa/mesa/-/issues/15161
>
> Thanks,
> Icenowy
>
> >
> > Icenowy
> >
> > >
> > > This is with Mesa 26.0.2 packages from Debian testing. At least
> > > now
> > > have the powervr vulkan driver enabled by default, so I don't
> > > have
> > > to rebuild the packages again.
> > >
> > >
> > > ChenYu
> > >
> > > > Thanks
> > > > Icenowy
> > > >
> > > > >
> > > > > Same program worked correctly on a BeaglePlay and displayed a
> > > > > color
> > > > > gradient triangle. Not sure what went wrong here.
> > > > >
> > > > > Anyway, please have a look and test.
> > > > >
> > > > >
> > > > > Thanks
> > > > > ChenYu
> > > > >
> > > > > [1]
> > > > > https://lore.kernel.org/dri-devel/20220815165156.118212-2-sarah.walker@imgtec.com/
> > > > > [2]
> > > > > https://gitlab.freedesktop.org/imagination/linux-firmware/-/tree/powervr
> > > > > [3] https://github.com/SaschaWillems/Vulkan
> > > > > [4]
> > > > > https://lore.kernel.org/dri-devel/f2b2671e-5acc-4dec-9c2e-3c9cd2e1f19e@imgtec.com/
> > > > >
> > > > > Chen-Yu Tsai (5):
> > > > > dt-bindings: clock: mediatek: Add mt8173 mfgtop
> > > > > clk: mediatek: Add mt8173-mfgtop driver
> > > > > dt-bindings: gpu: powervr-rogue: Add MediaTek MT8173 GPU
> > > > > arm64: dts: mediatek: mt8173: Fix MFG_ASYNC power domain
> > > > > clock
> > > > > arm64: dts: mediatek: mt8173: Add GPU device nodes
> > > > >
> > > > > .../clock/mediatek,mt8173-mfgtop.yaml | 70 +++++
> > > > > .../bindings/gpu/img,powervr-rogue.yaml | 1 +
> > > > > arch/arm64/boot/dts/mediatek/mt8173.dtsi | 33 ++-
> > > > > drivers/clk/mediatek/Kconfig | 9 +
> > > > > drivers/clk/mediatek/Makefile | 1 +
> > > > > drivers/clk/mediatek/clk-mt8173-mfgtop.c | 243
> > > > > ++++++++++++++++++
> > > > > include/dt-bindings/clock/mt8173-clk.h | 7 +
> > > > > 7 files changed, 363 insertions(+), 1 deletion(-)
> > > > > create mode 100644
> > > > > Documentation/devicetree/bindings/clock/mediatek,mt8173-
> > > > > mfgtop.yaml
> > > > > create mode 100644 drivers/clk/mediatek/clk-mt8173-mfgtop.c
^ permalink raw reply
* Re: [PATCH v5 2/8] ARM: dts: aspeed: yosemite5: Remove ambiguous power monitor DTS nodes
From: Andrew Jeffery @ 2026-03-26 6:07 UTC (permalink / raw)
To: Kevin Tung
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Joel Stanley,
devicetree, linux-arm-kernel, linux-aspeed, linux-kernel,
Amithash Prasasd, Kevin Tung, Ken Chen, Leo Yang, Jackson Liu,
Daniel Hsu
In-Reply-To: <CABh9gBd3b9TB1-s=Gq1q-M8bX+4UioXePUF0DPVrU2N3N8S9yw@mail.gmail.com>
Hi Kevin,
Sorry for the delay.
On Mon, 2026-03-09 at 11:41 -0700, Kevin Tung wrote:
> On Tue, Mar 3, 2026 at 6:41 PM Andrew Jeffery
> <andrew@codeconstruct.com.au> wrote:
> >
> > Hi Kevin,
> >
> > Sorry for the patchy replies so far, but this series bothers me and
> > other priorities keep bumping it down the list.
> >
> > On Mon, 2026-02-23 at 19:17 +0800, Kevin Tung wrote:
> > > Two different power monitor devices, using different drivers, reuse
> > > I2C addresses 0x40 and 0x45 on bus 10 across Yosemite5 board variants.
> > > Defining these devices statically in the DTS can lead to incorrect
> > > driver binding on newer boards when the wrong device is instantiated.
> >
> > There are effective methods of maintaining devicetrees for variants.
> > Why are we choosing to remove information about the platform rather
> > than use existing techniques to properly describe them?
> >
> Hi Andrew,
>
> This is due to hardware design changes during earlier development
> stages, and the fix is expected to remain stable as the design has
> matured.
> Could you guide me on the best way to maintain devicetrees for
> variants? Thank you :)
My expectation is your platforms move through several design phases
prior to (mass?) production. My suspicion is that you have sent a
devicetree for the pre-production design phases, and you're trying to
evolve that one devicetree to match the design for whatever current
phase you're in.
So, ideally: Send a devicetree only for the finalised design. Don't
send devicetrees for pre-production designs.
If you feel you can't do that for some reason, an alternative is to
have a separate .dts file for each phase in the design process.
This may sound tedious but it doesn't have to be a burden to maintain.
For instance, you can use one or more .dtsi files to describe the
common components and relationships for your platform. These .dtsi
files are then #included into .dts files as usual. Often .dtsi files
are used to isolate different hardware scopes (SoC vs board, for
instance), but we're not limited to that, we can use them for the
purpose outlined above too.
If there are only (very) minor differences, there's also the option of
#including another .dts file. From there you can adjust properties or
even delete nodes where it makes sense. For example, we maintain a .dts
file for the latest revision of the AST2600-EVB, but we also have a
separate .dts for the A1 revision with a different regulator setup:
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/arm/boot/dts/aspeed/aspeed-ast2600-evb.dts?h=v7.0-rc5
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/arm/boot/dts/aspeed/aspeed-ast2600-evb-a1.dts?h=v7.0-rc5
Any of these are better options than this current approach of trying to
justify incompatible changes against unclear design boundaries.
Andrew
^ permalink raw reply
* Re: [PATCH v3 05/13] wifi: mt76: mt7925: advertise EHT 320MHz capabilities for 6GHz band
From: Sean Wang @ 2026-03-26 6:08 UTC (permalink / raw)
To: Javier Tia
Cc: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
Ming Yen Hsieh, Deren Wu, linux-wireless, linux-kernel,
linux-arm-kernel, linux-mediatek, Marcin FM,
Cristian-Florin Radoi, George Salukvadze, Evgeny Kapusta,
Samu Toljamo, Ariel Rosenfeld, Chapuis Dario,
Thibaut François, 张旭涵
In-Reply-To: <20260325-mt7927-wifi-support-v2-v3-5-5ca66c97a755@jetm.me>
Hi, Javier
On Wed, Mar 25, 2026 at 5:14 PM Javier Tia <floss@jetm.me> wrote:
>
> mt7925_init_eht_caps() only populates EHT MCS/NSS maps for BW <= 80
> and BW = 160, but never sets BW = 320. This means iw phy shows no
> 320MHz MCS map entries even though the hardware supports 320MHz
> operation in the 6GHz band.
>
> Add the missing 320MHz capability bits for 6GHz:
> - PHY_CAP0: IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ
> - PHY_CAP1: beamformee SS for 320MHz
> - PHY_CAP2: sounding dimensions for 320MHz
> - PHY_CAP6: MCS15 support for 320MHz width
> - MCS/NSS: populate bw._320 maps for 6GHz band
>
> Introduce is_320mhz_supported() to gate 320MHz on MT7927 only, since
> MT7925 does not support 320MHz operation.
>
> Tested-by: Marcin FM <marcin@lgic.pl>
> Tested-by: Cristian-Florin Radoi <radoi.chris@gmail.com>
> Tested-by: George Salukvadze <giosal90@gmail.com>
> Tested-by: Evgeny Kapusta <3193631@gmail.com>
> Tested-by: Samu Toljamo <samu.toljamo@gmail.com>
> Tested-by: Ariel Rosenfeld <ariel.rosenfeld.750@gmail.com>
> Tested-by: Chapuis Dario <chapuisdario4@gmail.com>
> Tested-by: Thibaut François <tibo@humeurlibre.fr>
> Tested-by: 张旭涵 <Loong.0x00@gmail.com>
> Signed-off-by: Javier Tia <floss@jetm.me>
> ---
> drivers/net/wireless/mediatek/mt76/mt76_connac.h | 5 +++++
> drivers/net/wireless/mediatek/mt76/mt7925/main.c | 22 +++++++++++++++++++++-
> 2 files changed, 26 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
> index 813d61bffc2c..3785fbf5ac99 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
> @@ -177,6 +177,11 @@ static inline bool is_mt7925(struct mt76_dev *dev)
> return mt76_chip(dev) == 0x7925;
> }
>
> +static inline bool is_320mhz_supported(struct mt76_dev *dev)
> +{
> + return is_mt7927(dev);
This looks like it would cause a build error since is_mt7927() is not
introduced until patch 6.
> +}
> +
> static inline bool is_mt7920(struct mt76_dev *dev)
> {
> return mt76_chip(dev) == 0x7920;
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> index f128a198f81d..cfce851a94e2 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> @@ -183,6 +183,10 @@ mt7925_init_eht_caps(struct mt792x_phy *phy, enum nl80211_band band,
> IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMER |
> IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMEE;
>
> + if (band == NL80211_BAND_6GHZ && is_320mhz_supported(&phy->dev->mt76))
> + eht_cap_elem->phy_cap_info[0] |=
> + IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ;
> +
> eht_cap_elem->phy_cap_info[0] |=
> u8_encode_bits(u8_get_bits(sts - 1, BIT(0)),
> IEEE80211_EHT_PHY_CAP0_BEAMFORMEE_SS_80MHZ_MASK);
> @@ -193,10 +197,20 @@ mt7925_init_eht_caps(struct mt792x_phy *phy, enum nl80211_band band,
> u8_encode_bits(sts - 1,
> IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_160MHZ_MASK);
>
> + if (band == NL80211_BAND_6GHZ && is_320mhz_supported(&phy->dev->mt76))
> + eht_cap_elem->phy_cap_info[1] |=
> + u8_encode_bits(sts - 1,
> + IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_320MHZ_MASK);
> +
> eht_cap_elem->phy_cap_info[2] =
> u8_encode_bits(sts - 1, IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_80MHZ_MASK) |
> u8_encode_bits(sts - 1, IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_160MHZ_MASK);
>
> + if (band == NL80211_BAND_6GHZ && is_320mhz_supported(&phy->dev->mt76))
> + eht_cap_elem->phy_cap_info[2] |=
> + u8_encode_bits(sts - 1,
> + IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_320MHZ_MASK);
> +
> eht_cap_elem->phy_cap_info[3] =
> IEEE80211_EHT_PHY_CAP3_NG_16_SU_FEEDBACK |
> IEEE80211_EHT_PHY_CAP3_NG_16_MU_FEEDBACK |
> @@ -217,7 +231,8 @@ mt7925_init_eht_caps(struct mt792x_phy *phy, enum nl80211_band band,
> u8_encode_bits(u8_get_bits(0x11, GENMASK(1, 0)),
> IEEE80211_EHT_PHY_CAP5_MAX_NUM_SUPP_EHT_LTF_MASK);
>
> - val = width == NL80211_CHAN_WIDTH_160 ? 0x7 :
> + val = width == NL80211_CHAN_WIDTH_320 ? 0xf :
> + width == NL80211_CHAN_WIDTH_160 ? 0x7 :
> width == NL80211_CHAN_WIDTH_80 ? 0x3 : 0x1;
> eht_cap_elem->phy_cap_info[6] =
> u8_encode_bits(u8_get_bits(0x11, GENMASK(4, 2)),
> @@ -239,6 +254,11 @@ mt7925_init_eht_caps(struct mt792x_phy *phy, enum nl80211_band band,
> eht_nss->bw._160.rx_tx_mcs9_max_nss = val;
> eht_nss->bw._160.rx_tx_mcs11_max_nss = val;
> eht_nss->bw._160.rx_tx_mcs13_max_nss = val;
> + if (band == NL80211_BAND_6GHZ && is_320mhz_supported(&phy->dev->mt76)) {
> + eht_nss->bw._320.rx_tx_mcs9_max_nss = val;
> + eht_nss->bw._320.rx_tx_mcs11_max_nss = val;
> + eht_nss->bw._320.rx_tx_mcs13_max_nss = val;
> + }
> }
>
> int mt7925_init_mlo_caps(struct mt792x_phy *phy)
>
> --
> 2.53.0
>
>
^ permalink raw reply
* Re: [PATCH v5 3/8] ARM: dts: aspeed: yosemite5: Add new SGPIO line names and rename signal
From: Andrew Jeffery @ 2026-03-26 6:10 UTC (permalink / raw)
To: Kevin Tung
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Joel Stanley,
devicetree, linux-arm-kernel, linux-aspeed, linux-kernel,
Amithash Prasasd, Kevin Tung, Ken Chen, Leo Yang, Jackson Liu,
Daniel Hsu
In-Reply-To: <CABh9gBe2BcgnONwx=cmcOAsT15HgVGjxSChgFMvckTAW_8JfMQ@mail.gmail.com>
Hi Kevin,
On Mon, 2026-03-09 at 11:34 -0700, Kevin Tung wrote:
> > Broadly, it feels a lot like you're revising platform designs, then
> > trying to make the one devicetree fit the current design, and are not
> > explicitly communicating that this is what you're doing.
> >
> > If that _is_ what you're doing, then we can come up with much better
> > schemes to handle it that aren't a constant stream of compatibility
> > breaks.
> >
> > I need you to engage with this concern.
> >
> Thanks for your feedback. I realize there may be a lack of knowledge
> on my side regarding the best practices here.
> Could you kindly guide me on how we might implement a better approach
> that avoids a constant stream of compatibility breaks?
> I’d like to ensure we handle this correctly and align with the
> expected workflow.
Sure, see the reply I just sent here:
https://lore.kernel.org/all/d7794f74b26bbc1ee0a70e39c5671acc018f80eb.camel@codeconstruct.com.au/
>
> > From inspection, I only find patches 1, 4 and 7 of this series to be
> > something I'd consider applying without further discussion.
> >
> Got it. Should I split patches 1, 4, and 7 into a separate series?
> This would keep the current series shorter by excluding items that
> don’t require further discussion.
>
I am okay with that, so long as it makes sense in the context of the
discussion linked above.
Andrew
^ permalink raw reply
* Re: [PATCH v4] nvme: Skip trace complete_rq on host path error
From: hch @ 2026-03-26 6:14 UTC (permalink / raw)
To: 전민식
Cc: Keith Busch, Justin Tee, axboe@kernel.dk, sven@kernel.org,
j@jannau.net, neal@gompa.dev, hch@lst.de, sagi@grimberg.me,
justin.tee@broadcom.com, nareshgottumukkala83@gmail.com,
paul.ely@broadcom.com, James Smart, kch@nvidia.com,
linux-arm-kernel@lists.infradead.org,
linux-nvme@lists.infradead.org, asahi@lists.linux.dev,
linux-kernel@vger.kernel.org, 이은수,
칸찬
In-Reply-To: <20260326014429epcms2p135ffd3c2b2fface6423d045e9614c262@epcms2p1>
> - trace_nvme_complete_rq(req);
> + if (nvme_req(req)->status != NVME_SC_HOST_PATH_ERROR)
> + trace_nvme_complete_rq(req);
> +
Please add a comment here explaining why we need the check.
^ permalink raw reply
* Re: [PATCH v3 12/13] wifi: mt76: mt7925: disable ASPM and runtime PM for MT7927
From: Sean Wang @ 2026-03-26 6:14 UTC (permalink / raw)
To: Javier Tia
Cc: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
Ming Yen Hsieh, Deren Wu, linux-wireless, linux-kernel,
linux-arm-kernel, linux-mediatek
In-Reply-To: <20260325-mt7927-wifi-support-v2-v3-12-5ca66c97a755@jetm.me>
Hi, Javier
On Wed, Mar 25, 2026 at 5:13 PM Javier Tia <floss@jetm.me> wrote:
>
> Disable PCIe ASPM unconditionally for MT7927. The CONNINFRA power
> domain and WFDMA register access are unreliable with PCIe L1 active,
> causing throughput to drop from 1+ Gbps to ~200 Mbps.
>
> Disable runtime PM and deep sleep for MT7927. The combo chip shares
> a CONNINFRA power domain between WiFi (PCIe) and BT (USB).
> SET_OWN/CLR_OWN transitions on the LPCTL register crash the BT
> firmware, requiring a full power cycle to recover. PM enablement will
> be addressed in a follow-up once safe power state transitions are
> determined.
>
> Signed-off-by: Javier Tia <floss@jetm.me>
> ---
> drivers/net/wireless/mediatek/mt76/mt7925/init.c | 6 +++++-
> drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 9 +++++++--
> 2 files changed, 12 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
> index c4c99380f5b5..89140fc6a2b6 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
> @@ -243,7 +243,11 @@ int mt7925_register_device(struct mt792x_dev *dev)
> dev->pm.idle_timeout = MT792x_PM_TIMEOUT;
> dev->pm.stats.last_wake_event = jiffies;
> dev->pm.stats.last_doze_event = jiffies;
> - if (!mt76_is_usb(&dev->mt76)) {
> + /* MT7927: runtime PM disabled. The combo chip shares a CONNINFRA
> + * power domain between WiFi (PCIe) and BT (USB). SET_OWN/CLR_OWN
> + * transitions on the LPCTL register crash BT firmware.
> + */
I think this level of detail is better kept in the commit message. For
the code itself, a short comment should be enough, or even no comment
if the condition is already clear enough.
> + if (!mt76_is_usb(&dev->mt76) && !is_mt7927(&dev->mt76)) {
> dev->pm.enable_user = true;
> dev->pm.enable = true;
> dev->pm.ds_enable_user = true;
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> index 393d9f408b84..693e08f35d68 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> @@ -530,7 +530,13 @@ static int mt7925_pci_probe(struct pci_dev *pdev,
> if (ret)
> goto err_free_pci_vec;
>
> - if (mt7925_disable_aspm)
> + is_mt7927_hw = (pdev->device == 0x6639 || pdev->device == 0x7927);
> +
> + /* MT7927: CONNINFRA power domain and WFDMA register access are
> + * unreliable with PCIe L1 active, causing throughput to drop
> + * from 1+ Gbps to ~200 Mbps. Disable ASPM unconditionally.
> + */
Ditto
> + if (mt7925_disable_aspm || is_mt7927_hw)
> mt76_pci_disable_aspm(pdev);
>
> ops = mt792x_get_mac80211_ops(&pdev->dev, &mt7925_ops,
> @@ -562,7 +568,6 @@ static int mt7925_pci_probe(struct pci_dev *pdev,
> dev = container_of(mdev, struct mt792x_dev, mt76);
> dev->fw_features = features;
> dev->hif_ops = &mt7925_pcie_ops;
> - is_mt7927_hw = (pdev->device == 0x6639 || pdev->device == 0x7927);
> dev->irq_map = is_mt7927_hw ? &mt7927_irq_map : &irq_map;
> dev->dma_config = is_mt7927_hw ? &mt7927_dma_cfg : &mt7925_dma_cfg;
> mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]);
>
> --
> 2.53.0
>
>
^ permalink raw reply
* [PATCH v3 0/4] AST2700-A2 interrupt controller hierarchy and route support
From: Ryan Chen @ 2026-03-26 6:19 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Joel Stanley,
Andrew Jeffery, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Thomas Gleixner, Thomas Gleixner
Cc: linux-kernel, devicetree, linux-arm-kernel, linux-aspeed,
linux-riscv, Ryan Chen
The AST2700 SoC has undergone multiple silicon revisions (A0, A1, A2)
prior to mass production.
A0 laid the ground-work with a split controller design (INTC0 and
INTC1) used for early development and bring-up. The interrupt
architecture was substantially reworked in the A1 to introduce an
explicit routing model and clearer hierarchy, though the split
controllers remained. The A1 interrupt architecture is unchanged in A2.
A2 is the production design. A0 and A1 are pre-production silicon and
are no longer intended for deployment outside of ASPEED.
The existing binding and driver were written against A0 prior to the A1
rework. The A0 design directly wired INTC1 instances to INTC0, and
INTC0 to the GIC of the Primary Service Processor (PSP, a Cortex-A35).
The A0 binding and driver therefore do not account for the alternative
destinations of the Secondary and Tertiary Service Processors (SSP,
TSP) and BootMCU, or the necessary route selection logic present in the
production design.
With the above context, this series replaces the existing binding and
driver.
It is not necessary for projects to maintain support for A0 due to its
pre-production nature, and between Linux, U-Boot and Zephyr there are
no upstream devicetree users of the current binding.
The new binding uses localised interrupt numbers and models the
hardware connectivity between interrupt controllers using the
aspeed,interrupt-ranges property. It is introduced in a new file before
the existing binding is removed in order to keep the diff readable.
The INTC0 driver creates a hierarchical irqdomain under the selected
upstream interrupt controller and implements route resolution logic.
INTC1 driver instances defer route selection to INTC0 and expose a
linear interrupt namespace to their parent.
A brief history of related submissions
--------------------------------------
Some modifications to the existing binding were sent to the lists in
the past. Due to process choices the revisions were difficult to track.
They are listed below.
The approaches took several forms but ended in the minor adjustment in
v6 being applied. This enabled use of the A1 design but requires
assumptions about platform route configuration defined in firmware.
These assumptions are removed by this current series.
* [PATCH] dt-bindings: interrupt-controller: aspeed: Refine AST2700 binding description and example
https://lore.kernel.org/all/20250714071753.2653620-1-ryan_chen@aspeedtech.com/
* [PATCH v2] dt-bindings: interrupt-controller: aspeed: Add parent node compatibles and refine documentation
https://lore.kernel.org/all/20250715024258.2304665-1-ryan_chen@aspeedtech.com/
* [PATCH v3 0/2] irqchip: aspeed: Add AST2700 INTC debugfs support and yaml update
https://lore.kernel.org/all/20250722095156.1672873-1-ryan_chen@aspeedtech.com/
* [PATCH v4 0/2] irqchip/ast2700-intc: Add AST2700 INTC debugfs support and yaml update
https://lore.kernel.org/all/20250812100830.145578-1-ryan_chen@aspeedtech.com/
* [PATCH v5 0/3] AST2700 interrupt controller hierarchy support
https://lore.kernel.org/all/20251022065507.1152071-1-ryan_chen@aspeedtech.com/
* [PATCH v6 0/1] Update correct AST2700 interrupt controller binding
https://lore.kernel.org/all/20251030060155.2342604-1-ryan_chen@aspeedtech.com/
Signed-off-by: Ryan Chen <ryan_chen@aspeedtech.com>
---
Changes in v3:
- 1/4 Squash patch 5/5 and 1/5.
- 1/4 modify wrap lines at 80 char.
- 1/4 modify maintainers name and email.
- 1/4 modify typo Sevice-> Service
- Link to v2: https://lore.kernel.org/r/20260306-irqchip-v2-0-f8512c09be63@aspeedtech.com
Changes in v2:
- Change suject to "AST2700-A2 interrupt controller hierarchy and route
support".
- Describe timeline for (pre-)production design evolution and
binding development to support the break in compatibility.
- fix "make dt_binding_check" compatible string consistance with
example.
- Split KUnit coverage out of the main driver patch.
- Link to v1: https://lore.kernel.org/r/20260205-irqchip-v1-0-b0310e06c087@aspeedtech.com
---
Ryan Chen (4):
dt-bindings: interrupt-controller: Describe AST2700-A2 hardware instead of A0
irqchip/ast2700-intc: Add AST2700-A2 support
irqchip/ast2700-intc: Add KUnit tests for route resolution
irqchip/aspeed-intc: Remove AST2700-A0 support
.../interrupt-controller/aspeed,ast2700-intc.yaml | 90 ----
.../aspeed,ast2700-interrupt.yaml | 188 +++++++
drivers/irqchip/.kunitconfig | 5 +
drivers/irqchip/Kconfig | 23 +
drivers/irqchip/Makefile | 3 +-
drivers/irqchip/irq-aspeed-intc.c | 139 -----
drivers/irqchip/irq-ast2700-intc0-test.c | 473 +++++++++++++++++
drivers/irqchip/irq-ast2700-intc0.c | 584 +++++++++++++++++++++
drivers/irqchip/irq-ast2700-intc1.c | 282 ++++++++++
drivers/irqchip/irq-ast2700.c | 106 ++++
drivers/irqchip/irq-ast2700.h | 47 ++
11 files changed, 1710 insertions(+), 230 deletions(-)
---
base-commit: 6de23f81a5e08be8fbf5e8d7e9febc72a5b5f27f
change-id: 20260205-irqchip-7eaef3674de9
Best regards,
--
Ryan Chen <ryan_chen@aspeedtech.com>
^ permalink raw reply
* [PATCH v3 1/4] dt-bindings: interrupt-controller: Describe AST2700-A2 hardware instead of A0
From: Ryan Chen @ 2026-03-26 6:19 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Joel Stanley,
Andrew Jeffery, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Thomas Gleixner, Thomas Gleixner
Cc: linux-kernel, devicetree, linux-arm-kernel, linux-aspeed,
linux-riscv, Ryan Chen
In-Reply-To: <20260326-irqchip-v3-0-366739f57acf@aspeedtech.com>
Introduce a new binding describing the AST2700 interrupt controller
architecture implemented in the A2 production silicon.
The AST2700 SoC has undergone multiple silicon revisions (A0, A1, A2)
prior to mass production. The interrupt architecture was substantially
reworked after the A0 revision for A1, and the A1 design is retained
unchanged in the A2 production silicon.
The existing AST2700 interrupt controller binding
("aspeed,ast2700-intc-ic")was written against the pre-production A0
design. That binding does not accurately describe the interrupt
hierarchy and routing model present in A1/A2, where interrupts can be
routed to multiple processor-local interrupt controllers (Primary
Service Processor (PSP) GIC, Secondary Service Processor (SSP)/Tertiary
Service Processor (TSP) NVICs, and BootMCU APLIC) depending on the
execution context.
Remove the binding for the pre-production A0 design in favour of the
binding for the A2 production design. There is no significant user
impact from the removal as there are no existing devicetrees in any
of Linux, u-boot or Zephyr that make use of the A0 binding.
Hardware connectivity between interrupt controllers is expressed using
the aspeed,interrupt-ranges property.
Signed-off-by: Ryan Chen <ryan_chen@aspeedtech.com>
---
Changes in v3:
- squash patch 5/5.
- modify wrap lines at 80 char.
- modify maintainers name and email.
- modify typo Sevice-> Service
Changes in v2:
- Describe AST2700 A0/A1/A2 design evolution.
- Drop the redundant '-ic' suffix from compatible strings.
- Expand commit message to match the series cover letter context.
- fix ascii diagram
- remove intc0 label
- remove spaces before >
- drop intc1 example
---
.../interrupt-controller/aspeed,ast2700-intc.yaml | 90 ----------
.../aspeed,ast2700-interrupt.yaml | 188 +++++++++++++++++++++
2 files changed, 188 insertions(+), 90 deletions(-)
diff --git a/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700-intc.yaml
deleted file mode 100644
index 258d21fe6e35..000000000000
--- a/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700-intc.yaml
+++ /dev/null
@@ -1,90 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
-%YAML 1.2
----
-$id: http://devicetree.org/schemas/interrupt-controller/aspeed,ast2700-intc.yaml#
-$schema: http://devicetree.org/meta-schemas/core.yaml#
-
-title: Aspeed AST2700 Interrupt Controller
-
-description:
- This interrupt controller hardware is second level interrupt controller that
- is hooked to a parent interrupt controller. It's useful to combine multiple
- interrupt sources into 1 interrupt to parent interrupt controller.
-
-maintainers:
- - Kevin Chen <kevin_chen@aspeedtech.com>
-
-properties:
- compatible:
- enum:
- - aspeed,ast2700-intc-ic
-
- reg:
- maxItems: 1
-
- interrupt-controller: true
-
- '#interrupt-cells':
- const: 1
- description:
- The first cell is the IRQ number, the second cell is the trigger
- type as defined in interrupt.txt in this directory.
-
- interrupts:
- minItems: 1
- maxItems: 10
- description: |
- Depend to which INTC0 or INTC1 used.
- INTC0 and INTC1 are two kinds of interrupt controller with enable and raw
- status registers for use.
- INTC0 is used to assert GIC if interrupt in INTC1 asserted.
- INTC1 is used to assert INTC0 if interrupt of modules asserted.
- +-----+ +-------+ +---------+---module0
- | GIC |---| INTC0 |--+--| INTC1_0 |---module2
- | | | | | | |---...
- +-----+ +-------+ | +---------+---module31
- |
- | +---------+---module0
- +---| INTC1_1 |---module2
- | | |---...
- | +---------+---module31
- ...
- | +---------+---module0
- +---| INTC1_5 |---module2
- | |---...
- +---------+---module31
-
-required:
- - compatible
- - reg
- - interrupt-controller
- - '#interrupt-cells'
- - interrupts
-
-additionalProperties: false
-
-examples:
- - |
- #include <dt-bindings/interrupt-controller/arm-gic.h>
-
- bus {
- #address-cells = <2>;
- #size-cells = <2>;
-
- interrupt-controller@12101b00 {
- compatible = "aspeed,ast2700-intc-ic";
- reg = <0 0x12101b00 0 0x10>;
- #interrupt-cells = <1>;
- interrupt-controller;
- interrupts = <GIC_SPI 192 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 193 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 194 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 195 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 196 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 197 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 198 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 199 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 200 IRQ_TYPE_LEVEL_HIGH>,
- <GIC_SPI 201 IRQ_TYPE_LEVEL_HIGH>;
- };
- };
diff --git a/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700-interrupt.yaml b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700-interrupt.yaml
new file mode 100644
index 000000000000..a62f0fd2435b
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2700-interrupt.yaml
@@ -0,0 +1,188 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/aspeed,ast2700-interrupt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ASPEED AST2700 Interrupt Controllers (INTC0/INTC1)
+
+description: |
+ The ASPEED AST2700 SoC integrates two interrupt controller designs:
+
+ - INTC0: Primary controller that routes interrupt sources to upstream,
+ processor-specific interrupt controllers
+
+ - INTC1: Secondary controller whose interrupt outputs feed into INTC0
+
+ The SoC contains four processors to which interrupts can be routed:
+
+ - PSP: Primary Service Processor (Cortex-A35)
+ - SSP: Secondary Service Processor (Cortex-M4)
+ - TSP: Tertiary Service Processor (Cortex-M4)
+ - BMCU: Boot MCU (a RISC-V microcontroller)
+
+ The following diagram illustrates the overall architecture of the
+ ASPEED AST2700 interrupt controllers:
+
+ +-----------+ +-----------+
+ | INTC0 | | INTC1(0) |
+ +-----------+ +-----------+
+ | Router | +-----------+ | Router |
+ | out int | +Peripheral + | out int |
+ +-----------+ | 0 0 <-+Controllers+ | INTM | +-----------+
+ |PSP GIC <-|---+ . . | +-----------+ | . . <-+Peripheral +
+ +-----------+ | . . | | . . | +Controllers+
+ +-----------+ | . . | | . . | +-----------+
+ |SSP NVIC <-|---+ . . <----------------+ . . |
+ +-----------+ | . . | | . . |
+ +-----------+ | . . <-------- | . . |
+ |TSP NVIC <-|---+ . . | | ----+ . . |
+ +-----------+ | . . | | | | O P |
+ | . . | | | +-----------+
+ | . . <---- | --------------------
+ | . . | | | +-----------+ |
+ | M N | | ---------+ INTC1(1) | |
+ +-----------+ | +-----------+ |
+ | . |
+ | +-----------+ |
+ -------------+ INTC1(N) | |
+ +-----------+ |
+ +--------------+ |
+ + BMCU APLIC <-+---------------------------------------------
+ +--------------+
+
+ INTC0 supports:
+ - 128 local peripheral interrupt inputs
+ - Fan-in from up to three INTC1 instances via banked interrupt lines (INTM)
+ - Local peripheral interrupt outputs
+ - Merged interrupt outputs
+ - Software interrupt outputs (SWINT)
+ - Configurable interrupt routes targeting the PSP, SSP, and TSP
+
+ INTC1 supports:
+ - 192 local peripheral interrupt inputs
+ - Banked interrupt outputs (INTM, 5 x 6 banks x 32 interrupts per bank)
+ - Configurable interrupt routes targeting the PSP, SSP, TSP, and BMCU
+
+ One INTC1 instance is always present, on the SoC's IO die. A further two
+ instances may be attached to the SoC's one INTC0 instance via LTPI (LVDS
+ Tunneling Protocol & Interface).
+
+ Interrupt numbering model
+ -------------------------
+ The binding uses a controller-local numbering model. Peripheral device
+ nodes use the INTCx local interrupt number (hwirq) in their 'interrupts' or
+ 'interrupts-extended' properties.
+
+ For AST2700, INTC0 exposes the following (inclusive) input ranges:
+
+ - 000..479: Independent interrupts
+ - 480..489: INTM0-INTM9
+ - 490..499: INTM10-INTM19
+ - 500..509: INTM20-INTM29
+ - 510..519: INTM30-INTM39
+ - 520..529: INTM40-INTM49
+
+ INTC0's (inclusive) output ranges are as follows:
+
+ - 000..127: 1:1 local peripheral interrupt output to PSP
+ - 144..151: Software interrupts from the SSP output to PSP
+ - 152..159: Software interrupts from the TSP output to PSP
+ - 192..201: INTM0-INTM9 banked outputs to PSP
+ - 208..217: INTM30-INTM39 banked outputs to PSP
+ - 224..233: INTM40-INTM49 banked outputs to PSP
+ - 256..383: 1:1 local peripheral interrupt output to SSP
+ - 384..393: INTM10-INTM19 banked outputs to SSP
+ - 400..407: Software interrupts from the PSP output to SSP
+ - 408..415: Software interrupts from the TSP output to SSP
+ - 426..553: 1:1 local peripheral interrupt output to TSP
+ - 554..563: INTM20-INTM29 banked outputs to TSP
+ - 570..577: Software interrupts from the PSP output to TSP
+ - 578..585: Software interrupts from the SSP output to TSP
+
+ Inputs and outputs for INTC1 instances are context-dependent. However, for the
+ first instance of INTC1, the (inclusive) output ranges are:
+
+ - 00..05: INTM0-INTM5
+ - 10..15: INTM10-INTM15
+ - 20..25: INTM20-INTM25
+ - 30..35: INTM30-INTM35
+ - 40..45: INTM40-INTM45
+ - 50..50: BootMCU
+
+maintainers:
+ - Ryan Chen <ryan_chen@aspeedtech.com>
+ - Andrew Jeffery <andrew@codeconstruct.com.au>
+
+properties:
+ compatible:
+ enum:
+ - aspeed,ast2700-intc0
+ - aspeed,ast2700-intc1
+
+ reg:
+ maxItems: 1
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 1
+ description: Single cell encoding the INTC local interrupt number (hwirq).
+
+ aspeed,interrupt-ranges:
+ description: |
+ Describes how ranges of controller output pins are routed to a parent
+ interrupt controller.
+
+ Each range entry is encoded as:
+
+ <out count phandle parent-specifier...>
+
+ where:
+ - out: First controller interrupt output index in the range.
+ - count: Number of consecutive controller interrupt outputs and parent
+ interrupt inputs in this range.
+ - phandle: Phandle to the parent interrupt controller node.
+ - parent-specifier: Interrupt specifier, as defined by the parent
+ interrupt controller binding.
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 3
+ items:
+ description: Range descriptors with a parent interrupt specifier.
+
+required:
+ - compatible
+ - reg
+ - interrupt-controller
+ - '#interrupt-cells'
+ - aspeed,interrupt-ranges
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ interrupt-controller@12100000 {
+ compatible = "aspeed,ast2700-intc0";
+ reg = <0x12100000 0x3b00>;
+ interrupt-parent = <&gic>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
+
+ aspeed,interrupt-ranges =
+ <0 128 &gic GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>,
+ <144 8 &gic GIC_SPI 144 IRQ_TYPE_LEVEL_HIGH>,
+ <152 8 &gic GIC_SPI 152 IRQ_TYPE_LEVEL_HIGH>,
+ <192 10 &gic GIC_SPI 192 IRQ_TYPE_LEVEL_HIGH>,
+ <208 10 &gic GIC_SPI 208 IRQ_TYPE_LEVEL_HIGH>,
+ <224 10 &gic GIC_SPI 224 IRQ_TYPE_LEVEL_HIGH>,
+ <256 128 &ssp_nvic 0 0>,
+ <384 10 &ssp_nvic 160 0>,
+ <400 8 &ssp_nvic 144 0>,
+ <408 8 &ssp_nvic 152 0>,
+ <426 128 &tsp_nvic 0 0>,
+ <554 10 &tsp_nvic 160 0>,
+ <570 8 &tsp_nvic 144 0>,
+ <578 8 &tsp_nvic 152 0>;
+ };
--
2.34.1
^ permalink raw reply related
* [PATCH v3 2/4] irqchip/ast2700-intc: Add AST2700-A2 support
From: Ryan Chen @ 2026-03-26 6:19 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Joel Stanley,
Andrew Jeffery, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Thomas Gleixner, Thomas Gleixner
Cc: linux-kernel, devicetree, linux-arm-kernel, linux-aspeed,
linux-riscv, Ryan Chen
In-Reply-To: <20260326-irqchip-v3-0-366739f57acf@aspeedtech.com>
The AST2700 interrupt fabric is shared by multiple integrated processors
(PSP/SSP/TSP/BootMCU), each with its own interrupt controller and its own
devicetree view of the system. As a result, interrupt routing cannot be
treated as fixed: the valid route for a peripheral interrupt depends on
which processor is consuming it.
The INTC0 driver models this by creating a hierarchical irqdomain under
the upstream interrupt controller selected by the interrupt-parent
property in the devicetree. Information derived from this relationship
is incorporated into the route resolution logic for the controller.
The INTC1 driver implements the banked INTM-fed controller and forwards
interrupts toward INTC0, without embedding assumptions about the final
destination processor.
Signed-off-by: Ryan Chen <ryan_chen@aspeedtech.com>
---
Changes in v2:
- remove typedef u32 aspeed_intc_output_t
- modify #include <asm-generic/errno.h> to <linux/err.h>
- add newline after include "irq-ast2700.h"
- make defines tabular
- Struct declarations should align the struct member names in a table
- modify raw_spinlock_irqsave() to raw_spin_lock()
- use u32 ier replace mask/unmask
- remove pointless line break
- refine aspeed_intc0_routes, aspeed_intc1_routes array
- remove range_contains_element(), use in_range32()
- remove dev_dbg()
- remove EXPORT_SYMBOL_GPL(aspeed_intc0_resolve_route);
- make irq_set_chip_and_handler() with one line
- replace magic constants to macro define
- move struct aspeed_intc0 to irq-ast2700.h
- add mcro define for upstream param
---
drivers/irqchip/Kconfig | 12 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-ast2700-intc0.c | 584 ++++++++++++++++++++++++++++++++++++
drivers/irqchip/irq-ast2700-intc1.c | 282 +++++++++++++++++
drivers/irqchip/irq-ast2700.c | 106 +++++++
drivers/irqchip/irq-ast2700.h | 47 +++
6 files changed, 1032 insertions(+)
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index f07b00d7fef9..0156fee89b2c 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -110,6 +110,18 @@ config AL_FIC
help
Support Amazon's Annapurna Labs Fabric Interrupt Controller.
+config ASPEED_AST2700_INTC
+ bool "ASPEED AST2700 Interrupt Controller support"
+ depends on OF
+ depends on ARCH_ASPEED || COMPILE_TEST
+ select IRQ_DOMAIN_HIERARCHY
+ help
+ Enable support for the ASPEED AST2700 interrupt controller.
+ This driver handles interrupt, routing and merged interrupt
+ sources to upstream parent interrupt controllers.
+
+ If unsure, say N.
+
config ATMEL_AIC_IRQ
bool
select GENERIC_IRQ_CHIP
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 26aa3b6ec99f..62790663f982 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -89,6 +89,7 @@ obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o
obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o
obj-$(CONFIG_LS_EXTIRQ) += irq-ls-extirq.o
obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o
+obj-$(CONFIG_ASPEED_AST2700_INTC) += irq-ast2700.o irq-ast2700-intc0.o irq-ast2700-intc1.o
obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-aspeed-scu-ic.o
obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-intc.o
obj-$(CONFIG_STM32MP_EXTI) += irq-stm32mp-exti.o
diff --git a/drivers/irqchip/irq-ast2700-intc0.c b/drivers/irqchip/irq-ast2700-intc0.c
new file mode 100644
index 000000000000..66e2fb108281
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700-intc0.c
@@ -0,0 +1,584 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Aspeed AST2700 Interrupt Controller.
+ *
+ * Copyright (C) 2026 ASPEED Technology Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/fwnode.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kconfig.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/overflow.h>
+#include <linux/property.h>
+#include <linux/spinlock.h>
+
+#include "irq-ast2700.h"
+
+#define INT_NUM 480
+#define INTM_NUM 50
+#define SWINT_NUM 16
+
+#define INTM_BASE (INT_NUM)
+#define SWINT_BASE (INT_NUM + INTM_NUM)
+#define INT0_NUM (INT_NUM + INTM_NUM + SWINT_NUM)
+
+#define INTC0_IN_NUM 480
+#define INTC0_ROUTE_NUM 5
+#define INTC0_INTM_NUM 50
+#define INTC0_ROUTE_BITS 3
+
+#define GIC_P2P_SPI_END 128
+#define INTC0_SWINT_OUT_BASE 144
+
+#define INTC0_SWINT_IER 0x10
+#define INTC0_SWINT_ISR 0x14
+#define INTC0_INTBANKX_IER 0x1000
+#define INTC0_INTBANK_SIZE 0x100
+#define INTC0_INTBANK_GROUPS 11
+#define INTC0_INTBANKS_PER_GRP 3
+#define INTC0_INTMX_IER 0x1b00
+#define INTC0_INTMX_ISR 0x1b04
+#define INTC0_INTMX_BANK_SIZE 0x10
+#define INTC0_INTM_BANK_NUM 3
+#define INTC0_IRQS_PER_BANK 32
+#define INTM_IRQS_PER_BANK 10
+#define INTC0_SEL_BASE 0x200
+#define INTC0_SEL_BANK_SIZE 0x4
+#define INTC0_SEL_ROUTE_SIZE 0x100
+
+static void aspeed_swint_irq_mask(struct irq_data *data)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ int bit = data->hwirq - SWINT_BASE;
+ u32 ier;
+
+ guard(raw_spinlock)(&intc0->intc_lock);
+ ier = readl(intc0->base + INTC0_SWINT_IER) & ~BIT(bit);
+ writel(ier, intc0->base + INTC0_SWINT_IER);
+ irq_chip_mask_parent(data);
+}
+
+static void aspeed_swint_irq_unmask(struct irq_data *data)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ int bit = data->hwirq - SWINT_BASE;
+ u32 ier;
+
+ guard(raw_spinlock)(&intc0->intc_lock);
+ ier = readl(intc0->base + INTC0_SWINT_IER) | BIT(bit);
+ writel(ier, intc0->base + INTC0_SWINT_IER);
+ irq_chip_unmask_parent(data);
+}
+
+static void aspeed_swint_irq_eoi(struct irq_data *data)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ int bit = data->hwirq - SWINT_BASE;
+
+ writel(BIT(bit), intc0->base + INTC0_SWINT_ISR);
+ irq_chip_eoi_parent(data);
+}
+
+static struct irq_chip aspeed_swint_chip = {
+ .name = "ast2700-swint",
+ .irq_eoi = aspeed_swint_irq_eoi,
+ .irq_mask = aspeed_swint_irq_mask,
+ .irq_unmask = aspeed_swint_irq_unmask,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .flags = IRQCHIP_SET_TYPE_MASKED,
+};
+
+static void aspeed_intc0_irq_mask(struct irq_data *data)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK;
+ int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK;
+ u32 ier;
+
+ guard(raw_spinlock)(&intc0->intc_lock);
+ ier = readl(intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE) & ~BIT(bit);
+ writel(ier, intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE);
+ irq_chip_mask_parent(data);
+}
+
+static void aspeed_intc0_irq_unmask(struct irq_data *data)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK;
+ int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK;
+ u32 ier;
+
+ guard(raw_spinlock)(&intc0->intc_lock);
+ ier = readl(intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE) | BIT(bit);
+ writel(ier, intc0->base + INTC0_INTMX_IER + bank * INTC0_INTMX_BANK_SIZE);
+ irq_chip_unmask_parent(data);
+}
+
+static void aspeed_intc0_irq_eoi(struct irq_data *data)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ int bank = (data->hwirq - INTM_BASE) / INTM_IRQS_PER_BANK;
+ int bit = (data->hwirq - INTM_BASE) % INTM_IRQS_PER_BANK;
+
+ writel(BIT(bit), intc0->base + INTC0_INTMX_ISR + bank * INTC0_INTMX_BANK_SIZE);
+ irq_chip_eoi_parent(data);
+}
+
+static struct irq_chip aspeed_intm_chip = {
+ .name = "ast2700-intmerge",
+ .irq_eoi = aspeed_intc0_irq_eoi,
+ .irq_mask = aspeed_intc0_irq_mask,
+ .irq_unmask = aspeed_intc0_irq_unmask,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .flags = IRQCHIP_SET_TYPE_MASKED,
+};
+
+static struct irq_chip linear_intr_irq_chip = {
+ .name = "ast2700-int",
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .flags = IRQCHIP_SET_TYPE_MASKED,
+};
+
+static const u32 aspeed_intc0_routes[INTC0_IN_NUM / INTC0_IRQS_PER_BANK][INTC0_ROUTE_NUM] = {
+ { 0, 256, 426, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE },
+ { 32, 288, 458, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE },
+ { 64, 320, 490, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE },
+ { 96, 352, 522, AST2700_INTC_INVALID_ROUTE, AST2700_INTC_INVALID_ROUTE },
+ { 128, 384, 554, 160, 176 },
+ { 129, 385, 555, 161, 177 },
+ { 130, 386, 556, 162, 178 },
+ { 131, 387, 557, 163, 179 },
+ { 132, 388, 558, 164, 180 },
+ { 133, 544, 714, 165, 181 },
+ { 134, 545, 715, 166, 182 },
+ { 135, 546, 706, 167, 183 },
+ { 136, 547, 707, 168, 184 },
+ { 137, 548, 708, 169, 185 },
+ { 138, 549, 709, 170, 186 },
+};
+
+static const u32 aspeed_intc0_intm_routes[INTC0_INTM_NUM / INTM_IRQS_PER_BANK] = {
+ 192, 416, 586, 208, 224
+};
+
+static int resolve_input_from_child_ranges(const struct aspeed_intc0 *intc0,
+ const struct aspeed_intc_interrupt_range *range,
+ u32 outpin, u32 *input)
+{
+ u32 offset, base;
+
+ if (!in_range32(outpin, range->start, range->count))
+ return -ENOENT;
+
+ if (range->upstream.param_count == 0)
+ return -EINVAL;
+
+ base = range->upstream.param[ASPEED_INTC_RANGES_BASE];
+ offset = outpin - range->start;
+ if (check_add_overflow(base, offset, input)) {
+ dev_warn(intc0->dev, "%s: Arithmetic overflow for input derivation: %u + %u\n",
+ __func__, base, offset);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int resolve_parent_range_for_output(const struct aspeed_intc0 *intc0,
+ const struct fwnode_handle *parent,
+ u32 output,
+ struct aspeed_intc_interrupt_range *resolved)
+{
+ for (size_t i = 0; i < intc0->ranges.nranges; i++) {
+ struct aspeed_intc_interrupt_range range =
+ intc0->ranges.ranges[i];
+
+ if (!in_range32(output, range.start, range.count))
+ continue;
+
+ if (range.upstream.fwnode != parent)
+ continue;
+
+ if (resolved) {
+ resolved->start = output;
+ resolved->count = 1;
+ resolved->upstream = range.upstream;
+ resolved->upstream.param[ASPEED_INTC_RANGES_COUNT] +=
+ output - range.start;
+ }
+
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static int resolve_parent_route_for_input(const struct aspeed_intc0 *intc0,
+ const struct fwnode_handle *parent, u32 input,
+ struct aspeed_intc_interrupt_range *resolved)
+{
+ int rc = -ENOENT;
+ u32 c0o;
+
+ if (input < INT_NUM) {
+ static_assert(INTC0_ROUTE_NUM < INT_MAX, "Broken cast");
+ for (size_t i = 0; rc == -ENOENT && i < INTC0_ROUTE_NUM; i++) {
+ c0o = aspeed_intc0_routes[input / INTC0_IRQS_PER_BANK][i];
+ if (c0o == AST2700_INTC_INVALID_ROUTE)
+ continue;
+
+ if (input < GIC_P2P_SPI_END)
+ c0o += input % INTC0_IRQS_PER_BANK;
+
+ rc = resolve_parent_range_for_output(intc0, parent, c0o, resolved);
+ if (!rc)
+ return (int)i;
+ }
+ } else if (input < (INT_NUM + INTM_NUM)) {
+ c0o = aspeed_intc0_intm_routes[(input - INT_NUM) / INTM_IRQS_PER_BANK];
+ c0o += ((input - INT_NUM) % INTM_IRQS_PER_BANK);
+ return resolve_parent_range_for_output(intc0, parent, c0o, resolved);
+ } else if (input < (INT_NUM + INTM_NUM + SWINT_NUM)) {
+ c0o = input - SWINT_BASE + INTC0_SWINT_OUT_BASE;
+ return resolve_parent_range_for_output(intc0, parent, c0o, resolved);
+ } else {
+ return -ENOENT;
+ }
+
+ return rc;
+}
+
+/**
+ * aspeed_intc0_resolve_route - Determine the necessary interrupt output at intc1
+ * @c0domain: The pointer to intc0's irq_domain
+ * @nc1outs: The number of valid intc1 outputs available for the input
+ * @c1outs: The array of available intc1 output indices for the input
+ * @nc1ranges: The number of interrupt range entries for intc1
+ * @c1ranges: The array of configured intc1 interrupt ranges
+ * @resolved: The fully resolved range entry after applying the resolution
+ * algorithm
+ *
+ * Returns: The intc1 route index associated with the intc1 output identified in
+ * @resolved on success. Otherwise, a negative errno value.
+ *
+ * The AST2700 interrupt architecture allows any peripheral interrupt source
+ * to be routed to one of up to four processors running in the SoC. A processor
+ * binding a driver for a peripheral that requests an interrupt is (without
+ * further design and effort) the destination for the requested interrupt.
+ *
+ * Routing a peripheral interrupt to its destination processor requires
+ * coordination between INTC0 on the CPU die and one or more INTC1 instances.
+ * At least one INTC1 instance exists in the SoC on the IO-die, however up
+ * to two more instances may be integrated via LTPI (LVDS Tunneling Protocol
+ * & Interface).
+ *
+ * Between the multiple destinations, various route constraints, and the
+ * devicetree binding design, some information that's needed at INTC1 instances
+ * to route inbound interrupts correctly to the destination processor is only
+ * available at INTC0.
+ *
+ * aspeed_intc0_resolve_route() is to be invoked by INTC1 driver instances to
+ * perform the route resolution. The implementation in INTC0 allows INTC0 to
+ * encapsulate the information used to perform route selection, and provides it
+ * with an opportunity to apply policy as part of the selection process. Such
+ * policy may, for instance, choose to de-prioritise some interrupts destined
+ * for the PSP (Primary Service Processor) GIC.
+ */
+int aspeed_intc0_resolve_route(const struct irq_domain *c0domain, size_t nc1outs,
+ const u32 *c1outs, size_t nc1ranges,
+ const struct aspeed_intc_interrupt_range *c1ranges,
+ struct aspeed_intc_interrupt_range *resolved)
+{
+ struct fwnode_handle *parent_fwnode;
+ struct aspeed_intc0 *intc0;
+ int ret;
+
+ if (!c0domain || !resolved)
+ return -EINVAL;
+
+ if (nc1outs > INT_MAX)
+ return -EINVAL;
+
+ if (nc1outs == 0 || nc1ranges == 0)
+ return -ENOENT;
+
+ if (!fwnode_device_is_compatible(c0domain->fwnode, "aspeed,ast2700-intc0"))
+ return -ENODEV;
+
+ intc0 = c0domain->host_data;
+ if (!intc0)
+ return -EINVAL;
+
+ parent_fwnode = of_fwnode_handle(intc0->parent);
+
+ for (size_t i = 0; i < nc1outs; i++) {
+ u32 c1o = c1outs[i];
+
+ if (c1o == AST2700_INTC_INVALID_ROUTE)
+ continue;
+
+ for (size_t j = 0; j < nc1ranges; j++) {
+ struct aspeed_intc_interrupt_range c1r = c1ranges[j];
+ u32 input;
+
+ /*
+ * Range match for intc1 output pin
+ *
+ * Assume a failed match is still a match for the purpose of testing,
+ * saves a bunch of mess in the test fixtures
+ */
+ if (!(c0domain == irq_find_matching_fwspec(&c1r.upstream,
+ c0domain->bus_token) ||
+ IS_ENABLED(CONFIG_ASPEED_AST2700_INTC_TEST)))
+ continue;
+
+ ret = resolve_input_from_child_ranges(intc0, &c1r, c1o, &input);
+ if (ret)
+ continue;
+
+ /*
+ * INTC1 should never request routes for peripheral interrupt sources
+ * directly attached to INTC0.
+ */
+ if (input < GIC_P2P_SPI_END)
+ continue;
+
+ ret = resolve_parent_route_for_input(intc0, parent_fwnode, input, NULL);
+ if (ret < 0)
+ continue;
+
+ /* Route resolution succeeded */
+ resolved->start = c1o;
+ resolved->count = 1;
+ resolved->upstream = c1r.upstream;
+ resolved->upstream.param[ASPEED_INTC_RANGES_BASE] = input;
+ /* Cast protected by prior test against nc1outs */
+ return (int)i;
+ }
+ }
+
+ return -ENOENT;
+}
+
+static int aspeed_intc0_irq_domain_map(struct irq_domain *domain,
+ unsigned int irq, irq_hw_number_t hwirq)
+{
+ if (hwirq < GIC_P2P_SPI_END)
+ irq_set_chip_and_handler(irq, &linear_intr_irq_chip, handle_level_irq);
+ else if (hwirq < INTM_BASE)
+ return -EINVAL;
+ else if (hwirq < SWINT_BASE)
+ irq_set_chip_and_handler(irq, &aspeed_intm_chip, handle_level_irq);
+ else if (hwirq < INT0_NUM)
+ irq_set_chip_and_handler(irq, &aspeed_swint_chip, handle_level_irq);
+ else
+ return -EINVAL;
+
+ irq_set_chip_data(irq, domain->host_data);
+ return 0;
+}
+
+static int aspeed_intc0_irq_domain_translate(struct irq_domain *domain,
+ struct irq_fwspec *fwspec,
+ unsigned long *hwirq,
+ unsigned int *type)
+{
+ if (fwspec->param_count != 1)
+ return -EINVAL;
+
+ *hwirq = fwspec->param[0];
+ *type = IRQ_TYPE_NONE;
+ return 0;
+}
+
+static int aspeed_intc0_irq_domain_alloc(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs, void *data)
+{
+ struct aspeed_intc0 *intc0 = domain->host_data;
+ struct aspeed_intc_interrupt_range resolved;
+ struct irq_fwspec *fwspec = data;
+ struct irq_fwspec parent_fwspec;
+ struct irq_chip *chip;
+ unsigned long hwirq;
+ unsigned int type;
+ int ret;
+
+ ret = aspeed_intc0_irq_domain_translate(domain, fwspec, &hwirq, &type);
+ if (ret)
+ return ret;
+
+ if (hwirq >= GIC_P2P_SPI_END && hwirq < INT_NUM)
+ return -EINVAL;
+
+ if (hwirq < INTM_BASE)
+ chip = &linear_intr_irq_chip;
+ else if (hwirq < SWINT_BASE)
+ chip = &aspeed_intm_chip;
+ else
+ chip = &aspeed_swint_chip;
+
+ ret = resolve_parent_route_for_input(intc0, domain->parent->fwnode,
+ (u32)hwirq, &resolved);
+ if (ret)
+ return ret;
+
+ parent_fwspec = resolved.upstream;
+ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
+ &parent_fwspec);
+ if (ret)
+ return ret;
+
+ for (int i = 0; i < nr_irqs; ++i, ++hwirq, ++virq) {
+ ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip,
+ domain->host_data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int aspeed_intc0_irq_domain_activate(struct irq_domain *domain,
+ struct irq_data *data, bool reserve)
+{
+ struct aspeed_intc0 *intc0 = irq_data_get_irq_chip_data(data);
+ unsigned long hwirq = data->hwirq;
+ int route, bank, bit;
+ u32 mask;
+
+ if (hwirq >= INT0_NUM)
+ return -EINVAL;
+
+ if (in_range32(hwirq, INTM_BASE, INTM_NUM + SWINT_NUM))
+ return 0;
+
+ bank = hwirq / INTC0_IRQS_PER_BANK;
+ bit = hwirq % INTC0_IRQS_PER_BANK;
+ mask = BIT(bit);
+
+ route = resolve_parent_route_for_input(intc0, intc0->local->parent->fwnode,
+ hwirq, NULL);
+ if (route < 0)
+ return route;
+
+ guard(raw_spinlock)(&intc0->intc_lock);
+ for (int i = 0; i < INTC0_ROUTE_BITS; i++) {
+ void __iomem *sel = intc0->base + INTC0_SEL_BASE +
+ (bank * INTC0_SEL_BANK_SIZE) +
+ (INTC0_SEL_ROUTE_SIZE * i);
+ u32 reg = readl(sel);
+
+ if (route & BIT(i))
+ reg |= mask;
+ else
+ reg &= ~mask;
+
+ writel(reg, sel);
+ if (readl(sel) != reg)
+ return -EACCES;
+ }
+
+ return 0;
+}
+
+static const struct irq_domain_ops aspeed_intc0_irq_domain_ops = {
+ .translate = aspeed_intc0_irq_domain_translate,
+ .activate = aspeed_intc0_irq_domain_activate,
+ .alloc = aspeed_intc0_irq_domain_alloc,
+ .free = irq_domain_free_irqs_common,
+ .map = aspeed_intc0_irq_domain_map,
+};
+
+static void aspeed_intc0_disable_swint(struct aspeed_intc0 *intc0)
+{
+ writel(0, intc0->base + INTC0_SWINT_IER);
+}
+
+static void aspeed_intc0_disable_intbank(struct aspeed_intc0 *intc0)
+{
+ for (int i = 0; i < INTC0_INTBANK_GROUPS; i++) {
+ for (int j = 0; j < INTC0_INTBANKS_PER_GRP; j++) {
+ u32 base = INTC0_INTBANKX_IER +
+ (INTC0_INTBANK_SIZE * i) +
+ (INTC0_INTMX_BANK_SIZE * j);
+
+ writel(0, intc0->base + base);
+ }
+ }
+}
+
+static void aspeed_intc0_disable_intm(struct aspeed_intc0 *intc0)
+{
+ for (int i = 0; i < INTC0_INTM_BANK_NUM; i++)
+ writel(0, intc0->base + INTC0_INTMX_IER + (INTC0_INTMX_BANK_SIZE * i));
+}
+
+static int aspeed_intc0_probe(struct platform_device *pdev,
+ struct device_node *parent)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct irq_domain *parent_domain;
+ struct aspeed_intc0 *intc0;
+ int ret;
+
+ if (!parent) {
+ pr_err("missing parent interrupt node\n");
+ return -ENODEV;
+ }
+
+ intc0 = devm_kzalloc(&pdev->dev, sizeof(*intc0), GFP_KERNEL);
+ if (!intc0)
+ return -ENOMEM;
+
+ intc0->dev = &pdev->dev;
+ intc0->parent = parent;
+ intc0->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(intc0->base))
+ return PTR_ERR(intc0->base);
+
+ aspeed_intc0_disable_swint(intc0);
+ aspeed_intc0_disable_intbank(intc0);
+ aspeed_intc0_disable_intm(intc0);
+
+ raw_spin_lock_init(&intc0->intc_lock);
+
+ parent_domain = irq_find_host(parent);
+ if (!parent_domain) {
+ pr_err("unable to obtain parent domain\n");
+ return -ENODEV;
+ }
+
+ if (!of_device_is_compatible(parent, "arm,gic-v3"))
+ return -ENODEV;
+
+ intc0->local = irq_domain_create_hierarchy(parent_domain, 0, INT0_NUM,
+ of_fwnode_handle(node),
+ &aspeed_intc0_irq_domain_ops,
+ intc0);
+ if (!intc0->local)
+ return -ENOMEM;
+
+ ret = aspeed_intc_populate_ranges(&pdev->dev, &intc0->ranges);
+ if (ret < 0) {
+ irq_domain_remove(intc0->local);
+ return ret;
+ }
+
+ return 0;
+}
+
+IRQCHIP_PLATFORM_DRIVER_BEGIN(ast2700_intc0)
+IRQCHIP_MATCH("aspeed,ast2700-intc0", aspeed_intc0_probe)
+IRQCHIP_PLATFORM_DRIVER_END(ast2700_intc0)
diff --git a/drivers/irqchip/irq-ast2700-intc1.c b/drivers/irqchip/irq-ast2700-intc1.c
new file mode 100644
index 000000000000..09b9d5d743e6
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700-intc1.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Aspeed AST2700 Interrupt Controller.
+ *
+ * Copyright (C) 2026 ASPEED Technology Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/spinlock.h>
+
+#include "irq-ast2700.h"
+
+#define INTC1_IER 0x100
+#define INTC1_ISR 0x104
+#define INTC1_BANK_SIZE 0x10
+#define INTC1_SEL_BASE 0x80
+#define INTC1_SEL_BANK_SIZE 0x4
+#define INTC1_SEL_ROUTE_SIZE 0x20
+#define INTC1_IRQS_PER_BANK 32
+#define INTC1_BANK_NUM 6
+#define INTC1_ROUTE_NUM 7
+#define INTC1_IN_NUM 192
+#define INTC1_BOOTMCU_ROUTE 6
+#define INTC1_ROUTE_SELECTOR_BITS 3
+#define INTC1_ROUTE_IRQS_PER_GROUP 32
+#define INTC1_ROUTE_SHIFT 5
+
+struct aspeed_intc1 {
+ struct device *dev;
+ void __iomem *base;
+ raw_spinlock_t intc_lock;
+ struct irq_domain *local;
+ struct irq_domain *upstream;
+ struct aspeed_intc_interrupt_ranges ranges;
+};
+
+static void aspeed_intc1_disable_int(struct aspeed_intc1 *intc1)
+{
+ for (int i = 0; i < INTC1_BANK_NUM; i++)
+ writel(0, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * i));
+}
+
+static void aspeed_intc1_irq_handler(struct irq_desc *desc)
+{
+ struct aspeed_intc1 *intc1 = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned long bit, status;
+
+ chained_irq_enter(chip, desc);
+
+ for (int bank = 0; bank < INTC1_BANK_NUM; bank++) {
+ status = readl(intc1->base + INTC1_ISR + (INTC1_BANK_SIZE * bank));
+ if (!status)
+ continue;
+
+ for_each_set_bit(bit, &status, INTC1_IRQS_PER_BANK) {
+ generic_handle_domain_irq(intc1->local, (bank * INTC1_IRQS_PER_BANK) + bit);
+ writel(BIT(bit), intc1->base + INTC1_ISR + (INTC1_BANK_SIZE * bank));
+ }
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static void aspeed_intc1_irq_mask(struct irq_data *data)
+{
+ struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
+ int bank = data->hwirq / INTC1_IRQS_PER_BANK;
+ int bit = data->hwirq % INTC1_IRQS_PER_BANK;
+ u32 ier;
+
+ guard(raw_spinlock)(&intc1->intc_lock);
+ ier = readl(intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)) & ~BIT(bit);
+ writel(ier, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank));
+}
+
+static void aspeed_intc1_irq_unmask(struct irq_data *data)
+{
+ struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
+ int bank = data->hwirq / INTC1_IRQS_PER_BANK;
+ int bit = data->hwirq % INTC1_IRQS_PER_BANK;
+ u32 ier;
+
+ guard(raw_spinlock)(&intc1->intc_lock);
+ ier = readl(intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank)) | BIT(bit);
+ writel(ier, intc1->base + INTC1_IER + (INTC1_BANK_SIZE * bank));
+}
+
+static struct irq_chip aspeed_intc_chip = {
+ .name = "ASPEED INTC1",
+ .irq_mask = aspeed_intc1_irq_mask,
+ .irq_unmask = aspeed_intc1_irq_unmask,
+};
+
+static int aspeed_intc1_irq_domain_translate(struct irq_domain *domain,
+ struct irq_fwspec *fwspec,
+ unsigned long *hwirq,
+ unsigned int *type)
+{
+ if (fwspec->param_count != 1)
+ return -EINVAL;
+
+ *hwirq = fwspec->param[0];
+ *type = IRQ_TYPE_LEVEL_HIGH;
+ return 0;
+}
+
+static int aspeed_intc1_map_irq_domain(struct irq_domain *domain,
+ unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ irq_domain_set_info(domain, irq, hwirq, &aspeed_intc_chip,
+ domain->host_data, handle_level_irq, NULL, NULL);
+ return 0;
+}
+
+/*
+ * In-bound interrupts are progressively merged into one out-bound interrupt in
+ * groups of 32. Apply this fact to compress the route table in corresponding
+ * groups of 32.
+ */
+static const u32
+aspeed_intc1_routes[INTC1_IN_NUM / INTC1_ROUTE_IRQS_PER_GROUP][INTC1_ROUTE_NUM] = {
+ { 0, AST2700_INTC_INVALID_ROUTE, 10, 20, 30, 40, 50 },
+ { 1, AST2700_INTC_INVALID_ROUTE, 11, 21, 31, 41, 50 },
+ { 2, AST2700_INTC_INVALID_ROUTE, 12, 22, 32, 42, 50 },
+ { 3, AST2700_INTC_INVALID_ROUTE, 13, 23, 33, 43, 50 },
+ { 4, AST2700_INTC_INVALID_ROUTE, 14, 24, 34, 44, 50 },
+ { 5, AST2700_INTC_INVALID_ROUTE, 15, 25, 35, 45, 50 },
+};
+
+static int aspeed_intc1_irq_domain_activate(struct irq_domain *domain,
+ struct irq_data *data, bool reserve)
+{
+ struct aspeed_intc1 *intc1 = irq_data_get_irq_chip_data(data);
+ struct aspeed_intc_interrupt_range resolved;
+ int rc, bank, bit;
+ u32 mask;
+
+ if (WARN_ON_ONCE((data->hwirq >> INTC1_ROUTE_SHIFT) >= ARRAY_SIZE(aspeed_intc1_routes)))
+ return -EINVAL;
+
+ /*
+ * outpin may be an error if the upstream is the BootMCU APLIC node, or
+ * anything except a valid intc0 driver instance
+ */
+ rc = aspeed_intc0_resolve_route(intc1->upstream, INTC1_ROUTE_NUM,
+ aspeed_intc1_routes[data->hwirq >> INTC1_ROUTE_SHIFT],
+ intc1->ranges.nranges,
+ intc1->ranges.ranges, &resolved);
+ if (rc < 0) {
+ if (!fwnode_device_is_compatible(intc1->upstream->fwnode, "riscv,aplic")) {
+ dev_warn(intc1->dev,
+ "Failed to resolve interrupt route for hwirq %lu in domain %s\n",
+ data->hwirq, domain->name);
+ return rc;
+ }
+ rc = INTC1_BOOTMCU_ROUTE;
+ }
+
+ bank = data->hwirq / INTC1_IRQS_PER_BANK;
+ bit = data->hwirq % INTC1_IRQS_PER_BANK;
+ mask = BIT(bit);
+
+ guard(raw_spinlock)(&intc1->intc_lock);
+ for (int i = 0; i < INTC1_ROUTE_SELECTOR_BITS; i++) {
+ void __iomem *sel = intc1->base + INTC1_SEL_BASE +
+ (bank * INTC1_SEL_BANK_SIZE) +
+ (INTC1_SEL_ROUTE_SIZE * i);
+ u32 reg = readl(sel);
+
+ if (rc & BIT(i))
+ reg |= mask;
+ else
+ reg &= ~mask;
+
+ writel(reg, sel);
+ if (readl(sel) != reg)
+ return -EACCES;
+ }
+
+ return 0;
+}
+
+static const struct irq_domain_ops aspeed_intc1_irq_domain_ops = {
+ .map = aspeed_intc1_map_irq_domain,
+ .translate = aspeed_intc1_irq_domain_translate,
+ .activate = aspeed_intc1_irq_domain_activate,
+};
+
+static void aspeed_intc1_request_interrupts(struct aspeed_intc1 *intc1)
+{
+ for (unsigned int i = 0; i < intc1->ranges.nranges; i++) {
+ struct aspeed_intc_interrupt_range *r =
+ &intc1->ranges.ranges[i];
+
+ if (intc1->upstream !=
+ irq_find_matching_fwspec(&r->upstream,
+ intc1->upstream->bus_token))
+ continue;
+
+ for (u32 k = 0; k < r->count; k++) {
+ struct of_phandle_args parent_irq;
+ int irq;
+
+ parent_irq.np = to_of_node(r->upstream.fwnode);
+ parent_irq.args_count = 1;
+ parent_irq.args[0] =
+ intc1->ranges.ranges[i].upstream.param[ASPEED_INTC_RANGES_BASE] + k;
+
+ irq = irq_create_of_mapping(&parent_irq);
+ if (!irq)
+ continue;
+
+ irq_set_chained_handler_and_data(irq,
+ aspeed_intc1_irq_handler, intc1);
+ }
+ }
+}
+
+static int aspeed_intc1_probe(struct platform_device *pdev,
+ struct device_node *parent)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct aspeed_intc1 *intc1;
+ struct irq_domain *host;
+ int ret;
+
+ if (!parent) {
+ dev_err(&pdev->dev, "missing parent interrupt node\n");
+ return -ENODEV;
+ }
+
+ if (!of_device_is_compatible(parent, "aspeed,ast2700-intc0"))
+ return -ENODEV;
+
+ host = irq_find_host(parent);
+ if (!host)
+ return -ENODEV;
+
+ intc1 = devm_kzalloc(&pdev->dev, sizeof(*intc1), GFP_KERNEL);
+ if (!intc1)
+ return -ENOMEM;
+
+ intc1->dev = &pdev->dev;
+ intc1->upstream = host;
+ intc1->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(intc1->base))
+ return PTR_ERR(intc1->base);
+
+ aspeed_intc1_disable_int(intc1);
+
+ raw_spin_lock_init(&intc1->intc_lock);
+
+ intc1->local = irq_domain_create_linear(of_fwnode_handle(node),
+ INTC1_BANK_NUM * INTC1_IRQS_PER_BANK,
+ &aspeed_intc1_irq_domain_ops, intc1);
+ if (!intc1->local)
+ return -ENOMEM;
+
+ ret = aspeed_intc_populate_ranges(&pdev->dev, &intc1->ranges);
+ if (ret < 0) {
+ irq_domain_remove(intc1->local);
+ return ret;
+ }
+
+ aspeed_intc1_request_interrupts(intc1);
+
+ return 0;
+}
+
+IRQCHIP_PLATFORM_DRIVER_BEGIN(ast2700_intc1)
+IRQCHIP_MATCH("aspeed,ast2700-intc1", aspeed_intc1_probe)
+IRQCHIP_PLATFORM_DRIVER_END(ast2700_intc1)
diff --git a/drivers/irqchip/irq-ast2700.c b/drivers/irqchip/irq-ast2700.c
new file mode 100644
index 000000000000..280657480d5f
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Aspeed AST2700 Interrupt Controller.
+ *
+ * Copyright (C) 2026 ASPEED Technology Inc.
+ */
+#include "irq-ast2700.h"
+
+#define ASPEED_INTC_RANGE_FIXED_CELLS 3U
+#define ASPEED_INTC_RANGE_OFF_START 0U
+#define ASPEED_INTC_RANGE_OFF_COUNT 1U
+#define ASPEED_INTC_RANGE_OFF_PHANDLE 2U
+
+/**
+ * aspeed_intc_populate_ranges
+ * @dev: Device owning the interrupt controller node.
+ * @ranges: Destination for parsed range descriptors.
+ *
+ * Return: 0 on success, negative errno on error.
+ */
+int aspeed_intc_populate_ranges(struct device *dev,
+ struct aspeed_intc_interrupt_ranges *ranges)
+{
+ struct aspeed_intc_interrupt_range *arr;
+ const __be32 *pvs, *pve;
+ struct device_node *dn;
+ int len;
+
+ if (!dev || !ranges)
+ return -EINVAL;
+
+ dn = dev->of_node;
+
+ pvs = of_get_property(dn, "aspeed,interrupt-ranges", &len);
+ if (!pvs)
+ return -EINVAL;
+
+ if (len % sizeof(__be32))
+ return -EINVAL;
+
+ /* Over-estimate the range entry count for now */
+ ranges->ranges = devm_kmalloc_array(dev,
+ len / (ASPEED_INTC_RANGE_FIXED_CELLS * sizeof(__be32)),
+ sizeof(*ranges->ranges),
+ GFP_KERNEL);
+ if (!ranges->ranges)
+ return -ENOMEM;
+
+ pve = pvs + (len / sizeof(__be32));
+ for (unsigned int i = 0; pve - pvs >= ASPEED_INTC_RANGE_FIXED_CELLS; i++) {
+ struct aspeed_intc_interrupt_range *r;
+ struct device_node *target;
+ u32 target_cells;
+
+ target = of_find_node_by_phandle(be32_to_cpu(pvs[ASPEED_INTC_RANGE_OFF_PHANDLE]));
+ if (!target)
+ return -EINVAL;
+
+ if (of_property_read_u32(target, "#interrupt-cells",
+ &target_cells)) {
+ of_node_put(target);
+ return -EINVAL;
+ }
+
+ if (!target_cells || target_cells > IRQ_DOMAIN_IRQ_SPEC_PARAMS) {
+ of_node_put(target);
+ return -EINVAL;
+ }
+
+ if (pve - pvs < ASPEED_INTC_RANGE_FIXED_CELLS + target_cells) {
+ of_node_put(target);
+ return -EINVAL;
+ }
+
+ r = &ranges->ranges[i];
+ r->start = be32_to_cpu(pvs[ASPEED_INTC_RANGE_OFF_START]);
+ r->count = be32_to_cpu(pvs[ASPEED_INTC_RANGE_OFF_COUNT]);
+
+ {
+ struct of_phandle_args args = {
+ .np = target,
+ .args_count = target_cells,
+ };
+
+ for (u32 j = 0; j < target_cells; j++)
+ args.args[j] = be32_to_cpu(pvs[ASPEED_INTC_RANGE_FIXED_CELLS + j]);
+
+ of_phandle_args_to_fwspec(target, args.args,
+ args.args_count,
+ &r->upstream);
+ }
+
+ of_node_put(target);
+ pvs += ASPEED_INTC_RANGE_FIXED_CELLS + target_cells;
+ ranges->nranges++;
+ }
+
+ /* Re-fit the range array now we know the entry count */
+ arr = devm_krealloc_array(dev, ranges->ranges, ranges->nranges,
+ sizeof(*ranges->ranges), GFP_KERNEL);
+ if (!arr)
+ return -ENOMEM;
+ ranges->ranges = arr;
+
+ return 0;
+}
diff --git a/drivers/irqchip/irq-ast2700.h b/drivers/irqchip/irq-ast2700.h
new file mode 100644
index 000000000000..544f1af4c8ab
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Aspeed AST2700 Interrupt Controller.
+ *
+ * Copyright (C) 2026 ASPEED Technology Inc.
+ */
+#ifndef DRIVERS_IRQCHIP_AST2700
+#define DRIVERS_IRQCHIP_AST2700
+
+#include <linux/device.h>
+#include <linux/irqdomain.h>
+
+#define AST2700_INTC_INVALID_ROUTE (~0U)
+#define ASPEED_INTC_RANGES_BASE 0U
+#define ASPEED_INTC_RANGES_COUNT 1U
+
+struct aspeed_intc_interrupt_range {
+ u32 start;
+ u32 count;
+ struct irq_fwspec upstream;
+};
+
+struct aspeed_intc_interrupt_ranges {
+ struct aspeed_intc_interrupt_range *ranges;
+ unsigned int nranges;
+};
+
+struct aspeed_intc0 {
+ struct device *dev;
+ void __iomem *base;
+ raw_spinlock_t intc_lock;
+ struct irq_domain *local;
+ struct device_node *parent;
+ struct aspeed_intc_interrupt_ranges ranges;
+};
+
+int aspeed_intc_populate_ranges(struct device *dev,
+ struct aspeed_intc_interrupt_ranges *ranges);
+
+int aspeed_intc0_resolve_route(const struct irq_domain *c0domain,
+ size_t nc1outs,
+ const u32 *c1outs,
+ size_t nc1ranges,
+ const struct aspeed_intc_interrupt_range *c1ranges,
+ struct aspeed_intc_interrupt_range *resolved);
+
+#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v3 3/4] irqchip/ast2700-intc: Add KUnit tests for route resolution
From: Ryan Chen @ 2026-03-26 6:19 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Joel Stanley,
Andrew Jeffery, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Thomas Gleixner, Thomas Gleixner
Cc: linux-kernel, devicetree, linux-arm-kernel, linux-aspeed,
linux-riscv, Ryan Chen
In-Reply-To: <20260326-irqchip-v3-0-366739f57acf@aspeedtech.com>
Add a KUnit suite for aspeed_intc0_resolve_route().
Cover invalid arguments, invalid domain/range data, connected and
disconnected mappings, and malformed upstream range cases.
Signed-off-by: Ryan Chen <ryan_chen@aspeedtech.com>
---
Changes in v2:
- add line break before include "irq-ast2700.h"
- remove pointless newline.
- rename arm_gicv3_fwnode_read_string_array to
gicv3_fwnode_read_string_array
- add .kunitconfig file
---
drivers/irqchip/.kunitconfig | 5 +
drivers/irqchip/Kconfig | 11 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-ast2700-intc0-test.c | 473 +++++++++++++++++++++++++++++++
4 files changed, 490 insertions(+)
diff --git a/drivers/irqchip/.kunitconfig b/drivers/irqchip/.kunitconfig
new file mode 100644
index 000000000000..00a12703f635
--- /dev/null
+++ b/drivers/irqchip/.kunitconfig
@@ -0,0 +1,5 @@
+CONFIG_KUNIT=y
+CONFIG_OF=y
+CONFIG_COMPILE_TEST=y
+CONFIG_ASPEED_AST2700_INTC=y
+CONFIG_ASPEED_AST2700_INTC_TEST=y
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 0156fee89b2c..143af3f30a4b 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -122,6 +122,17 @@ config ASPEED_AST2700_INTC
If unsure, say N.
+config ASPEED_AST2700_INTC_TEST
+ bool "Tests for the ASPEED AST2700 Interrupt Controller"
+ depends on ASPEED_AST2700_INTC && KUNIT=y
+ default KUNIT_ALL_TESTS
+ help
+ Enable KUnit tests for AST2700 INTC route resolution.
+ The tests exercise error handling and route selection paths.
+ This option is intended for test builds.
+
+ If unsure, say N.
+
config ATMEL_AIC_IRQ
bool
select GENERIC_IRQ_CHIP
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 62790663f982..ac04a4b97797 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -90,6 +90,7 @@ obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o
obj-$(CONFIG_LS_EXTIRQ) += irq-ls-extirq.o
obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o
obj-$(CONFIG_ASPEED_AST2700_INTC) += irq-ast2700.o irq-ast2700-intc0.o irq-ast2700-intc1.o
+obj-$(CONFIG_ASPEED_AST2700_INTC_TEST) += irq-ast2700-intc0-test.o
obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-aspeed-scu-ic.o
obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-intc.o
obj-$(CONFIG_STM32MP_EXTI) += irq-stm32mp-exti.o
diff --git a/drivers/irqchip/irq-ast2700-intc0-test.c b/drivers/irqchip/irq-ast2700-intc0-test.c
new file mode 100644
index 000000000000..2aa00df72c04
--- /dev/null
+++ b/drivers/irqchip/irq-ast2700-intc0-test.c
@@ -0,0 +1,473 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Code Construct
+ */
+#include <kunit/test.h>
+
+#include "irq-ast2700.h"
+
+static void aspeed_intc0_resolve_route_bad_args(struct kunit *test)
+{
+ static const struct aspeed_intc_interrupt_range c1ranges[] = { 0 };
+ static const u32 c1outs[] = { 0 };
+ struct aspeed_intc_interrupt_range resolved;
+ const struct irq_domain c0domain = { 0 };
+ int rc;
+
+ rc = aspeed_intc0_resolve_route(NULL, 0, c1outs, 0, c1ranges, NULL);
+ KUNIT_EXPECT_EQ(test, rc, -EINVAL);
+
+ rc = aspeed_intc0_resolve_route(&c0domain, 0, c1outs,
+ ARRAY_SIZE(c1ranges), c1ranges,
+ &resolved);
+ KUNIT_EXPECT_EQ(test, rc, -ENOENT);
+
+ rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+ 0, c1ranges, &resolved);
+ KUNIT_EXPECT_EQ(test, rc, -ENOENT);
+}
+
+static int gicv3_fwnode_read_string_array(const struct fwnode_handle *fwnode,
+ const char *propname, const char **val, size_t nval)
+{
+ if (!propname)
+ return -EINVAL;
+
+ if (!val)
+ return 1;
+
+ if (WARN_ON(nval != 1))
+ return -EOVERFLOW;
+
+ *val = "arm,gic-v3";
+ return 1;
+}
+
+static const struct fwnode_operations arm_gicv3_fwnode_ops = {
+ .property_read_string_array = gicv3_fwnode_read_string_array,
+};
+
+static void aspeed_intc_resolve_route_invalid_c0domain(struct kunit *test)
+{
+ struct device_node intc0_node = {
+ .fwnode = { .ops = &arm_gicv3_fwnode_ops },
+ };
+ const struct irq_domain c0domain = { .fwnode = &intc0_node.fwnode };
+ static const struct aspeed_intc_interrupt_range c1ranges[] = { 0 };
+ static const u32 c1outs[] = { 0 };
+ struct aspeed_intc_interrupt_range resolved;
+ int rc;
+
+ rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+ ARRAY_SIZE(c1ranges), c1ranges,
+ &resolved);
+ KUNIT_EXPECT_NE(test, rc, 0);
+}
+
+static int
+aspeed_intc0_fwnode_read_string_array(const struct fwnode_handle *fwnode_handle,
+ const char *propname, const char **val,
+ size_t nval)
+{
+ if (!propname)
+ return -EINVAL;
+
+ if (!val)
+ return 1;
+
+ if (WARN_ON(nval != 1))
+ return -EOVERFLOW;
+
+ *val = "aspeed,ast2700-intc0";
+ return nval;
+}
+
+static const struct fwnode_operations intc0_fwnode_ops = {
+ .property_read_string_array = aspeed_intc0_fwnode_read_string_array,
+};
+
+static void
+aspeed_intc0_resolve_route_c1i1o1c0i1o1_connected(struct kunit *test)
+{
+ struct device_node intc0_node = {
+ .fwnode = { .ops = &intc0_fwnode_ops },
+ };
+ struct aspeed_intc_interrupt_range c1ranges[] = {
+ {
+ .start = 0,
+ .count = 1,
+ .upstream = {
+ .fwnode = &intc0_node.fwnode,
+ .param_count = 1,
+ .param = { 128 }
+ }
+ }
+ };
+ static const u32 c1outs[] = { 0 };
+ struct aspeed_intc_interrupt_range resolved;
+ struct aspeed_intc_interrupt_range intc0_ranges[] = {
+ {
+ .start = 128,
+ .count = 1,
+ .upstream = {
+ .fwnode = NULL,
+ .param_count = 0,
+ .param = { 0 },
+ }
+ }
+ };
+ struct aspeed_intc0 intc0 = {
+ .ranges = { .ranges = intc0_ranges, .nranges = ARRAY_SIZE(intc0_ranges), }
+ };
+ const struct irq_domain c0domain = {
+ .host_data = &intc0,
+ .fwnode = &intc0_node.fwnode
+ };
+ int rc;
+
+ rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+ ARRAY_SIZE(c1ranges), c1ranges,
+ &resolved);
+ KUNIT_EXPECT_EQ(test, rc, 0);
+ KUNIT_EXPECT_EQ(test, resolved.start, 0);
+ KUNIT_EXPECT_EQ(test, resolved.count, 1);
+ KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 128);
+}
+
+static void
+aspeed_intc0_resolve_route_c1i1o1c0i1o1_disconnected(struct kunit *test)
+{
+ struct device_node intc0_node = {
+ .fwnode = { .ops = &intc0_fwnode_ops },
+ };
+ struct aspeed_intc_interrupt_range c1ranges[] = {
+ {
+ .start = 0,
+ .count = 1,
+ .upstream = {
+ .fwnode = &intc0_node.fwnode,
+ .param_count = 1,
+ .param = { 128 }
+ }
+ }
+ };
+ static const u32 c1outs[] = { 0 };
+ struct aspeed_intc_interrupt_range resolved;
+ struct aspeed_intc_interrupt_range intc0_ranges[] = {
+ {
+ .start = 129,
+ .count = 1,
+ .upstream = {
+ .fwnode = NULL,
+ .param_count = 0,
+ .param = { 0 },
+ }
+ }
+ };
+ struct aspeed_intc0 intc0 = {
+ .ranges = {
+ .ranges = intc0_ranges,
+ .nranges = ARRAY_SIZE(intc0_ranges),
+ }
+ };
+ const struct irq_domain c0domain = {
+ .host_data = &intc0,
+ .fwnode = &intc0_node.fwnode
+ };
+ int rc;
+
+ rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+ ARRAY_SIZE(c1ranges), c1ranges,
+ &resolved);
+ KUNIT_EXPECT_NE(test, rc, 0);
+}
+
+static void aspeed_intc0_resolve_route_c1i1o1mc0i1o1(struct kunit *test)
+{
+ struct device_node intc0_node = {
+ .fwnode = { .ops = &intc0_fwnode_ops },
+ };
+ struct aspeed_intc_interrupt_range c1ranges[] = {
+ {
+ .start = 0,
+ .count = 1,
+ .upstream = {
+ .fwnode = &intc0_node.fwnode,
+ .param_count = 1,
+ .param = { 480 }
+ }
+ }
+ };
+ static const u32 c1outs[] = { 0 };
+ struct aspeed_intc_interrupt_range resolved;
+ struct aspeed_intc_interrupt_range intc0_ranges[] = {
+ {
+ .start = 192,
+ .count = 1,
+ .upstream = {
+ .fwnode = NULL,
+ .param_count = 0,
+ .param = { 0 },
+ }
+ }
+ };
+ struct aspeed_intc0 intc0 = {
+ .ranges = {
+ .ranges = intc0_ranges,
+ .nranges = ARRAY_SIZE(intc0_ranges),
+ }
+ };
+ const struct irq_domain c0domain = {
+ .host_data = &intc0,
+ .fwnode = &intc0_node.fwnode
+ };
+ int rc;
+
+ rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+ ARRAY_SIZE(c1ranges), c1ranges,
+ &resolved);
+ KUNIT_EXPECT_EQ(test, rc, 0);
+ KUNIT_EXPECT_EQ(test, resolved.start, 0);
+ KUNIT_EXPECT_EQ(test, resolved.count, 1);
+ KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 480);
+}
+
+static void aspeed_intc0_resolve_route_c1i2o2mc0i1o1(struct kunit *test)
+{
+ struct device_node intc0_node = {
+ .fwnode = { .ops = &intc0_fwnode_ops },
+ };
+ struct aspeed_intc_interrupt_range c1ranges[] = {
+ {
+ .start = 0,
+ .count = 1,
+ .upstream = {
+ .fwnode = &intc0_node.fwnode,
+ .param_count = 1,
+ .param = { 480 }
+ }
+ },
+ {
+ .start = 1,
+ .count = 1,
+ .upstream = {
+ .fwnode = &intc0_node.fwnode,
+ .param_count = 1,
+ .param = { 510 }
+ }
+ }
+ };
+ static const u32 c1outs[] = { 1 };
+ struct aspeed_intc_interrupt_range resolved;
+ struct aspeed_intc_interrupt_range intc0_ranges[] = {
+ {
+ .start = 208,
+ .count = 1,
+ .upstream = {
+ .fwnode = NULL,
+ .param_count = 0,
+ .param = { 0 },
+ }
+ }
+ };
+ struct aspeed_intc0 intc0 = {
+ .ranges = {
+ .ranges = intc0_ranges,
+ .nranges = ARRAY_SIZE(intc0_ranges),
+ }
+ };
+ const struct irq_domain c0domain = {
+ .host_data = &intc0,
+ .fwnode = &intc0_node.fwnode
+ };
+ int rc;
+
+ rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+ ARRAY_SIZE(c1ranges), c1ranges,
+ &resolved);
+ KUNIT_EXPECT_EQ(test, rc, 0);
+ KUNIT_EXPECT_EQ(test, resolved.start, 1);
+ KUNIT_EXPECT_EQ(test, resolved.count, 1);
+ KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 510);
+}
+
+static void aspeed_intc0_resolve_route_c1i1o1mc0i2o1(struct kunit *test)
+{
+ struct device_node intc0_node = {
+ .fwnode = { .ops = &intc0_fwnode_ops },
+ };
+ struct aspeed_intc_interrupt_range c1ranges[] = {
+ {
+ .start = 0,
+ .count = 1,
+ .upstream = {
+ .fwnode = &intc0_node.fwnode,
+ .param_count = 1,
+ .param = { 510 }
+ }
+ },
+ };
+ static const u32 c1outs[] = { 0 };
+ struct aspeed_intc_interrupt_range resolved;
+ struct aspeed_intc_interrupt_range intc0_ranges[] = {
+ {
+ .start = 192,
+ .count = 1,
+ .upstream = {
+ .fwnode = NULL,
+ .param_count = 0,
+ .param = {0},
+ }
+ },
+ {
+ .start = 208,
+ .count = 1,
+ .upstream = {
+ .fwnode = NULL,
+ .param_count = 0,
+ .param = {0},
+ }
+ }
+ };
+ struct aspeed_intc0 intc0 = {
+ .ranges = {
+ .ranges = intc0_ranges,
+ .nranges = ARRAY_SIZE(intc0_ranges),
+ }
+ };
+ const struct irq_domain c0domain = {
+ .host_data = &intc0,
+ .fwnode = &intc0_node.fwnode
+ };
+ int rc;
+
+ rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+ ARRAY_SIZE(c1ranges), c1ranges,
+ &resolved);
+ KUNIT_EXPECT_EQ(test, rc, 0);
+ KUNIT_EXPECT_EQ(test, resolved.start, 0);
+ KUNIT_EXPECT_EQ(test, resolved.count, 1);
+ KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 510);
+}
+
+static void aspeed_intc0_resolve_route_c1i1o2mc0i1o1_invalid(struct kunit *test)
+{
+ struct device_node intc0_node = {
+ .fwnode = { .ops = &intc0_fwnode_ops },
+ };
+ struct aspeed_intc_interrupt_range c1ranges[] = {
+ {
+ .start = 0,
+ .count = 1,
+ .upstream = {
+ .fwnode = &intc0_node.fwnode,
+ .param_count = 1,
+ .param = { 480 }
+ }
+ }
+ };
+ static const u32 c1outs[] = {
+ AST2700_INTC_INVALID_ROUTE, 0
+ };
+ struct aspeed_intc_interrupt_range resolved;
+ struct aspeed_intc_interrupt_range intc0_ranges[] = {
+ {
+ .start = 192,
+ .count = 1,
+ .upstream = {
+ .fwnode = NULL,
+ .param_count = 0,
+ .param = { 0 },
+ }
+ }
+ };
+ struct aspeed_intc0 intc0 = {
+ .ranges = {
+ .ranges = intc0_ranges,
+ .nranges = ARRAY_SIZE(intc0_ranges),
+ }
+ };
+ const struct irq_domain c0domain = {
+ .host_data = &intc0,
+ .fwnode = &intc0_node.fwnode
+ };
+ int rc;
+
+ rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+ ARRAY_SIZE(c1ranges), c1ranges,
+ &resolved);
+ KUNIT_EXPECT_EQ(test, rc, 1);
+ KUNIT_EXPECT_EQ(test, resolved.start, 0);
+ KUNIT_EXPECT_EQ(test, resolved.count, 1);
+ KUNIT_EXPECT_EQ(test, resolved.upstream.param[0], 480);
+}
+
+static void
+aspeed_intc0_resolve_route_c1i1o1mc0i1o1_bad_range_upstream(struct kunit *test)
+{
+ struct device_node intc0_node = {
+ .fwnode = { .ops = &intc0_fwnode_ops },
+ };
+ struct aspeed_intc_interrupt_range c1ranges[] = {
+ {
+ .start = 0,
+ .count = 1,
+ .upstream = {
+ .fwnode = &intc0_node.fwnode,
+ .param_count = 0,
+ .param = { 0 }
+ }
+ }
+ };
+ static const u32 c1outs[] = { 0 };
+ struct aspeed_intc_interrupt_range resolved;
+ struct aspeed_intc_interrupt_range intc0_ranges[] = {
+ {
+ .start = 0,
+ .count = 0,
+ .upstream = {
+ .fwnode = NULL,
+ .param_count = 0,
+ .param = { 0 },
+ }
+ }
+ };
+ struct aspeed_intc0 intc0 = {
+ .ranges = {
+ .ranges = intc0_ranges,
+ .nranges = ARRAY_SIZE(intc0_ranges),
+ }
+ };
+ const struct irq_domain c0domain = {
+ .host_data = &intc0,
+ .fwnode = &intc0_node.fwnode
+ };
+ int rc;
+
+ rc = aspeed_intc0_resolve_route(&c0domain, ARRAY_SIZE(c1outs), c1outs,
+ ARRAY_SIZE(c1ranges), c1ranges,
+ &resolved);
+ KUNIT_EXPECT_NE(test, rc, 0);
+}
+
+static struct kunit_case ast2700_intc0_test_cases[] = {
+ KUNIT_CASE(aspeed_intc0_resolve_route_bad_args),
+ KUNIT_CASE(aspeed_intc_resolve_route_invalid_c0domain),
+ KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1c0i1o1_connected),
+ KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1c0i1o1_disconnected),
+ KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1mc0i1o1),
+ KUNIT_CASE(aspeed_intc0_resolve_route_c1i2o2mc0i1o1),
+ KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1mc0i2o1),
+ KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o2mc0i1o1_invalid),
+ KUNIT_CASE(aspeed_intc0_resolve_route_c1i1o1mc0i1o1_bad_range_upstream),
+ {},
+};
+
+static struct kunit_suite ast2700_intc0_test_suite = {
+ .name = "ast2700-intc0",
+ .test_cases = ast2700_intc0_test_cases,
+};
+
+kunit_test_suite(ast2700_intc0_test_suite);
+
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related
* [PATCH v3 4/4] irqchip/aspeed-intc: Remove AST2700-A0 support
From: Ryan Chen @ 2026-03-26 6:19 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Joel Stanley,
Andrew Jeffery, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Thomas Gleixner, Thomas Gleixner
Cc: linux-kernel, devicetree, linux-arm-kernel, linux-aspeed,
linux-riscv, Ryan Chen
In-Reply-To: <20260326-irqchip-v3-0-366739f57acf@aspeedtech.com>
The existing AST2700 interrupt controller driver
("aspeed,ast2700-intc-ic") was written against the A0 pre-production
design.
From A1 onwards (retained in the A2 production silicon), the interrupt
fabric was re-architected: interrupt routing is programmable and
interrupt outputs can be directed to multiple upstream controllers
(PSP GIC, Secondary Service Processor (SSP) NVIC, Tertiary Service
Processor (TSP) NVIC, and Boot MCU interrupt controller). This design
requires route resolution and a controller hierarchy model which the
A0 driver cannot represent.
Remove driver support for A0 in favour of the driver for the A2
production design.
Signed-off-by: Ryan Chen <ryan_chen@aspeedtech.com>
---
drivers/irqchip/Makefile | 1 -
drivers/irqchip/irq-aspeed-intc.c | 139 --------------------------------------
2 files changed, 140 deletions(-)
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index ac04a4b97797..3d02441b3ee6 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -92,7 +92,6 @@ obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o
obj-$(CONFIG_ASPEED_AST2700_INTC) += irq-ast2700.o irq-ast2700-intc0.o irq-ast2700-intc1.o
obj-$(CONFIG_ASPEED_AST2700_INTC_TEST) += irq-ast2700-intc0-test.o
obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-aspeed-scu-ic.o
-obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-intc.o
obj-$(CONFIG_STM32MP_EXTI) += irq-stm32mp-exti.o
obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o
obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o
diff --git a/drivers/irqchip/irq-aspeed-intc.c b/drivers/irqchip/irq-aspeed-intc.c
deleted file mode 100644
index 4fb0dd8349da..000000000000
--- a/drivers/irqchip/irq-aspeed-intc.c
+++ /dev/null
@@ -1,139 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Aspeed Interrupt Controller.
- *
- * Copyright (C) 2023 ASPEED Technology Inc.
- */
-
-#include <linux/bitops.h>
-#include <linux/irq.h>
-#include <linux/irqchip.h>
-#include <linux/irqchip/chained_irq.h>
-#include <linux/irqdomain.h>
-#include <linux/of_address.h>
-#include <linux/of_irq.h>
-#include <linux/io.h>
-#include <linux/spinlock.h>
-
-#define INTC_INT_ENABLE_REG 0x00
-#define INTC_INT_STATUS_REG 0x04
-#define INTC_IRQS_PER_WORD 32
-
-struct aspeed_intc_ic {
- void __iomem *base;
- raw_spinlock_t gic_lock;
- raw_spinlock_t intc_lock;
- struct irq_domain *irq_domain;
-};
-
-static void aspeed_intc_ic_irq_handler(struct irq_desc *desc)
-{
- struct aspeed_intc_ic *intc_ic = irq_desc_get_handler_data(desc);
- struct irq_chip *chip = irq_desc_get_chip(desc);
-
- chained_irq_enter(chip, desc);
-
- scoped_guard(raw_spinlock, &intc_ic->gic_lock) {
- unsigned long bit, status;
-
- status = readl(intc_ic->base + INTC_INT_STATUS_REG);
- for_each_set_bit(bit, &status, INTC_IRQS_PER_WORD) {
- generic_handle_domain_irq(intc_ic->irq_domain, bit);
- writel(BIT(bit), intc_ic->base + INTC_INT_STATUS_REG);
- }
- }
-
- chained_irq_exit(chip, desc);
-}
-
-static void aspeed_intc_irq_mask(struct irq_data *data)
-{
- struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data);
- unsigned int mask = readl(intc_ic->base + INTC_INT_ENABLE_REG) & ~BIT(data->hwirq);
-
- guard(raw_spinlock)(&intc_ic->intc_lock);
- writel(mask, intc_ic->base + INTC_INT_ENABLE_REG);
-}
-
-static void aspeed_intc_irq_unmask(struct irq_data *data)
-{
- struct aspeed_intc_ic *intc_ic = irq_data_get_irq_chip_data(data);
- unsigned int unmask = readl(intc_ic->base + INTC_INT_ENABLE_REG) | BIT(data->hwirq);
-
- guard(raw_spinlock)(&intc_ic->intc_lock);
- writel(unmask, intc_ic->base + INTC_INT_ENABLE_REG);
-}
-
-static struct irq_chip aspeed_intc_chip = {
- .name = "ASPEED INTC",
- .irq_mask = aspeed_intc_irq_mask,
- .irq_unmask = aspeed_intc_irq_unmask,
-};
-
-static int aspeed_intc_ic_map_irq_domain(struct irq_domain *domain, unsigned int irq,
- irq_hw_number_t hwirq)
-{
- irq_set_chip_and_handler(irq, &aspeed_intc_chip, handle_level_irq);
- irq_set_chip_data(irq, domain->host_data);
-
- return 0;
-}
-
-static const struct irq_domain_ops aspeed_intc_ic_irq_domain_ops = {
- .map = aspeed_intc_ic_map_irq_domain,
-};
-
-static int __init aspeed_intc_ic_of_init(struct device_node *node,
- struct device_node *parent)
-{
- struct aspeed_intc_ic *intc_ic;
- int irq, i, ret = 0;
-
- intc_ic = kzalloc_obj(*intc_ic);
- if (!intc_ic)
- return -ENOMEM;
-
- intc_ic->base = of_iomap(node, 0);
- if (!intc_ic->base) {
- pr_err("Failed to iomap intc_ic base\n");
- ret = -ENOMEM;
- goto err_free_ic;
- }
- writel(0xffffffff, intc_ic->base + INTC_INT_STATUS_REG);
- writel(0x0, intc_ic->base + INTC_INT_ENABLE_REG);
-
- intc_ic->irq_domain = irq_domain_create_linear(of_fwnode_handle(node), INTC_IRQS_PER_WORD,
- &aspeed_intc_ic_irq_domain_ops, intc_ic);
- if (!intc_ic->irq_domain) {
- ret = -ENOMEM;
- goto err_iounmap;
- }
-
- raw_spin_lock_init(&intc_ic->gic_lock);
- raw_spin_lock_init(&intc_ic->intc_lock);
-
- /* Check all the irq numbers valid. If not, unmaps all the base and frees the data. */
- for (i = 0; i < of_irq_count(node); i++) {
- irq = irq_of_parse_and_map(node, i);
- if (!irq) {
- pr_err("Failed to get irq number\n");
- ret = -EINVAL;
- goto err_iounmap;
- }
- }
-
- for (i = 0; i < of_irq_count(node); i++) {
- irq = irq_of_parse_and_map(node, i);
- irq_set_chained_handler_and_data(irq, aspeed_intc_ic_irq_handler, intc_ic);
- }
-
- return 0;
-
-err_iounmap:
- iounmap(intc_ic->base);
-err_free_ic:
- kfree(intc_ic);
- return ret;
-}
-
-IRQCHIP_DECLARE(ast2700_intc_ic, "aspeed,ast2700-intc-ic", aspeed_intc_ic_of_init);
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v2] ARM: dts: aspeed: anacapa: Add eeprom device node for NFC adaptor board
From: Andrew Jeffery @ 2026-03-26 6:25 UTC (permalink / raw)
To: carl.lee
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Joel Stanley,
devicetree, linux-arm-kernel, linux-aspeed, linux-kernel,
peter.shen, colin.huang2
In-Reply-To: <20260309-arm-dts-aspeed-anacapa-add-eeprom-device-v2-1-91c7dde4b79d@amd.com>
Hi Carl,
Sorry for the delayed in review.
On Mon, 2026-03-09 at 11:46 +0800, Carl Lee via B4 Relay wrote:
> From: Carl Lee <carl.lee@amd.com>
>
> Add eeprom device node for NFC adaptor board FRU.
>
> Signed-off-by: Carl Lee <carl.lee@amd.com>
> ---
> Add eeprom device nodes used to store FRU data for the PRoT
> module and NFC adapter board on Anacapa platform.
This patch note seems inaccurate for the purpose of v2?
Doesn't matter too much in this instance, but should be fixed for any
subsequent revision. Which, speaking of ...
> ---
> Changes in v2:
> - Remove PRoT module eeprom commit since it is already included in another series under review.
> - Only include NFC adapter board eeprom node.
> - Link to v1: https://lore.kernel.org/r/20260309-arm-dts-aspeed-anacapa-add-eeprom-device-v1-0-45092310e0e6@amd.com
> ---
> arch/arm/boot/dts/aspeed/aspeed-bmc-facebook-anacapa.dts | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/arch/arm/boot/dts/aspeed/aspeed-bmc-facebook-anacapa.dts b/arch/arm/boot/dts/aspeed/aspeed-bmc-facebook-anacapa.dts
> index 2cb7bd128d24..69c41532fdfb 100644
> --- a/arch/arm/boot/dts/aspeed/aspeed-bmc-facebook-anacapa.dts
> +++ b/arch/arm/boot/dts/aspeed/aspeed-bmc-facebook-anacapa.dts
> @@ -815,6 +815,11 @@ i2c13mux0ch7: i2c@7 {
> #address-cells = <1>;
> #size-cells = <0>;
>
> + eeprom@50 {
> + compatible = "atmel,24c128";
> + reg = <0x50>;
> + };
> +
> nfc@28 {
... these nodes are out of order.
Can you please order them by ascending unit address, as per the DTS
coding style?
https://docs.kernel.org/devicetree/bindings/dts-coding-style.html#order-of-nodes
Andrew
> compatible = "nxp,nxp-nci-i2c";
> reg = <0x28>;
>
> ---
> base-commit: a0ae2a256046c0c5d3778d1a194ff2e171f16e5f
> change-id: 20260309-arm-dts-aspeed-anacapa-add-eeprom-device-a1aabe06a35b
>
> Best regards,
^ permalink raw reply
* Re: [PATCH v3 13/13] wifi: mt76: mt7925: enable MT7927 PCI device IDs
From: Sean Wang @ 2026-03-26 6:26 UTC (permalink / raw)
To: Javier Tia
Cc: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
Ming Yen Hsieh, Deren Wu, linux-wireless, linux-kernel,
linux-arm-kernel, linux-mediatek, Marcin FM,
Cristian-Florin Radoi, George Salukvadze, Evgeny Kapusta,
Samu Toljamo, Ariel Rosenfeld, Chapuis Dario,
Thibaut François, 张旭涵
In-Reply-To: <20260325-mt7927-wifi-support-v2-v3-13-5ca66c97a755@jetm.me>
Hi, Javier
On Wed, Mar 25, 2026 at 5:14 PM Javier Tia <floss@jetm.me> wrote:
>
> Add PCI device table entries for MT7927 hardware variants:
> - 14c3:7927 (ASUS ROG Crosshair X870E Hero, Lenovo Legion Pro 7)
> - 14c3:6639 (Foxconn/Azurewave modules)
> - 14c3:0738 (AMD RZ738 / MediaTek MT7927)
>
> This is the final patch in the series, enabling MT7927 device
> enumeration after all infrastructure (DMA, IRQ, HW init, band index,
> power management) is in place.
>
> Tested-by: Marcin FM <marcin@lgic.pl>
> Tested-by: Cristian-Florin Radoi <radoi.chris@gmail.com>
> Tested-by: George Salukvadze <giosal90@gmail.com>
> Tested-by: Evgeny Kapusta <3193631@gmail.com>
> Tested-by: Samu Toljamo <samu.toljamo@gmail.com>
> Tested-by: Ariel Rosenfeld <ariel.rosenfeld.750@gmail.com>
> Tested-by: Chapuis Dario <chapuisdario4@gmail.com>
> Tested-by: Thibaut François <tibo@humeurlibre.fr>
> Tested-by: 张旭涵 <Loong.0x00@gmail.com>
> Signed-off-by: Javier Tia <floss@jetm.me>
> ---
> drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 29 +++++++++++++++----------
> 1 file changed, 18 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> index 693e08f35d68..00c3601f14ff 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> @@ -16,6 +16,12 @@ static const struct pci_device_id mt7925_pci_device_table[] = {
> .driver_data = (kernel_ulong_t)MT7925_FIRMWARE_WM },
> { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0717),
> .driver_data = (kernel_ulong_t)MT7925_FIRMWARE_WM },
> + { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7927),
> + .driver_data = (kernel_ulong_t)MT7927_FIRMWARE_WM },
> + { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x6639),
> + .driver_data = (kernel_ulong_t)MT7927_FIRMWARE_WM },
> + { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0738),
> + .driver_data = (kernel_ulong_t)MT7927_FIRMWARE_WM },
> { },
> };
>
> @@ -530,7 +536,8 @@ static int mt7925_pci_probe(struct pci_dev *pdev,
> if (ret)
> goto err_free_pci_vec;
>
> - is_mt7927_hw = (pdev->device == 0x6639 || pdev->device == 0x7927);
> + is_mt7927_hw = (pdev->device == 0x6639 || pdev->device == 0x7927 ||
> + pdev->device == 0x0738);
>
> /* MT7927: CONNINFRA power domain and WFDMA register access are
> * unreliable with PCIe L1 active, causing throughput to drop
> @@ -546,16 +553,16 @@ static int mt7925_pci_probe(struct pci_dev *pdev,
> goto err_free_pci_vec;
> }
>
> - /* MT7927 firmware lacks the connac2 feature trailer, so
> - * mt792x_get_mac80211_ops() can't detect CNM support and
> - * replaces chanctx/ROC/mgd_prepare_tx ops with stubs.
> - * Force CNM and restore the original mt7925 ops.
> - */
> - if ((pdev->device == 0x6639 || pdev->device == 0x7927) &&
> - !(features & MT792x_FW_CAP_CNM)) {
> - features |= MT792x_FW_CAP_CNM;
> - memcpy(ops, &mt7925_ops, sizeof(*ops));
> - }
> + /* MT7927 firmware lacks the connac2 feature trailer, so
> + * mt792x_get_mac80211_ops() can't detect CNM support and
> + * replaces chanctx/ROC/mgd_prepare_tx ops with stubs.
> + * Force CNM and restore the original mt7925 ops.
> + */
> + if ((pdev->device == 0x6639 || pdev->device == 0x7927 ||
> + pdev->device == 0x0738) && !(features & MT792x_FW_CAP_CNM)) {
> + features |= MT792x_FW_CAP_CNM;
> + memcpy(ops, &mt7925_ops, sizeof(*ops));
> + }
I think the quirk should be applied before the CNM check in
mt792x_get_mac80211_ops().
That helper already owns the fw_features detection and the ops rewrite,
so adding another memcpy() later to restore mt7925_ops feels fragile.
The final ops selection is no longer decided in one place, and the extra
full memcpy() depends on the current call flow and implementation
details of mt792x_get_mac80211_ops().
If MT7927 should force MT792x_FW_CAP_CNM despite the missing trailer,
please fold that into mt792x_get_mac80211_ops() and keep the ops
selection in one place.
>
> mdev = mt76_alloc_device(&pdev->dev, sizeof(*dev), ops, &drv_ops);
> if (!mdev) {
>
> --
> 2.53.0
>
>
^ permalink raw reply
* [PATCH v3 net-next 00/14] Add preliminary NETC switch support for i.MX94
From: Wei Fang @ 2026-03-26 6:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, andrew
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
i.MX94 NETC (v4.3) integrates 802.1Q Ethernet switch functionality, the
switch provides advanced QoS with 8 traffic classes and a full range of
TSN standards capabilities. It has 3 user ports and 1 CPU port, and the
CPU port is connected to an internal ENETC through the pseduo link, so
instead of a back-to-back MAC, the lightweight "pseudo MAC" is used at
both ends of the pseudo link to transfer Ethernet frames. The pseudo
link provides a zero-copy interface (no serialization delay) and lower
power (less logic and memory).
Like most Ethernet switches, the NETC switch also supports a proprietary
switch tag, is used to carry in-band metadata information about frames.
This in-band metadata information can include the source port from which
the frame was received, what was the reason why this frame got forwarded
to the entity, and for the entity to indicate the precise destination
port of a frame. The NETC switch tag is added to frames after the source
MAC address. There are three types of switch tags, and each type has 1
to 4 subtypes, more details are as follows.
Forward switch tag (Type = 0): Represents forwarded frames.
- SubType = 0 - Normal frame processing.
To_Port switch tag (Type = 1): Represents frames that are to be sent to
a specific switch port.
- SubType = 0. No request to perform timestamping.
- SubType = 1. Request to perform one-step timestamping.
- SubType = 2. Request to perform two-step timestamping.
- SubType = 3. Request to perform both one-step timestamping and
two-step timestamping.
To_Host switch tag (Type = 2): Represents frames redirected or copied to
the switch management port.
- SubType = 0. Received frames redirected or copied to the switch
management port.
- SubType = 1. Received frames redirected or copied to the switch
management port with captured timestamp at the switch port where
the frame was received.
- SubType = 2. Transmit timestamp response (two-step timestamping).
Currently, this patch set supports Forward tag, SubType 0 of To_Port tag
and SubType 0 of To_Host tag. More tags will be supported in the future.
In addition, the switch supports NETC Table Management Protocol (NTMP),
some switch functionality is controlled using control messages sent to
the hardware using BD ring interface with 32B descriptors similar to the
packet Transmit BD ring used on ENETC. This interface is referred to as
the command BD ring. This is used to configure functionality where the
underlying resources may be shared between different entities or being
too large to configure using direct registers.
For this patch set, we have supported the following tables through the
command BD ring interface.
FDB Table: It contains forwarding and/or filtering information about MAC
addresses. The FDB table is used for MAC learning lookups and MAC
forwarding lookups.
VLAN Filter Table: It contains configuration and control information for
each VLAN configured on the switch.
Buffer Pool Table: It contains buffer pool configuration and operational
information. Each entry corresponds to a buffer pool. Currently, we use
this table to implement flow control feature on each port.
Ingress Port Filter Table: It contains a set of filters each capable of
classifying incoming traffic using a mix of L2, L3, and L4 parsed and
arbitrary field data. We use this table to implement host flood support
to the switch port.
The switch also supports other tables, and we will add more advanced
features through them in the future.
---
v3:
1. Improve the description of 'dsa,member' property in patch 2.
2. Remove netc_select_cbdr() and use spin_trylock_bh() instead.
3. Use pskb_may_pull() in netc_rcv().
4. Remove phylink_autoneg_inband() from netc_main.c
5. Remove netc_port_set_hd_flow_control() from netc_main.c.
6. Impove netc_mac_link_up(), remove unnecessary code from it.
7. Restore cfge if ntmp_bpt_update_entry() returns an error in
netc_port_set_tx_pause().
8. Improve netc_ipv_to_buffer_pool_mapping().
v2 link: https://lore.kernel.org/imx/20260323060752.1157031-1-wei.fang@nxp.com/
v2:
1. Use raw_smp_processor_id() in netc_select_cbdr() instead of
smp_processor_id().
2. Remove netc_port_free_mdio_bus() and netc_free_mdio_bus().
3. Correct the mask value in netc_port_set_mac_mode()
4. Rename net_port_set_rmii_mii_mac() to netc_port_set_rmii_mii_mac().
5. Check the return value of ntmp_bpt_update_entry() in
netc_switch_bpt_default_config().
6. Add some comments to avoid false positives from AI review.
v1 link: https://lore.kernel.org/imx/20260316094152.1558671-1-wei.fang@nxp.com/
---
Wei Fang (14):
dt-bindings: net: dsa: update the description of 'dsa,member' property
dt-bindings: net: dsa: add NETC switch
net: enetc: add pre-boot initialization for i.MX94 switch
net: enetc: add basic operations to the FDB table
net: enetc: add support for the "Add" operation to VLAN filter table
net: enetc: add support for the "Update" operation to buffer pool
table
net: enetc: add support for "Add" and "Delete" operations to IPFT
net: enetc: add multiple command BD rings support
net: dsa: add NETC switch tag support
net: dsa: netc: introduce NXP NETC switch driver for i.MX94
net: dsa: netc: add phylink MAC operations
net: dsa: netc: add more basic functions support
net: dsa: netc: initialize buffer bool table and implement
flow-control
net: dsa: netc: add support for the standardized counters
.../devicetree/bindings/net/dsa/dsa.yaml | 6 +-
.../bindings/net/dsa/nxp,netc-switch.yaml | 130 ++
MAINTAINERS | 11 +
drivers/net/dsa/Kconfig | 3 +
drivers/net/dsa/Makefile | 1 +
drivers/net/dsa/netc/Kconfig | 14 +
drivers/net/dsa/netc/Makefile | 3 +
drivers/net/dsa/netc/netc_ethtool.c | 192 ++
drivers/net/dsa/netc/netc_main.c | 1558 +++++++++++++++++
drivers/net/dsa/netc/netc_platform.c | 89 +
drivers/net/dsa/netc/netc_switch.h | 155 ++
drivers/net/dsa/netc/netc_switch_hw.h | 335 ++++
.../ethernet/freescale/enetc/netc_blk_ctrl.c | 188 +-
drivers/net/ethernet/freescale/enetc/ntmp.c | 383 +++-
.../ethernet/freescale/enetc/ntmp_private.h | 122 +-
include/linux/dsa/tag_netc.h | 14 +
include/linux/fsl/netc_global.h | 6 +
include/linux/fsl/ntmp.h | 235 ++-
include/net/dsa.h | 2 +
include/uapi/linux/if_ether.h | 1 +
net/dsa/Kconfig | 10 +
net/dsa/Makefile | 1 +
net/dsa/tag_netc.c | 185 ++
23 files changed, 3614 insertions(+), 30 deletions(-)
create mode 100644 Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
create mode 100644 drivers/net/dsa/netc/Kconfig
create mode 100644 drivers/net/dsa/netc/Makefile
create mode 100644 drivers/net/dsa/netc/netc_ethtool.c
create mode 100644 drivers/net/dsa/netc/netc_main.c
create mode 100644 drivers/net/dsa/netc/netc_platform.c
create mode 100644 drivers/net/dsa/netc/netc_switch.h
create mode 100644 drivers/net/dsa/netc/netc_switch_hw.h
create mode 100644 include/linux/dsa/tag_netc.h
create mode 100644 net/dsa/tag_netc.c
--
2.34.1
^ permalink raw reply
* [PATCH v3 net-next 01/14] dt-bindings: net: dsa: update the description of 'dsa,member' property
From: Wei Fang @ 2026-03-26 6:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, andrew
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260326062917.3552334-1-wei.fang@nxp.com>
The current description indicates that the 'dsa,member' property cannot
be set for a switch that is not part of any cluster. Vladimir thinks
that this is a case where the actual technical limitation was poorly
transposed into words when this restriction was first documented, in
commit 8c5ad1d6179d ("net: dsa: Document new binding").
The true technical limitation is that many DSA tagging protocols are
topology-unaware, and always call dsa_conduit_find_user() with a
switch_id of 0. Specifying a custom "dsa,member" property with a
non-zero switch_id would break them.
Therefore, for topology-aware switches, it is fine to specify this
property for them, even if they are not part of any cluster. Our NETC
switch is a good example which is topology-aware, the switch_id is
carried in the switch tag, but the switch_id 0 is reserved for VEPA
switch and cannot be used, so we need to use this property to assign
a non-zero switch_id for it.
Suggested-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
Documentation/devicetree/bindings/net/dsa/dsa.yaml | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/net/dsa/dsa.yaml b/Documentation/devicetree/bindings/net/dsa/dsa.yaml
index 2abd036578d1..801e1411e5c2 100644
--- a/Documentation/devicetree/bindings/net/dsa/dsa.yaml
+++ b/Documentation/devicetree/bindings/net/dsa/dsa.yaml
@@ -28,7 +28,11 @@ properties:
A two element list indicates which DSA cluster, and position within the
cluster a switch takes. <0 0> is cluster 0, switch 0. <0 1> is cluster 0,
switch 1. <1 0> is cluster 1, switch 0. A switch not part of any cluster
- (single device hanging off a CPU port) must not specify this property
+ (single device hanging off a CPU port) does not usually need to specify
+ this property, and then it becomes cluster 0, switch 0. For a topology
+ aware switch, its switch index can be specified through this property,
+ even if it is not part of any cluster. Also, topology-unaware switches
+ must always be defined as index 0 of their cluster.
$ref: /schemas/types.yaml#/definitions/uint32-array
additionalProperties: true
--
2.34.1
^ permalink raw reply related
* [PATCH v3 net-next 02/14] dt-bindings: net: dsa: add NETC switch
From: Wei Fang @ 2026-03-26 6:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, andrew
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260326062917.3552334-1-wei.fang@nxp.com>
Add bindings for NETC switch. This switch is a PCIe function of NETC IP,
it supports advanced QoS with 8 traffic classes and 4 drop resilience
levels, and a full range of TSN standards capabilities. The switch CPU
port connects to an internal ENETC port, which is also a PCIe function
of NETC IP. So these two ports use a light-weight "pseudo MAC" instead
of a back-to-back MAC, because the "pseudo MAC" provides the delineation
between switch and ENETC, this translates to lower power (less logic and
memory) and lower delay (as there is no serialization delay across this
link).
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
.../bindings/net/dsa/nxp,netc-switch.yaml | 130 ++++++++++++++++++
1 file changed, 130 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
diff --git a/Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml b/Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
new file mode 100644
index 000000000000..e34a4e3504c3
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
@@ -0,0 +1,130 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dsa/nxp,netc-switch.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NETC Switch family
+
+description:
+ The NETC presents itself as a multi-function PCIe Root Complex Integrated
+ Endpoint (RCiEP) and provides full 802.1Q Ethernet switch functionality,
+ advanced QoS with 8 traffic classes and 4 drop resilience levels, and a
+ full range of TSN standards capabilities.
+
+ The CPU port of the switch connects to an internal ENETC. The switch and
+ the internal ENETC are fully integrated into the NETC IP, a back-to-back
+ MAC is not required. Instead, a light-weight "pseudo MAC" provides the
+ delineation between the switch and ENETC. This translates to lower power
+ (less logic and memory) and lower delay (as there is no serialization
+ delay across this link).
+
+maintainers:
+ - Wei Fang <wei.fang@nxp.com>
+
+properties:
+ compatible:
+ enum:
+ - pci1131,eef2
+
+ reg:
+ maxItems: 1
+
+ dsa,member:
+ description:
+ The property indicates DSA cluster and switch index. For NETC switch,
+ the valid range of the switch index is 1 ~ 7, the index is reflected
+ in the switch tag as an indication of the switch ID where the frame
+ originated. The value 0 is reserved for ENETC VEPA switch, whose ID
+ is hardwired to zero.
+
+$ref: dsa.yaml#
+
+patternProperties:
+ "^(ethernet-)?ports$":
+ type: object
+ additionalProperties: true
+ patternProperties:
+ "^(ethernet-)?port@[0-9a-f]$":
+ type: object
+
+ $ref: dsa-port.yaml#
+
+ properties:
+ clocks:
+ items:
+ - description: MAC transmit/receive reference clock.
+
+ clock-names:
+ items:
+ - const: ref
+
+ mdio:
+ $ref: /schemas/net/mdio.yaml#
+ unevaluatedProperties: false
+ description:
+ Optional child node for switch port, otherwise use NETC EMDIO.
+
+ unevaluatedProperties: false
+
+required:
+ - compatible
+ - reg
+ - dsa,member
+
+allOf:
+ - $ref: /schemas/pci/pci-device.yaml
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ pcie {
+ #address-cells = <3>;
+ #size-cells = <2>;
+
+ ethernet-switch@0,2 {
+ compatible = "pci1131,eef2";
+ reg = <0x200 0 0 0 0>;
+ dsa,member = <0 1>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_switch>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ phy-handle = <ðphy0>;
+ phy-mode = "mii";
+ };
+
+ port@1 {
+ reg = <1>;
+ phy-handle = <ðphy1>;
+ phy-mode = "mii";
+ };
+
+ port@2 {
+ reg = <2>;
+ clocks = <&scmi_clk 103>;
+ clock-names = "ref";
+ phy-handle = <ðphy2>;
+ phy-mode = "rgmii-id";
+ };
+
+ port@3 {
+ reg = <3>;
+ ethernet = <&enetc3>;
+ phy-mode = "internal";
+
+ fixed-link {
+ speed = <2500>;
+ full-duplex;
+ pause;
+ };
+ };
+ };
+ };
+ };
--
2.34.1
^ permalink raw reply related
* [PATCH v3 net-next 03/14] net: enetc: add pre-boot initialization for i.MX94 switch
From: Wei Fang @ 2026-03-26 6:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, andrew
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260326062917.3552334-1-wei.fang@nxp.com>
Before probing the NETC switch driver, some pre-initialization needs to
be set in NETCMIX and IERB to ensure that the switch can work properly.
For example, i.MX94 NETC switch has three external ports and each port
is bound to a link. And each link needs to be configured so that it can
work properly, such as I/O variant and MII protocol.
In addition, the switch port 2 (MAC 2) and ENETC 0 (MAC 3) share the same
parallel interface, they cannot be used at the same time due to the SoC
constraint. And the MAC selection is controlled by the mac2_mac3_sel bit
of EXT_PIN_CONTROL register. Currently, the interface is set for ENETC 0
by default unless the switch port 2 is enabled in the DT node.
Like ENETC, each external port of the NETC switch can manage its external
PHY through its port MDIO registers. And the port can only access its own
external PHY by setting the PHY address to the LaBCR[MDIO_PHYAD_PRTAD].
If the accessed PHY address is not equal to LaBCR[MDIO_PHYAD_PRTAD], then
the MDIO access initiated by port MDIO will be invalid.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
.../ethernet/freescale/enetc/netc_blk_ctrl.c | 188 ++++++++++++++++--
1 file changed, 166 insertions(+), 22 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c b/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
index 92a0f824dae7..c21230f7a7fb 100644
--- a/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
+++ b/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
@@ -261,40 +261,112 @@ static int imx94_link_config(struct netc_blk_ctrl *priv,
}
static int imx94_enetc_link_config(struct netc_blk_ctrl *priv,
- struct device_node *np)
+ struct device_node *np,
+ bool *enetc0_en)
{
int link_id = imx94_enetc_get_link_id(np);
if (link_id < 0)
return link_id;
+ if (link_id == IMX94_ENETC0_LINK && of_device_is_available(np))
+ *enetc0_en = true;
+
return imx94_link_config(priv, np, link_id);
}
+static struct device_node *netc_get_switch_ports(struct device_node *np)
+{
+ struct device_node *ports;
+
+ ports = of_get_child_by_name(np, "ports");
+ if (!ports)
+ ports = of_get_child_by_name(np, "ethernet-ports");
+
+ return ports;
+}
+
+static int imx94_switch_link_config(struct netc_blk_ctrl *priv,
+ struct device_node *np,
+ bool *swp2_en)
+{
+ struct device_node *ports;
+ int port_id, err = 0;
+
+ ports = netc_get_switch_ports(np);
+ if (!ports)
+ return -ENODEV;
+
+ for_each_available_child_of_node_scoped(ports, child) {
+ if (of_property_read_u32(child, "reg", &port_id) < 0) {
+ err = -ENODEV;
+ goto end;
+ }
+
+ switch (port_id) {
+ case 0 ... 2: /* External ports */
+ err = imx94_link_config(priv, child, port_id);
+ if (err)
+ goto end;
+
+ if (port_id == 2)
+ *swp2_en = true;
+
+ break;
+ case 3: /* CPU port */
+ break;
+ default:
+ err = -EINVAL;
+ goto end;
+ }
+ }
+
+end:
+ of_node_put(ports);
+
+ return err;
+}
+
static int imx94_netcmix_init(struct platform_device *pdev)
{
struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
struct device_node *np = pdev->dev.of_node;
+ bool enetc0_en = false, swp2_en = false;
u32 val;
int err;
for_each_child_of_node_scoped(np, child) {
for_each_child_of_node_scoped(child, gchild) {
- if (!of_device_is_compatible(gchild, "pci1131,e101"))
- continue;
-
- err = imx94_enetc_link_config(priv, gchild);
- if (err)
- return err;
+ if (of_device_is_compatible(gchild, "pci1131,e101")) {
+ err = imx94_enetc_link_config(priv, gchild,
+ &enetc0_en);
+ if (err)
+ return err;
+ } else if (of_device_is_compatible(gchild,
+ "pci1131,eef2")) {
+ err = imx94_switch_link_config(priv, gchild,
+ &swp2_en);
+ if (err)
+ return err;
+ }
}
}
- /* ENETC 0 and switch port 2 share the same parallel interface.
- * Currently, the switch is not supported, so this interface is
- * used by ENETC 0 by default.
+ if (enetc0_en && swp2_en) {
+ dev_err(&pdev->dev,
+ "Cannot enable swp2 and enetc0 at the same time\n");
+ return -EINVAL;
+ }
+
+ /* ENETC 0 and switch port 2 share the same parallel interface, they
+ * cannot be enabled at the same time. The interface is set for the
+ * ENETC 0 by default unless the switch port 2 is enabled in the DTS.
*/
val = netc_reg_read(priv->netcmix, IMX94_EXT_PIN_CONTROL);
- val |= MAC2_MAC3_SEL;
+ if (!swp2_en)
+ val |= MAC2_MAC3_SEL;
+ else
+ val &= ~MAC2_MAC3_SEL;
netc_reg_write(priv->netcmix, IMX94_EXT_PIN_CONTROL, val);
return 0;
@@ -610,6 +682,77 @@ static int imx94_enetc_mdio_phyaddr_config(struct netc_blk_ctrl *priv,
return 0;
}
+static int imx94_ierb_enetc_init(struct netc_blk_ctrl *priv,
+ struct device_node *np,
+ u32 phy_mask)
+{
+ int err;
+
+ err = imx94_enetc_update_tid(priv, np);
+ if (err)
+ return err;
+
+ return imx94_enetc_mdio_phyaddr_config(priv, np, phy_mask);
+}
+
+static int imx94_switch_mdio_phyaddr_config(struct netc_blk_ctrl *priv,
+ struct device_node *np,
+ int port_id, u32 phy_mask)
+{
+ int addr;
+
+ /* The switch has 3 external ports at most */
+ if (port_id > 2)
+ return 0;
+
+ addr = netc_get_phy_addr(np);
+ if (addr < 0) {
+ if (addr == -ENODEV)
+ return 0;
+
+ return addr;
+ }
+
+ if (phy_mask & BIT(addr)) {
+ dev_err(&priv->pdev->dev,
+ "Found same PHY address in EMDIO and switch node\n");
+ return -EINVAL;
+ }
+
+ netc_reg_write(priv->ierb, IERB_LBCR(port_id),
+ LBCR_MDIO_PHYAD_PRTAD(addr));
+
+ return 0;
+}
+
+static int imx94_ierb_switch_init(struct netc_blk_ctrl *priv,
+ struct device_node *np,
+ u32 phy_mask)
+{
+ struct device_node *ports;
+ int port_id, err = 0;
+
+ ports = netc_get_switch_ports(np);
+ if (!ports)
+ return -ENODEV;
+
+ for_each_available_child_of_node_scoped(ports, child) {
+ err = of_property_read_u32(child, "reg", &port_id);
+ if (err)
+ goto end;
+
+ err = imx94_switch_mdio_phyaddr_config(priv, child,
+ port_id, phy_mask);
+ if (err)
+ goto end;
+ }
+
+end:
+ of_node_put(ports);
+
+ return err;
+}
+
static int imx94_ierb_init(struct platform_device *pdev)
{
struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
@@ -625,17 +768,18 @@ static int imx94_ierb_init(struct platform_device *pdev)
for_each_child_of_node_scoped(np, child) {
for_each_child_of_node_scoped(child, gchild) {
- if (!of_device_is_compatible(gchild, "pci1131,e101"))
- continue;
-
- err = imx94_enetc_update_tid(priv, gchild);
- if (err)
- return err;
-
- err = imx94_enetc_mdio_phyaddr_config(priv, gchild,
- phy_mask);
- if (err)
- return err;
+ if (of_device_is_compatible(gchild, "pci1131,e101")) {
+ err = imx94_ierb_enetc_init(priv, gchild,
+ phy_mask);
+ if (err)
+ return err;
+ } else if (of_device_is_compatible(gchild,
+ "pci1131,eef2")) {
+ err = imx94_ierb_switch_init(priv, gchild,
+ phy_mask);
+ if (err)
+ return err;
+ }
}
}
--
2.34.1
^ permalink raw reply related
* [PATCH v3 net-next 04/14] net: enetc: add basic operations to the FDB table
From: Wei Fang @ 2026-03-26 6:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, andrew
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260326062917.3552334-1-wei.fang@nxp.com>
The FDB table is used for MAC learning lookups and MAC forwarding lookups.
Each table entry includes information such as a FID and MAC address that
may be unicast or multicast and a forwarding destination field containing
a port bitmap identifying the associated port(s) with the MAC address.
FDB table entries can be static or dynamic. Static entries are added from
software whereby dynamic entries are added either by software or by the
hardware as MAC addresses are learned in the datapath.
The FDB table can only be managed by the command BD ring using table
management protocol version 2.0. Table management command operations Add,
Delete, Update and Query are supported. And the FDB table supports three
access methods: Entry ID, Exact Match Key Element and Search. This patch
adds the following basic supports to the FDB table.
ntmp_fdbt_update_entry() - update the configuration element data of a
specified FDB entry
ntmp_fdbt_delete_entry() - delete a specified FDB entry
ntmp_fdbt_add_entry() - add an entry into the FDB table
ntmp_fdbt_search_port_entry() - Search the FDB entry on the specified
port based on RESUME_ENTRY_ID.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 201 +++++++++++++++++-
.../ethernet/freescale/enetc/ntmp_private.h | 61 +++++-
include/linux/fsl/ntmp.h | 69 +++++-
3 files changed, 328 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 703752995e93..a4a99954baf2 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
* NETC NTMP (NETC Table Management Protocol) 2.0 Library
- * Copyright 2025 NXP
+ * Copyright 2025-2026 NXP
*/
#include <linux/dma-mapping.h>
@@ -20,11 +20,15 @@
/* Define NTMP Table ID */
#define NTMP_MAFT_ID 1
#define NTMP_RSST_ID 3
+#define NTMP_FDBT_ID 15
/* Generic Update Actions for most tables */
#define NTMP_GEN_UA_CFGEU BIT(0)
#define NTMP_GEN_UA_STSEU BIT(1)
+/* Query Action: 0: Full query, 1: Only query entry ID */
+#define NTMP_QA_ENTRY_ID 1
+
#define NTMP_ENTRY_ID_SIZE 4
#define RSST_ENTRY_NUM 64
#define RSST_STSE_DATA_SIZE(n) ((n) * 8)
@@ -225,6 +229,8 @@ static const char *ntmp_table_name(int tbl_id)
return "MAC Address Filter Table";
case NTMP_RSST_ID:
return "RSS Table";
+ case NTMP_FDBT_ID:
+ return "FDB Table";
default:
return "Unknown Table";
}
@@ -453,5 +459,198 @@ int ntmp_rsst_query_entry(struct ntmp_user *user, u32 *table, int count)
}
EXPORT_SYMBOL_GPL(ntmp_rsst_query_entry);
+/**
+ * ntmp_fdbt_add_entry - add an entry into the FDB table
+ * @user: target ntmp_user struct
+ * @entry_id: returned value, the entry ID of the new added entry
+ * @keye: key element data
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
+ const struct fdbt_keye_data *keye,
+ const struct fdbt_cfge_data *cfge)
+{
+ struct ntmp_dma_buf data = {
+ .dev = user->dev,
+ .size = sizeof(struct fdbt_req_ua),
+ };
+ struct fdbt_resp_query *resp;
+ struct fdbt_req_ua *req;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ err = ntmp_alloc_data_mem(&data, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, NTMP_QA_ENTRY_ID,
+ NTMP_GEN_UA_CFGEU);
+ req->ak.exact.keye = *keye;
+ req->cfge = *cfge;
+
+ len = NTMP_LEN(data.size, sizeof(*resp));
+ /* The entry ID is allotted by hardware, so we need to perform
+ * a query action after the add action to get the entry ID from
+ * hardware.
+ */
+ ntmp_fill_request_hdr(&cbd, data.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_AQ, NTMP_AM_EXACT_KEY);
+ err = netc_xmit_ntmp_cmd(user, &cbd);
+ if (err) {
+ dev_err(user->dev, "Failed to add %s entry, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err));
+ goto end;
+ }
+
+ if (entry_id) {
+ resp = (struct fdbt_resp_query *)req;
+ *entry_id = le32_to_cpu(resp->entry_id);
+ }
+
+end:
+ ntmp_free_data_mem(&data);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_add_entry);
+
+/**
+ * ntmp_fdbt_update_entry - update the configuration element data of the
+ * specified FDB entry
+ * @user: target ntmp_user struct
+ * @entry_id: the specified entry ID of the FDB table
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct fdbt_cfge_data *cfge)
+{
+ struct ntmp_dma_buf data = {
+ .dev = user->dev,
+ .size = sizeof(struct fdbt_req_ua),
+ };
+ struct fdbt_req_ua *req;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ err = ntmp_alloc_data_mem(&data, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, NTMP_GEN_UA_CFGEU);
+ req->ak.eid.entry_id = cpu_to_le32(entry_id);
+ req->cfge = *cfge;
+
+ /* Request header */
+ len = NTMP_LEN(data.size, NTMP_STATUS_RESP_LEN);
+ ntmp_fill_request_hdr(&cbd, data.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_UPDATE, NTMP_AM_ENTRY_ID);
+ err = netc_xmit_ntmp_cmd(user, &cbd);
+ if (err)
+ dev_err(user->dev, "Failed to update %s entry, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err));
+
+ ntmp_free_data_mem(&data);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_update_entry);
+
+/**
+ * ntmp_fdbt_delete_entry - delete the specified FDB entry
+ * @user: target ntmp_user struct
+ * @entry_id: the specified ID of the FDB entry
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id)
+{
+ u32 req_len = sizeof(struct fdbt_req_qd);
+
+ return ntmp_delete_entry_by_id(user, NTMP_FDBT_ID,
+ user->tbl.fdbt_ver,
+ entry_id, req_len,
+ NTMP_STATUS_RESP_LEN);
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_entry);
+
+/**
+ * ntmp_fdbt_search_port_entry - Search the FDB entry on the specified
+ * port based on RESUME_ENTRY_ID
+ * @user: target ntmp_user struct
+ * @port: the specified switch port ID
+ * @resume_entry_id: it is both an input and an output. As an input, it
+ * represents the FDB entry ID to be searched. If it is a NULL entry ID,
+ * it indicates that the first FDB entry for that port is being searched.
+ * As an output, it represents the next FDB entry ID to be searched.
+ * @entry: returned value, the response data of the searched FDB entry
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
+ u32 *resume_entry_id,
+ struct fdbt_entry_data *entry)
+{
+ struct ntmp_dma_buf data = {
+ .dev = user->dev,
+ .size = sizeof(struct fdbt_req_qd),
+ };
+ struct fdbt_resp_query *resp;
+ struct fdbt_req_qd *req;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ err = ntmp_alloc_data_mem(&data, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, 0);
+ req->ak.search.resume_eid = cpu_to_le32(*resume_entry_id);
+ req->ak.search.cfge.port_bitmap = cpu_to_le32(BIT(port));
+ /* Match CFGE_DATA[PORT_BITMAP] field */
+ req->ak.search.cfge_mc = FDBT_CFGE_MC_PORT_BITMAP;
+
+ /* Request header */
+ len = NTMP_LEN(data.size, sizeof(*resp));
+ ntmp_fill_request_hdr(&cbd, data.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_QUERY, NTMP_AM_SEARCH);
+
+ err = netc_xmit_ntmp_cmd(user, &cbd);
+ if (err) {
+ dev_err(user->dev,
+ "Failed to search %s entry on port %d, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), port, ERR_PTR(err));
+ goto end;
+ }
+
+ if (!cbd.resp_hdr.num_matched) {
+ entry->entry_id = NTMP_NULL_ENTRY_ID;
+ *resume_entry_id = NTMP_NULL_ENTRY_ID;
+ goto end;
+ }
+
+ resp = (struct fdbt_resp_query *)req;
+ *resume_entry_id = le32_to_cpu(resp->status);
+ entry->entry_id = le32_to_cpu(resp->entry_id);
+ entry->keye = resp->keye;
+ entry->cfge = resp->cfge;
+ entry->acte = resp->acte;
+
+end:
+ ntmp_free_data_mem(&data);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_search_port_entry);
+
MODULE_DESCRIPTION("NXP NETC Library");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index 34394e40fddd..8999eafe1920 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
/*
* NTMP table request and response data buffer formats
- * Copyright 2025 NXP
+ * Copyright 2025-2026 NXP
*/
#ifndef __NTMP_PRIVATE_H
@@ -11,6 +11,7 @@
#include <linux/fsl/ntmp.h>
#define NTMP_EID_REQ_LEN 8
+#define NTMP_STATUS_RESP_LEN 4
#define NETC_CBDR_BD_NUM 256
union netc_cbd {
@@ -27,6 +28,7 @@ union netc_cbd {
#define NTMP_CMD_QUERY BIT(2)
#define NTMP_CMD_ADD BIT(3)
#define NTMP_CMD_QU (NTMP_CMD_QUERY | NTMP_CMD_UPDATE)
+#define NTMP_CMD_AQ (NTMP_CMD_ADD | NTMP_CMD_QUERY)
u8 access_method;
#define NTMP_ACCESS_METHOD GENMASK(7, 4)
#define NTMP_AM_ENTRY_ID 0
@@ -101,4 +103,61 @@ struct rsst_req_update {
u8 groups[];
};
+/* Access Key Format of FDB Table */
+struct fdbt_ak_eid {
+ __le32 entry_id;
+ __le32 resv[7];
+};
+
+struct fdbt_ak_exact {
+ struct fdbt_keye_data keye;
+ __le32 resv[5];
+};
+
+struct fdbt_ak_search {
+ __le32 resume_eid;
+ struct fdbt_keye_data keye;
+ struct fdbt_cfge_data cfge;
+ u8 acte;
+ u8 keye_mc;
+#define FDBT_KEYE_MAC GENMASK(1, 0)
+ u8 cfge_mc;
+#define FDBT_CFGE_MC GENMASK(2, 0)
+#define FDBT_CFGE_MC_ANY 0
+#define FDBT_CFGE_MC_DYNAMIC 1
+#define FDBT_CFGE_MC_PORT_BITMAP 2
+#define FDBT_CFGE_MC_DYNAMIC_AND_PORT_BITMAP 3
+ u8 acte_mc;
+#define FDBT_ACTE_MC BIT(0)
+};
+
+union fdbt_access_key {
+ struct fdbt_ak_eid eid;
+ struct fdbt_ak_exact exact;
+ struct fdbt_ak_search search;
+};
+
+/* FDB Table Request Data Buffer Format of Update and Add actions */
+struct fdbt_req_ua {
+ struct ntmp_cmn_req_data crd;
+ union fdbt_access_key ak;
+ struct fdbt_cfge_data cfge;
+};
+
+/* FDB Table Request Data Buffer Format of Query and Delete actions */
+struct fdbt_req_qd {
+ struct ntmp_cmn_req_data crd;
+ union fdbt_access_key ak;
+};
+
+/* FDB Table Response Data Buffer Format of Query action */
+struct fdbt_resp_query {
+ __le32 status;
+ __le32 entry_id;
+ struct fdbt_keye_data keye;
+ struct fdbt_cfge_data cfge;
+ u8 acte;
+ u8 resv[3];
+};
+
#endif
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 916dc4fe7de3..a9f3e6cbf422 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -1,11 +1,13 @@
/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
-/* Copyright 2025 NXP */
+/* Copyright 2025-2026 NXP */
#ifndef __NETC_NTMP_H
#define __NETC_NTMP_H
#include <linux/bitops.h>
#include <linux/if_ether.h>
+#define NTMP_NULL_ENTRY_ID 0xffffffffU
+
struct maft_keye_data {
u8 mac_addr[ETH_ALEN];
__le16 resv;
@@ -29,6 +31,7 @@ struct netc_cbdr_regs {
struct netc_tbl_vers {
u8 maft_ver;
u8 rsst_ver;
+ u8 fdbt_ver;
};
struct netc_cbdr {
@@ -61,6 +64,36 @@ struct maft_entry_data {
struct maft_cfge_data cfge;
};
+struct fdbt_keye_data {
+ u8 mac_addr[ETH_ALEN]; /* big-endian */
+ __le16 resv0;
+ __le16 fid;
+#define FDBT_FID GENMASK(11, 0)
+ __le16 resv1;
+};
+
+struct fdbt_cfge_data {
+ __le32 port_bitmap;
+#define FDBT_PORT_BITMAP GENMASK(23, 0)
+ __le32 cfg;
+#define FDBT_OETEID GENMASK(1, 0)
+#define FDBT_EPORT GENMASK(6, 2)
+#define FDBT_IMIRE BIT(7)
+#define FDBT_CTD GENMASK(10, 9)
+#define FDBT_DYNAMIC BIT(11)
+#define FDBT_TIMECAPE BIT(12)
+ __le32 et_eid;
+};
+
+struct fdbt_entry_data {
+ u32 entry_id;
+ struct fdbt_keye_data keye;
+ struct fdbt_cfge_data cfge;
+ u8 acte;
+#define FDBT_ACT_CNT GENMASK(6, 0)
+#define FDBT_ACT_FLAG BIT(7)
+};
+
#if IS_ENABLED(CONFIG_NXP_NETC_LIB)
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
@@ -76,6 +109,15 @@ int ntmp_rsst_update_entry(struct ntmp_user *user, const u32 *table,
int count);
int ntmp_rsst_query_entry(struct ntmp_user *user,
u32 *table, int count);
+int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
+ const struct fdbt_keye_data *keye,
+ const struct fdbt_cfge_data *cfge);
+int ntmp_fdbt_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct fdbt_cfge_data *cfge);
+int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id);
+int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
+ u32 *resume_entry_id,
+ struct fdbt_entry_data *entry);
#else
static inline int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs)
@@ -116,6 +158,31 @@ static inline int ntmp_rsst_query_entry(struct ntmp_user *user,
return 0;
}
+static inline int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
+ const struct fdbt_keye_data *keye,
+ const struct fdbt_cfge_data *data)
+{
+ return 0;
+}
+
+static inline int ntmp_fdbt_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct fdbt_cfge_data *cfge)
+{
+ return 0;
+}
+
+static inline int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id)
+{
+ return 0;
+}
+
+static inline int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
+ u32 *resume_entry_id,
+ struct fdbt_entry_data *entry)
+{
+ return 0;
+}
+
#endif
#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v3 net-next 05/14] net: enetc: add support for the "Add" operation to VLAN filter table
From: Wei Fang @ 2026-03-26 6:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, andrew
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260326062917.3552334-1-wei.fang@nxp.com>
The VLAN filter table contains configuration and control information for
each VLAN configured on the switch. Each VLAN entry includes the VLAN
port membership, which FID to use in the FDB lookup, which spanning tree
group to use, the egress frame modification actions to apply to a frame
exiting form this VLAN, and various configuration and control parameters
for this VLAN.
The VLAN filter table can only be managed by the command BD ring using
table management protocol version 2.0. The table supports Add, Delete,
Update and Query operations. And the table supports 3 access methods:
Entry ID, Exact Match Key Element and Search. But currently we only add
the ntmp_vft_add_entry() helper to support the upcoming switch driver to
add an entry to the VLAN filter table. Other interfaces will be added in
the future.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 50 +++++++++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 19 +++++++
include/linux/fsl/ntmp.h | 30 +++++++++++
3 files changed, 99 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index a4a99954baf2..d7d8a37078d1 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -21,6 +21,7 @@
#define NTMP_MAFT_ID 1
#define NTMP_RSST_ID 3
#define NTMP_FDBT_ID 15
+#define NTMP_VFT_ID 18
/* Generic Update Actions for most tables */
#define NTMP_GEN_UA_CFGEU BIT(0)
@@ -231,6 +232,8 @@ static const char *ntmp_table_name(int tbl_id)
return "RSS Table";
case NTMP_FDBT_ID:
return "FDB Table";
+ case NTMP_VFT_ID:
+ return "VLAN Filter Table";
default:
return "Unknown Table";
}
@@ -652,5 +655,52 @@ int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
}
EXPORT_SYMBOL_GPL(ntmp_fdbt_search_port_entry);
+/**
+ * ntmp_vft_add_entry - add an entry into the VLAN filter table
+ * @user: target ntmp_user struct
+ * @vid: VLAN ID
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
+ const struct vft_cfge_data *cfge)
+{
+ struct ntmp_dma_buf data = {
+ .dev = user->dev,
+ .size = sizeof(struct vft_req_ua),
+ };
+ struct vft_req_ua *req;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ err = ntmp_alloc_data_mem(&data, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0,
+ NTMP_GEN_UA_CFGEU);
+ req->ak.exact.vid = cpu_to_le16(vid);
+ req->cfge = *cfge;
+
+ /* Request header */
+ len = NTMP_LEN(data.size, NTMP_STATUS_RESP_LEN);
+ ntmp_fill_request_hdr(&cbd, data.dma, len, NTMP_VFT_ID,
+ NTMP_CMD_ADD, NTMP_AM_EXACT_KEY);
+
+ err = netc_xmit_ntmp_cmd(user, &cbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to add %s entry, vid: %u, err: %pe\n",
+ ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err));
+
+ ntmp_free_data_mem(&data);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_vft_add_entry);
+
MODULE_DESCRIPTION("NXP NETC Library");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index 8999eafe1920..d2a6399b0a36 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -160,4 +160,23 @@ struct fdbt_resp_query {
u8 resv[3];
};
+/* Access Key Format of VLAN Filter Table */
+struct vft_ak_exact {
+ __le16 vid; /* bit0~11: VLAN ID, other bits are reserved */
+ __le16 resv;
+};
+
+union vft_access_key {
+ __le32 entry_id; /* entry_id match */
+ struct vft_ak_exact exact;
+ __le32 resume_entry_id; /* search */
+};
+
+/* VLAN Filter Table Request Data Buffer Format of Update and Add actions */
+struct vft_req_ua {
+ struct ntmp_cmn_req_data crd;
+ union vft_access_key ak;
+ struct vft_cfge_data cfge;
+};
+
#endif
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index a9f3e6cbf422..2aedea17307f 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -32,6 +32,7 @@ struct netc_tbl_vers {
u8 maft_ver;
u8 rsst_ver;
u8 fdbt_ver;
+ u8 vft_ver;
};
struct netc_cbdr {
@@ -94,6 +95,27 @@ struct fdbt_entry_data {
#define FDBT_ACT_FLAG BIT(7)
};
+struct vft_cfge_data {
+ __le32 bitmap_stg;
+#define VFT_PORT_MEMBERSHIP GENMASK(23, 0)
+#define VFT_STG_ID_MASK GENMASK(27, 24)
+#define VFT_STG_ID(g) FIELD_PREP(VFT_STG_ID_MASK, (g))
+ __le16 fid;
+#define VFT_FID GENMASK(11, 0)
+ __le16 cfg;
+#define VFT_MLO GENMASK(2, 0)
+#define VFT_MFO GENMASK(4, 3)
+#define VFT_IPMFE BIT(6)
+#define VFT_IPMFLE BIT(7)
+#define VFT_PGA BIT(8)
+#define VFT_SFDA BIT(10)
+#define VFT_OSFDA BIT(11)
+#define VFT_FDBAFSS BIT(12)
+ __le32 eta_port_bitmap;
+#define VFT_ETA_PORT_BITMAP GENMASK(23, 0)
+ __le32 et_eid;
+};
+
#if IS_ENABLED(CONFIG_NXP_NETC_LIB)
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
@@ -118,6 +140,8 @@ int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id);
int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
u32 *resume_entry_id,
struct fdbt_entry_data *entry);
+int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
+ const struct vft_cfge_data *cfge);
#else
static inline int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs)
@@ -183,6 +207,12 @@ static inline int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
return 0;
}
+static inline int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
+ const struct vft_cfge_data *cfge)
+{
+ return 0;
+}
+
#endif
#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v3 net-next 06/14] net: enetc: add support for the "Update" operation to buffer pool table
From: Wei Fang @ 2026-03-26 6:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, andrew
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260326062917.3552334-1-wei.fang@nxp.com>
The buffer pool table contains buffer pool configuration and operational
information. Each entry corresponds to a buffer pool. The Entry ID value
represents the buffer pool ID to access.
The buffer pool table is a static bounded index table, buffer pools are
always present and enabled. It only supports Update and Query operations,
This patch only adds ntmp_bpt_update_entry() helper to support updating
the specified entry of the buffer pool table. Query action to the table
will be added in the future.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 39 +++++++++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 6 +++
include/linux/fsl/ntmp.h | 32 +++++++++++++++
3 files changed, 77 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index d7d8a37078d1..3c59b355c142 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -22,11 +22,15 @@
#define NTMP_RSST_ID 3
#define NTMP_FDBT_ID 15
#define NTMP_VFT_ID 18
+#define NTMP_BPT_ID 41
/* Generic Update Actions for most tables */
#define NTMP_GEN_UA_CFGEU BIT(0)
#define NTMP_GEN_UA_STSEU BIT(1)
+/* Specific Update Actions for some tables */
+#define BPT_UA_BPSEU BIT(1)
+
/* Query Action: 0: Full query, 1: Only query entry ID */
#define NTMP_QA_ENTRY_ID 1
@@ -234,6 +238,8 @@ static const char *ntmp_table_name(int tbl_id)
return "FDB Table";
case NTMP_VFT_ID:
return "VLAN Filter Table";
+ case NTMP_BPT_ID:
+ return "Buffer Pool Table";
default:
return "Unknown Table";
}
@@ -702,5 +708,38 @@ int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
}
EXPORT_SYMBOL_GPL(ntmp_vft_add_entry);
+int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct bpt_cfge_data *cfge)
+{
+ struct ntmp_dma_buf data = {
+ .dev = user->dev,
+ .size = sizeof(struct bpt_req_update),
+ };
+ struct bpt_req_update *req;
+ union netc_cbd cbd;
+ int err;
+
+ err = ntmp_alloc_data_mem(&data, (void **)&req);
+ if (err)
+ return err;
+
+ ntmp_fill_crd_eid(&req->rbe, user->tbl.bpt_ver, 0,
+ NTMP_GEN_UA_CFGEU | BPT_UA_BPSEU, entry_id);
+ req->cfge = *cfge;
+ ntmp_fill_request_hdr(&cbd, data.dma, NTMP_LEN(data.size, 0),
+ NTMP_BPT_ID, NTMP_CMD_UPDATE, NTMP_AM_ENTRY_ID);
+
+ err = netc_xmit_ntmp_cmd(user, &cbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to update %s entry 0x%x, err: %pe\n",
+ ntmp_table_name(NTMP_BPT_ID), entry_id, ERR_PTR(err));
+
+ ntmp_free_data_mem(&data);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_bpt_update_entry);
+
MODULE_DESCRIPTION("NXP NETC Library");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index d2a6399b0a36..7d50af7745c7 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -179,4 +179,10 @@ struct vft_req_ua {
struct vft_cfge_data cfge;
};
+/* Buffer Pool Table Request Data Buffer Format of Update action */
+struct bpt_req_update {
+ struct ntmp_req_by_eid rbe;
+ struct bpt_cfge_data cfge;
+};
+
#endif
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 2aedea17307f..a54945dcdc61 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -33,6 +33,7 @@ struct netc_tbl_vers {
u8 rsst_ver;
u8 fdbt_ver;
u8 vft_ver;
+ u8 bpt_ver;
};
struct netc_cbdr {
@@ -116,6 +117,29 @@ struct vft_cfge_data {
__le32 et_eid;
};
+struct bpt_bpse_data {
+ __le32 amount_used;
+ __le32 amount_used_hwm;
+ u8 bpd_fc_state;
+#define BPT_FC_STATE BIT(0)
+#define BPT_BPD BIT(1)
+} __packed;
+
+struct bpt_cfge_data {
+ u8 fccfg_sbpen;
+#define BPT_SBP_EN BIT(0)
+#define BPT_FC_CFG GENMASK(2, 1)
+#define BPT_FC_CFG_EN_BPFC 1
+ u8 pfc_vector;
+ __le16 max_thresh;
+ __le16 fc_on_thresh;
+ __le16 fc_off_thresh;
+ __le16 sbp_thresh;
+ __le16 resv;
+ __le32 sbp_eid;
+ __le32 fc_ports;
+};
+
#if IS_ENABLED(CONFIG_NXP_NETC_LIB)
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
@@ -142,6 +166,8 @@ int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
struct fdbt_entry_data *entry);
int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
const struct vft_cfge_data *cfge);
+int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct bpt_cfge_data *cfge);
#else
static inline int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs)
@@ -213,6 +239,12 @@ static inline int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
return 0;
}
+static inline int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct bpt_cfge_data *cfge)
+{
+ return 0;
+}
+
#endif
#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v3 net-next 07/14] net: enetc: add support for "Add" and "Delete" operations to IPFT
From: Wei Fang @ 2026-03-26 6:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, andrew
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260326062917.3552334-1-wei.fang@nxp.com>
The ingress port filter table (IPFT )contains a set of filters each
capable of classifying incoming traffic using a mix of L2, L3, and L4
parsed and arbitrary field data. As a result of a filter match, several
actions can be specified such as on whether to deny or allow a frame,
overriding internal QoS attributes associated with the frame and setting
parameters for the subsequent frame processing functions, such as stream
identification, policing, ingress mirroring. Each entry corresponds to a
filter. The ingress port filter entries are added using a precedence
value. If a frame matches multiple entries, the entry with the higher
precedence is used. Currently, this patch only adds "Add" and "Delete"
operations to the ingress port filter table. These two interfaces will
be used by both ENETC driver and NETC switch driver.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 76 +++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 36 ++++++
include/linux/fsl/ntmp.h | 104 ++++++++++++++++++
3 files changed, 216 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 3c59b355c142..960d5be8ec42 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -20,6 +20,7 @@
/* Define NTMP Table ID */
#define NTMP_MAFT_ID 1
#define NTMP_RSST_ID 3
+#define NTMP_IPFT_ID 13
#define NTMP_FDBT_ID 15
#define NTMP_VFT_ID 18
#define NTMP_BPT_ID 41
@@ -234,6 +235,8 @@ static const char *ntmp_table_name(int tbl_id)
return "MAC Address Filter Table";
case NTMP_RSST_ID:
return "RSS Table";
+ case NTMP_IPFT_ID:
+ return "Ingress Port Filter Table";
case NTMP_FDBT_ID:
return "FDB Table";
case NTMP_VFT_ID:
@@ -468,6 +471,79 @@ int ntmp_rsst_query_entry(struct ntmp_user *user, u32 *table, int count)
}
EXPORT_SYMBOL_GPL(ntmp_rsst_query_entry);
+/**
+ * ntmp_ipft_add_entry - add an entry into the ingress port filter table
+ * @user: target ntmp_user struct
+ * @entry: the entry data, entry->cfge (configuration element data) and
+ * entry->keye (key element data) are used as input. Since the entry ID
+ * is assigned by the hardware, so entry->entry_id is a returned value
+ * for the driver to use, the driver can update/delete/query the entry
+ * based on the entry_id.
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ipft_add_entry(struct ntmp_user *user,
+ struct ipft_entry_data *entry)
+{
+ struct ntmp_dma_buf data = {
+ .dev = user->dev,
+ .size = sizeof(struct ipft_resp_query),
+ };
+ struct ipft_resp_query *resp;
+ struct ipft_req_ua *req;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ err = ntmp_alloc_data_mem(&data, (void **)&req);
+ if (err)
+ return err;
+
+ ntmp_fill_crd(&req->crd, user->tbl.ipft_ver, NTMP_QA_ENTRY_ID,
+ NTMP_GEN_UA_CFGEU | NTMP_GEN_UA_STSEU);
+ req->ak.keye = entry->keye;
+ req->cfge = entry->cfge;
+
+ len = NTMP_LEN(sizeof(*req), data.size);
+ ntmp_fill_request_hdr(&cbd, data.dma, len, NTMP_IPFT_ID,
+ NTMP_CMD_AQ, NTMP_AM_TERNARY_KEY);
+
+ err = netc_xmit_ntmp_cmd(user, &cbd);
+ if (err) {
+ dev_err(user->dev, "Failed to add %s entry, err: %pe\n",
+ ntmp_table_name(NTMP_IPFT_ID), ERR_PTR(err));
+
+ goto end;
+ }
+
+ resp = (struct ipft_resp_query *)req;
+ entry->entry_id = le32_to_cpu(resp->entry_id);
+
+end:
+ ntmp_free_data_mem(&data);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_ipft_add_entry);
+
+/**
+ * ntmp_ipft_delete_entry - delete a specified ingress port filter table entry
+ * @user: target ntmp_user struct
+ * @entry_id: the specified ID of the ingress port filter table entry
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ipft_delete_entry(struct ntmp_user *user, u32 entry_id)
+{
+ u32 req_len = sizeof(struct ipft_req_qd);
+
+ return ntmp_delete_entry_by_id(user, NTMP_IPFT_ID,
+ user->tbl.ipft_ver,
+ entry_id, req_len,
+ NTMP_STATUS_RESP_LEN);
+}
+EXPORT_SYMBOL_GPL(ntmp_ipft_delete_entry);
+
/**
* ntmp_fdbt_add_entry - add an entry into the FDB table
* @user: target ntmp_user struct
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index 7d50af7745c7..6d519d59a433 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -103,6 +103,42 @@ struct rsst_req_update {
u8 groups[];
};
+/* Ingress Port Filter Table Response Data Buffer Format of Query action */
+struct ipft_resp_query {
+ __le32 status;
+ __le32 entry_id;
+ struct ipft_keye_data keye;
+ __le64 match_count; /* STSE_DATA */
+ struct ipft_cfge_data cfge;
+} __packed;
+
+struct ipft_ak_eid {
+ __le32 entry_id;
+ __le32 resv[52];
+};
+
+union ipft_access_key {
+ struct ipft_ak_eid eid;
+ struct ipft_keye_data keye;
+};
+
+/* Ingress Port Filter Table Request Data Buffer Format of Update and
+ * Add actions
+ */
+struct ipft_req_ua {
+ struct ntmp_cmn_req_data crd;
+ union ipft_access_key ak;
+ struct ipft_cfge_data cfge;
+};
+
+/* Ingress Port Filter Table Request Data Buffer Format of Query and
+ * Delete actions
+ */
+struct ipft_req_qd {
+ struct ntmp_req_by_eid rbe;
+ __le32 resv[52];
+};
+
/* Access Key Format of FDB Table */
struct fdbt_ak_eid {
__le32 entry_id;
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index a54945dcdc61..efda35e5e22c 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -7,6 +7,7 @@
#include <linux/if_ether.h>
#define NTMP_NULL_ENTRY_ID 0xffffffffU
+#define IPFT_MAX_PLD_LEN 24
struct maft_keye_data {
u8 mac_addr[ETH_ALEN];
@@ -34,6 +35,7 @@ struct netc_tbl_vers {
u8 fdbt_ver;
u8 vft_ver;
u8 bpt_ver;
+ u8 ipft_ver;
};
struct netc_cbdr {
@@ -66,6 +68,94 @@ struct maft_entry_data {
struct maft_cfge_data cfge;
};
+struct ipft_pld_byte {
+ u8 data;
+ u8 mask;
+};
+
+struct ipft_keye_data {
+ __le16 precedence;
+ __le16 resv0[3];
+ __le16 frm_attr_flags;
+#define IPFT_FAF_OVLAN BIT(2)
+#define IPFT_FAF_IVLAN BIT(3)
+#define IPFT_FAF_IP_HDR BIT(7)
+#define IPFT_FAF_IP_VER6 BIT(8)
+#define IPFT_FAF_L4_CODE GENMASK(11, 10)
+#define IPFT_FAF_TCP_HDR 1
+#define IPFT_FAF_UDP_HDR 2
+#define IPFT_FAF_SCTP_HDR 3
+#define IPFT_FAF_WOL_MAGIC BIT(12)
+ __le16 frm_attr_flags_mask;
+ __le16 dscp;
+#define IPFT_DSCP GENMASK(5, 0)
+#define IPFT_DSCP_MASK GENMASK(11, 0)
+#define IPFT_DSCP_MASK_ALL 0x3f
+ __le16 src_port; /* This field is reserved for ENETC */
+#define IPFT_SRC_PORT GENMASK(4, 0)
+#define IPFT_SRC_PORT_MASK GENMASK(9, 5)
+#define IPFT_SRC_PORT_MASK_ALL 0x1f
+ __be16 outer_vlan_tci;
+ __be16 outer_vlan_tci_mask;
+ u8 dmac[ETH_ALEN];
+ u8 dmac_mask[ETH_ALEN];
+ u8 smac[ETH_ALEN];
+ u8 smac_mask[ETH_ALEN];
+ __be16 inner_vlan_tci;
+ __be16 inner_vlan_tci_mask;
+ __be16 ethertype;
+ __be16 ethertype_mask;
+ u8 ip_protocol;
+ u8 ip_protocol_mask;
+ __le16 resv1[7];
+ __be32 ip_src[4];
+ __le32 resv2[2];
+ __be32 ip_src_mask[4];
+ __be16 l4_src_port;
+ __be16 l4_src_port_mask;
+ __le32 resv3;
+ __be32 ip_dst[4];
+ __le32 resv4[2];
+ __be32 ip_dst_mask[4];
+ __be16 l4_dst_port;
+ __be16 l4_dst_port_mask;
+ __le32 resv5;
+ struct ipft_pld_byte byte[IPFT_MAX_PLD_LEN];
+};
+
+struct ipft_cfge_data {
+ __le32 cfg;
+#define IPFT_IPV GENMASK(3, 0)
+#define IPFT_OIPV BIT(4)
+#define IPFT_DR GENMASK(6, 5)
+#define IPFT_ODR BIT(7)
+#define IPFT_FLTFA GENMASK(10, 8)
+#define IPFT_FLTFA_DISCARD 0
+#define IPFT_FLTFA_PERMIT 1
+/* Redirect is only for switch */
+#define IPFT_FLTFA_REDIRECT 2
+#define IPFT_IMIRE BIT(11)
+#define IPFT_WOLTE BIT(12)
+#define IPFT_FLTA GENMASK(14, 13)
+#define IPFT_FLTA_RP 1
+#define IPFT_FLTA_IS 2
+#define IPFT_FLTA_SI_BITMAP 3
+#define IPFT_RPR GENMASK(16, 15)
+#define IPFT_CTD BIT(17)
+#define IPFT_HR GENMASK(21, 18)
+#define IPFT_TIMECAPE BIT(22)
+#define IPFT_RRT BIT(23)
+#define IPFT_BL2F BIT(24)
+#define IPFT_EVMEID GENMASK(31, 28)
+ __le32 flta_tgt;
+};
+
+struct ipft_entry_data {
+ u32 entry_id; /* hardware assigns entry ID */
+ struct ipft_keye_data keye;
+ struct ipft_cfge_data cfge;
+};
+
struct fdbt_keye_data {
u8 mac_addr[ETH_ALEN]; /* big-endian */
__le16 resv0;
@@ -155,6 +245,9 @@ int ntmp_rsst_update_entry(struct ntmp_user *user, const u32 *table,
int count);
int ntmp_rsst_query_entry(struct ntmp_user *user,
u32 *table, int count);
+int ntmp_ipft_add_entry(struct ntmp_user *user,
+ struct ipft_entry_data *entry);
+int ntmp_ipft_delete_entry(struct ntmp_user *user, u32 entry_id);
int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
const struct fdbt_keye_data *keye,
const struct fdbt_cfge_data *cfge);
@@ -208,6 +301,17 @@ static inline int ntmp_rsst_query_entry(struct ntmp_user *user,
return 0;
}
+static inline int ntmp_ipft_add_entry(struct ntmp_user *user,
+ struct ipft_entry_data *entry)
+{
+ return 0;
+}
+
+static inline int ntmp_ipft_delete_entry(struct ntmp_user *user, u32 entry_id)
+{
+ return 0;
+}
+
static inline int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
const struct fdbt_keye_data *keye,
const struct fdbt_cfge_data *data)
--
2.34.1
^ permalink raw reply related
* [PATCH v3 net-next 08/14] net: enetc: add multiple command BD rings support
From: Wei Fang @ 2026-03-26 6:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, andrew
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260326062917.3552334-1-wei.fang@nxp.com>
All the tables of NETC switch are managed through the command BD ring,
but unlike ENETC, the switch has two command BD rings, if the current
ring is busy, the switch driver can switch to another ring to manage
the table. Currently, the NTMP driver does not support multiple rings.
Therefore, netc_select_cbdr() is added to select a appropriate ring to
execute the command for the switch.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 960d5be8ec42..5679b866af48 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -125,12 +125,21 @@ static int netc_xmit_ntmp_cmd(struct ntmp_user *user, union netc_cbd *cbd)
u16 status;
u32 val;
- /* Currently only i.MX95 ENETC is supported, and it only has one
- * command BD ring
+ for (i = 0; i < user->cbdr_num; i++) {
+ cbdr = &user->ring[i];
+ if (spin_trylock_bh(&cbdr->ring_lock))
+ break;
+ }
+
+ /* If all command BD rings are locked, we need to select
+ * one of them and wait for it.
*/
- cbdr = &user->ring[0];
+ if (i == user->cbdr_num) {
+ int cpu = raw_smp_processor_id();
- spin_lock_bh(&cbdr->ring_lock);
+ cbdr = &user->ring[cpu % user->cbdr_num];
+ spin_lock_bh(&cbdr->ring_lock);
+ }
if (unlikely(!ntmp_get_free_cbd_num(cbdr)))
ntmp_clean_cbdr(cbdr);
--
2.34.1
^ permalink raw reply related
* [PATCH v3 net-next 09/14] net: dsa: add NETC switch tag support
From: Wei Fang @ 2026-03-26 6:29 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux, andrew
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260326062917.3552334-1-wei.fang@nxp.com>
The NXP NETC switch tag is a proprietary header added to frames after the
source MAC address. The switch tag has 3 types, and each type has 1 ~ 4
subtypes, the details are as follows.
Forward NXP switch tag (Type=0): Represents forwarded frames.
- SubType = 0 - Normal frame processing.
To_Port NXP switch tag (Type=1): Represents frames that are to be sent
to a specific switch port.
- SubType = 0. No request to perform timestamping.
- SubType = 1. Request to perform one-step timestamping.
- SubType = 2. Request to perform two-step timestamping.
- SubType = 3. Request to perform both one-step timestamping and
two-step timestamping.
To_Host NXP switch tag (Type=2): Represents frames redirected or copied
to the switch management port.
- SubType = 0. Received frames redirected or copied to the switch
management port.
- SubType = 1. Received frames redirected or copied to the switch
management port with captured timestamp at the switch port where
the frame was received.
- SubType = 2. Transmit timestamp response (two-step timestamping).
In addition, the length of different type switch tag is different, the
minimum length is 6 bytes, the maximum length is 14 bytes. Currently,
Forward tag, SubType 0 of To_Port tag and Subtype 0 of To_Host tag are
supported. More tags will be supported in the future.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
include/linux/dsa/tag_netc.h | 14 +++
include/net/dsa.h | 2 +
include/uapi/linux/if_ether.h | 1 +
net/dsa/Kconfig | 10 ++
net/dsa/Makefile | 1 +
net/dsa/tag_netc.c | 185 ++++++++++++++++++++++++++++++++++
6 files changed, 213 insertions(+)
create mode 100644 include/linux/dsa/tag_netc.h
create mode 100644 net/dsa/tag_netc.c
diff --git a/include/linux/dsa/tag_netc.h b/include/linux/dsa/tag_netc.h
new file mode 100644
index 000000000000..fe964722e5b0
--- /dev/null
+++ b/include/linux/dsa/tag_netc.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright 2025-2026 NXP
+ */
+
+#ifndef __NET_DSA_TAG_NETC_H
+#define __NET_DSA_TAG_NETC_H
+
+#include <linux/skbuff.h>
+#include <net/dsa.h>
+
+#define NETC_TAG_MAX_LEN 14
+
+#endif
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 6c17446f3dcc..6bed824d1f07 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -58,6 +58,7 @@ struct tc_action;
#define DSA_TAG_PROTO_YT921X_VALUE 30
#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31
#define DSA_TAG_PROTO_MXL862_VALUE 32
+#define DSA_TAG_PROTO_NETC_VALUE 33
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -93,6 +94,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_YT921X = DSA_TAG_PROTO_YT921X_VALUE,
DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE,
DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE,
+ DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE,
};
struct dsa_switch;
diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
index df9d44a11540..fb5efc8e06cc 100644
--- a/include/uapi/linux/if_ether.h
+++ b/include/uapi/linux/if_ether.h
@@ -123,6 +123,7 @@
#define ETH_P_DSA_A5PSW 0xE001 /* A5PSW Tag Value [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_IFE 0xED3E /* ForCES inter-FE LFB type */
#define ETH_P_AF_IUCV 0xFBFB /* IBM af_iucv [ NOT AN OFFICIALLY REGISTERED ID ] */
+#define ETH_P_NXP_NETC 0xFD3A /* NXP NETC DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_802_3_MIN 0x0600 /* If the value in the ethernet type is more than this value
* then the frame is Ethernet II. Else it is 802.3 */
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index 5ed8c704636d..d5e725b90d78 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -125,6 +125,16 @@ config NET_DSA_TAG_KSZ
Say Y if you want to enable support for tagging frames for the
Microchip 8795/937x/9477/9893 families of switches.
+config NET_DSA_TAG_NETC
+ tristate "Tag driver for NXP NETC switches"
+ help
+ Say Y or M if you want to enable support for the NXP Switch Tag (NST),
+ as implemented by NXP NETC switches having version 4.3 or later. The
+ switch tag is a proprietary header added to frames after the source
+ MAC address, it has 3 types and each type has different subtypes, so
+ its length depends on the type and subtype of the tag, the maximum
+ length is 14 bytes.
+
config NET_DSA_TAG_OCELOT
tristate "Tag driver for Ocelot family of switches, using NPI port"
select PACKING
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index bf7247759a64..b8c2667cd14a 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o
obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o
+obj-$(CONFIG_NET_DSA_TAG_NETC) += tag_netc.o
obj-$(CONFIG_NET_DSA_TAG_NONE) += tag_none.o
obj-$(CONFIG_NET_DSA_TAG_OCELOT) += tag_ocelot.o
obj-$(CONFIG_NET_DSA_TAG_OCELOT_8021Q) += tag_ocelot_8021q.o
diff --git a/net/dsa/tag_netc.c b/net/dsa/tag_netc.c
new file mode 100644
index 000000000000..addd41f7f1b6
--- /dev/null
+++ b/net/dsa/tag_netc.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025-2026 NXP
+ */
+
+#include <linux/dsa/tag_netc.h>
+
+#include "tag.h"
+
+#define NETC_NAME "nxp_netc"
+
+/* Forward NXP switch tag */
+#define NETC_TAG_FORWARD 0
+
+/* To_Port NXP switch tag */
+#define NETC_TAG_TO_PORT 1
+/* SubType0: No request to perform timestamping */
+#define NETC_TAG_TP_SUBTYPE0 0
+
+/* To_Host NXP switch tag */
+#define NETC_TAG_TO_HOST 2
+/* SubType0: frames redirected or copied to CPU port */
+#define NETC_TAG_TH_SUBTYPE0 0
+/* SubType1: frames redirected or copied to CPU port with timestamp */
+#define NETC_TAG_TH_SUBTYPE1 1
+/* SubType2: Transmit timestamp response (two-step timestamping) */
+#define NETC_TAG_TH_SUBTYPE2 2
+
+/* NETC switch tag lengths */
+#define NETC_TAG_FORWARD_LEN 6
+#define NETC_TAG_TP_SUBTYPE0_LEN 6
+#define NETC_TAG_TH_SUBTYPE0_LEN 6
+#define NETC_TAG_TH_SUBTYPE1_LEN 14
+#define NETC_TAG_TH_SUBTYPE2_LEN 14
+#define NETC_TAG_CMN_LEN 5
+
+#define NETC_TAG_SUBTYPE GENMASK(3, 0)
+#define NETC_TAG_TYPE GENMASK(7, 4)
+#define NETC_TAG_QV BIT(0)
+#define NETC_TAG_IPV GENMASK(4, 2)
+#define NETC_TAG_SWITCH GENMASK(2, 0)
+#define NETC_TAG_PORT GENMASK(7, 3)
+
+struct netc_tag_cmn {
+ __be16 tpid;
+ u8 type;
+ u8 qos;
+ u8 switch_port;
+} __packed;
+
+static void netc_fill_common_tag(struct netc_tag_cmn *tag, u8 type,
+ u8 subtype, u8 sw_id, u8 port, u8 ipv)
+{
+ tag->tpid = htons(ETH_P_NXP_NETC);
+ tag->type = FIELD_PREP(NETC_TAG_TYPE, type) |
+ FIELD_PREP(NETC_TAG_SUBTYPE, subtype);
+ tag->qos = NETC_TAG_QV | FIELD_PREP(NETC_TAG_IPV, ipv);
+ tag->switch_port = FIELD_PREP(NETC_TAG_SWITCH, sw_id) |
+ FIELD_PREP(NETC_TAG_PORT, port);
+}
+
+static void *netc_fill_common_tp_tag(struct sk_buff *skb,
+ struct net_device *ndev,
+ u8 subtype, int tag_len)
+{
+ struct dsa_port *dp = dsa_user_to_port(ndev);
+ u16 queue = skb_get_queue_mapping(skb);
+ u8 ipv = netdev_txq_to_tc(ndev, queue);
+ void *tag;
+
+ skb_push(skb, tag_len);
+ dsa_alloc_etype_header(skb, tag_len);
+
+ tag = dsa_etype_header_pos_tx(skb);
+ memset(tag + NETC_TAG_CMN_LEN, 0, tag_len - NETC_TAG_CMN_LEN);
+ netc_fill_common_tag(tag, NETC_TAG_TO_PORT, subtype,
+ dp->ds->index, dp->index, ipv);
+
+ return tag;
+}
+
+static void netc_fill_tp_tag_subtype0(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ netc_fill_common_tp_tag(skb, ndev, NETC_TAG_TP_SUBTYPE0,
+ NETC_TAG_TP_SUBTYPE0_LEN);
+}
+
+/* Currently only support To_Port tag, subtype 0 */
+static struct sk_buff *netc_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ netc_fill_tp_tag_subtype0(skb, ndev);
+
+ return skb;
+}
+
+static int netc_get_rx_tag_len(int rx_type)
+{
+ int type = FIELD_GET(NETC_TAG_TYPE, rx_type);
+
+ if (type == NETC_TAG_TO_HOST) {
+ u8 subtype = rx_type & NETC_TAG_SUBTYPE;
+
+ if (subtype == NETC_TAG_TH_SUBTYPE1)
+ return NETC_TAG_TH_SUBTYPE1_LEN;
+ else if (subtype == NETC_TAG_TH_SUBTYPE2)
+ return NETC_TAG_TH_SUBTYPE2_LEN;
+ else
+ return NETC_TAG_TH_SUBTYPE0_LEN;
+ }
+
+ return NETC_TAG_FORWARD_LEN;
+}
+
+static struct sk_buff *netc_rcv(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct netc_tag_cmn *tag_cmn;
+ int tag_len, sw_id, port;
+
+ if (unlikely(!pskb_may_pull(skb, NETC_TAG_MAX_LEN)))
+ return NULL;
+
+ tag_cmn = dsa_etype_header_pos_rx(skb);
+ tag_len = netc_get_rx_tag_len(tag_cmn->type);
+
+ if (ntohs(tag_cmn->tpid) != ETH_P_NXP_NETC) {
+ dev_warn_ratelimited(&ndev->dev, "Unknown TPID 0x%04x\n",
+ ntohs(tag_cmn->tpid));
+
+ return NULL;
+ }
+
+ if (tag_cmn->qos & NETC_TAG_QV)
+ skb->priority = FIELD_GET(NETC_TAG_IPV, tag_cmn->qos);
+
+ sw_id = NETC_TAG_SWITCH & tag_cmn->switch_port;
+ /* ENETC VEPA switch ID (0) is not supported yet */
+ if (!sw_id) {
+ dev_warn_ratelimited(&ndev->dev,
+ "VEPA switch ID is not supported yet\n");
+
+ return NULL;
+ }
+
+ port = FIELD_GET(NETC_TAG_PORT, tag_cmn->switch_port);
+ skb->dev = dsa_conduit_find_user(ndev, sw_id, port);
+ if (!skb->dev)
+ return NULL;
+
+ if (tag_cmn->type == NETC_TAG_FORWARD)
+ dsa_default_offload_fwd_mark(skb);
+
+ /* Remove Switch tag from the frame */
+ skb_pull_rcsum(skb, tag_len);
+ dsa_strip_etype_header(skb, tag_len);
+
+ return skb;
+}
+
+static void netc_flow_dissect(const struct sk_buff *skb, __be16 *proto,
+ int *offset)
+{
+ struct netc_tag_cmn *tag_cmn = (struct netc_tag_cmn *)(skb->data - 2);
+ int tag_len = netc_get_rx_tag_len(tag_cmn->type);
+
+ *offset = tag_len;
+ *proto = ((__be16 *)skb->data)[(tag_len / 2) - 1];
+}
+
+static const struct dsa_device_ops netc_netdev_ops = {
+ .name = NETC_NAME,
+ .proto = DSA_TAG_PROTO_NETC,
+ .xmit = netc_xmit,
+ .rcv = netc_rcv,
+ .needed_headroom = NETC_TAG_MAX_LEN,
+ .flow_dissect = netc_flow_dissect,
+};
+
+MODULE_DESCRIPTION("DSA tag driver for NXP NETC switch family");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_NETC, NETC_NAME);
+module_dsa_tag_driver(netc_netdev_ops);
--
2.34.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox