Merge Official Source

Signed-off-by: Tianling Shen <cnsztl@immortalwrt.org>
This commit is contained in:
Tianling Shen
2026-04-24 15:34:46 +08:00
301 changed files with 32283 additions and 7579 deletions
+1 -3
View File
@@ -2,8 +2,6 @@
. /lib/functions/ipv4.sh
PROG="$(basename "$0")"
# wrapper to convert an integer to an address, unless we're using
# decimal output format.
# hook for library function
@@ -21,7 +19,7 @@ _ip2str() {
}
usage() {
echo "Usage: $PROG [ -d | -x ] address/prefix [ start limit ]" >&2
echo "Usage: ${0##*/} [ -d | -x ] address/prefix [ start limit ]" >&2
exit 1
}
+1 -1
View File
@@ -17,7 +17,7 @@ endef
$(eval $(call BuildPackage,airoha-en8811h-firmware))
Package/airoha-en7581-npu-firmware = $(call Package/firmware-default,Airoha EN7581 NPU firmware,,LICENSE.airoha)
Package/airoha-en7581-npu-firmware = $(call Package/firmware-default,Airoha EN7581+MT7992 NPU firmware,,LICENSE.airoha)
define Package/airoha-en7581-npu-firmware/install
$(INSTALL_DIR) $(1)/lib/firmware/airoha
$(CP) \
+3 -3
View File
@@ -741,7 +741,7 @@ define KernelPackage/crypto-md5
KCONFIG:= \
CONFIG_CRYPTO_MD5 \
CONFIG_CRYPTO_MD5_OCTEON \
CONFIG_CRYPTO_MD5_PPC
CONFIG_CRYPTO_MD5_PPC@lt6.18
FILES:=$(LINUX_DIR)/crypto/md5.ko \
$(LINUX_DIR)/lib/crypto/libmd5.ko@ge6.18
AUTOLOAD:=$(call AutoLoad,09,md5 libmd5@ge6.18)
@@ -754,8 +754,8 @@ define KernelPackage/crypto-md5/octeon
endef
define KernelPackage/crypto-md5/powerpc
FILES+=$(LINUX_DIR)/arch/powerpc/crypto/md5-ppc.ko
AUTOLOAD+=$(call AutoLoad,09,md5-ppc)
FILES+=$(LINUX_DIR)/arch/powerpc/crypto/md5-ppc.ko@lt6.18
AUTOLOAD+=$(call AutoLoad,09,LINUX_6_12:md5-ppc)
endef
ifdef KernelPackage/crypto-md5/$(ARCH)
+2 -1
View File
@@ -700,7 +700,6 @@ endef
define KernelPackage/dwmac-motorcomm/description
Supports the Motorcomm DWMAC-based PCI Ethernet controllers.
Currently only YT6801 is supported.
endef
$(eval $(call KernelPackage,dwmac-motorcomm))
@@ -2451,6 +2450,7 @@ endef
$(eval $(call KernelPackage,mhi-wwan-mbim))
define KernelPackage/mtk-t7xx
SUBMENU:=$(NETWORK_DEVICES_MENU)
TITLE:=MediaTek T7xx 5G modem
@@ -2469,6 +2469,7 @@ endef
$(eval $(call KernelPackage,mtk-t7xx))
define KernelPackage/atlantic
SUBMENU:=$(NETWORK_DEVICES_MENU)
TITLE:=Aquantia AQtion 10Gbps Ethernet NIC
+2 -1
View File
@@ -862,7 +862,8 @@ define KernelPackage/ptp-qoriq
TITLE:=Freescale QorIQ PTP support
DEPENDS:=@(TARGET_mpc85xx||TARGET_qoriq) +kmod-ptp
KCONFIG:=CONFIG_PTP_1588_CLOCK_QORIQ
FILES:=$(LINUX_DIR)/drivers/ptp/ptp-qoriq.ko
FILES:=$(LINUX_DIR)/drivers/ptp/ptp-qoriq.ko@lt6.18 \
$(LINUX_DIR)/drivers/ptp/ptp_qoriq.ko@ge6.18
AUTOLOAD:=$(call AutoProbe,ptp-qoriq)
endef
+18 -1
View File
@@ -352,6 +352,22 @@ endef
$(eval $(call KernelPackage,drm-buddy))
define KernelPackage/drm-client-lib
SUBMENU:=$(VIDEO_MENU)
TITLE:=DRM client library setup helper
DEPENDS:=@DISPLAY_SUPPORT +@LINUX_6_18 +kmod-drm +kmod-drm-kms-helper
KCONFIG:=CONFIG_DRM_CLIENT_LIB
FILES:= $(LINUX_DIR)/drivers/gpu/drm/clients/drm_client_lib.ko
AUTOLOAD:=$(call AutoProbe,drm_client_lib)
endef
define KernelPackage/drm-client-lib/description
DRM client library setup helper
endef
$(eval $(call KernelPackage,drm-client-lib))
define KernelPackage/drm-display-helper
SUBMENU:=$(VIDEO_MENU)
TITLE:=DRM helpers for display adapters drivers
@@ -424,7 +440,8 @@ define KernelPackage/drm-mipi-dbi
SUBMENU:=$(VIDEO_MENU)
HIDDEN:=1
TITLE:=MIPI DBI helpers
DEPENDS:=@DISPLAY_SUPPORT +kmod-backlight +kmod-drm-kms-helper
DEPENDS:=@DISPLAY_SUPPORT +kmod-backlight +kmod-drm-kms-helper \
+LINUX_6_18:kmod-drm-client-lib
KCONFIG:=CONFIG_DRM_MIPI_DBI
FILES:=$(LINUX_DIR)/drivers/gpu/drm/drm_mipi_dbi.ko
AUTOLOAD:=$(call AutoProbe,drm_mipi_dbi)
+1
View File
@@ -36,6 +36,7 @@ microcontroller on the Turris Omnia router. It can also show state of MCU
settings and configure MCU options (GPIOs, LEDs, power).
endef
TARGET_CFLAGS += -std=gnu99
TARGET_LDFLAGS += -lcrypto
define Build/Compile
+4 -4
View File
@@ -8,12 +8,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=util-linux
PKG_VERSION:=2.41.3
PKG_VERSION:=2.42
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
PKG_SOURCE_URL:=@KERNEL/linux/utils/$(PKG_NAME)/v2.41
PKG_HASH:=3330d873f0fceb5560b89a7dc14e4f3288bbd880e96903ed9b50ec2b5799e58b
PKG_SOURCE_URL:=@KERNEL/linux/utils/$(PKG_NAME)/v2.42
PKG_HASH:=3452b260bbaa775d6e749ac3bb22111785003fc1f444970025c8da26dfa758e9
PKG_CPE_ID:=cpe:/a:kernel:util-linux
PKG_INSTALL:=1
@@ -202,7 +202,7 @@ endef
define Package/column
$(call Package/util-linux/Default)
TITLE:=column formats input into multiple columns
DEPENDS:= +libsmartcols
DEPENDS:= +libsmartcols +libncursesw
LICENSE:=LGPL-2.1-or-later
LICENSE_FILES:=Documentation/licenses/COPYING.LGPL-2.1-or-later
endef
@@ -0,0 +1,30 @@
From af6168533cb011489bd0b4b1a2fbbaaeec6859b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= <thomas@t-8ch.de>
Date: Fri, 10 Apr 2026 18:04:58 +0200
Subject: [PATCH] meson: test for statx::stx_mnt_id in sys/stat.h
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Both the check for the general availability of 'struct statx' and the
code actual code use sys/stat.h and not linux/stat.h.
The test for stx_mnt_id also needs to test in sys/stat.h as otherwise
the detection result is wrong.
Signed-off-by: Thomas Weißschuh <thomas@t-8ch.de>
---
meson.build | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/meson.build
+++ b/meson.build
@@ -111,7 +111,7 @@ conf.set('HAVE_MOUNTFD_API', have_mountf
have_struct_statx = cc.has_type('struct statx', args : '-D_GNU_SOURCE', prefix : '#include <sys/stat.h>')
conf.set('HAVE_STRUCT_STATX', have_struct_statx ? 1 : false)
have = cc.has_member('struct statx', 'stx_mnt_id',
- prefix : '#include <linux/stat.h>')
+ prefix : '#include <sys/stat.h>')
conf.set('HAVE_STRUCT_STATX_STX_MNT_ID', have ? 1 : false)
-1
View File
@@ -352,7 +352,6 @@ ifneq ($(CONFIG_CCACHE),)
export CCACHE_NOCOMPRESS:=true
export CCACHE_BASEDIR:=$(TOPDIR)
export CCACHE_DIR:=$(if $(call qstrip,$(CONFIG_CCACHE_DIR)),$(call qstrip,$(CONFIG_CCACHE_DIR)),$(TOPDIR)/.ccache)
export CCACHE_COMPILERCHECK:=%compiler% -v -c
endif
TARGET_CONFIGURE_OPTS = \
+10 -1
View File
@@ -2,7 +2,7 @@
from os import getenv, environ
from pathlib import Path
from subprocess import run, PIPE
from subprocess import run, PIPE, DEVNULL
from sys import argv
import json
import re
@@ -92,6 +92,15 @@ if output:
"vermagic": linux_vermagic,
}
git_commit = run(
["git", "rev-parse", "HEAD"],
stdout=PIPE,
stderr=DEVNULL,
universal_newlines=True,
)
if git_commit.returncode == 0:
output["git_commit"] = git_commit.stdout.strip()
for artifact in "imagebuilder", "sdk", "toolchain":
filename = add_artifact(artifact)
add_artifact("llvm-bpf", prefix="")
+1 -1
View File
@@ -75,7 +75,7 @@ define Device/airoha_an7581-evb-emmc-kite
DEVICE_VENDOR := Airoha
DEVICE_MODEL := AN7581 Evaluation Board (eMMC + Kite)
DEVICE_DTS := an7581-evb-emmc-kite
DEVICE_PACKAGES := kmod-i2c-an7581 airoha-en7581-mt7992-npu-firmware \
DEVICE_PACKAGES := kmod-i2c-an7581 airoha-en7581-npu-firmware \
kmod-mt7992-firmware wpad-basic-mbedtls
ARTIFACT/preloader.bin := an7581-preloader rfb
ARTIFACT/bl31-uboot.fip := an7581-bl31-uboot rfb
@@ -0,0 +1,332 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "qca955x.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/leds/common.h>
/ {
compatible = "mikrotik,routerboard-960pgs", "qca,qca9557";
model = "MikroTik RouterBOARD 960PGS (hEX PoE/PowerBox Pro)";
aliases {
led-boot = &led_user;
led-failsafe = &led_user;
led-running = &led_user;
led-upgrade = &led_user;
};
gpio_export {
compatible = "gpio-export";
buzzer {
/* Beeper requires PWM for frequency selection */
gpio-export,name = "buzzer";
gpio-export,output = <0>;
gpios = <&gpio 4 GPIO_ACTIVE_HIGH>;
};
};
i2c: i2c {
compatible = "i2c-gpio";
i2c-gpio,delay-us = <5>;
i2c-gpio,timeout-ms = <1>;
sda-gpios = <&gpio 18 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>;
scl-gpios = <&gpio 19 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>;
};
keys {
compatible = "gpio-keys";
reset {
debounce-interval = <60>;
gpios = <&gpio 20 GPIO_ACTIVE_LOW>;
label = "reset";
linux,code = <KEY_RESTART>;
};
};
leds {
compatible = "gpio-leds";
led_user: user {
label = "green:user";
gpios = <&gpio 12 GPIO_ACTIVE_LOW>;
};
sfp {
label = "green:sfp";
gpios = <&gpio 2 GPIO_ACTIVE_HIGH>;
};
};
sfp1: sfp {
compatible = "sff,sfp";
i2c-bus = <&i2c>;
maximum-power-milliwatt = <1000>;
los-gpios = <&gpio 21 GPIO_ACTIVE_HIGH>;
mod-def0-gpios = <&gpio 17 GPIO_ACTIVE_LOW>;
tx-disable-gpios = <&gpio 16 GPIO_ACTIVE_HIGH>;
};
reg_usb_vbus {
compatible = "regulator-fixed";
regulator-boot-on;
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
regulator-name = "usb_vbus";
gpio = <&gpio 13 GPIO_ACTIVE_HIGH>;
enable-active-high;
};
};
&mdio0 {
status = "okay";
switch@0 {
compatible = "qca,qca8337";
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
label = "cpu";
ethernet = <&eth0>;
phy-mode = "rgmii-id";
fixed-link {
speed = <1000>;
full-duplex;
};
};
port@1 {
reg = <1>;
label = "lan1";
phy-mode = "internal";
phy-handle = <&phy_port1>;
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
};
port@2 {
reg = <2>;
label = "lan2";
phy-mode = "internal";
phy-handle = <&phy_port2>;
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
};
port@3 {
reg = <3>;
label = "lan3";
phy-mode = "internal";
phy-handle = <&phy_port3>;
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
};
port@4 {
reg = <4>;
label = "lan4";
phy-mode = "internal";
phy-handle = <&phy_port4>;
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
};
port@5 {
reg = <5>;
label = "lan5";
phy-mode = "internal";
phy-handle = <&phy_port5>;
leds {
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0>;
color = <LED_COLOR_ID_GREEN>;
function = LED_FUNCTION_LAN;
default-state = "keep";
};
};
};
};
mdio {
#address-cells = <1>;
#size-cells = <0>;
phy_port1: phy@0 {
reg = <0>;
};
phy_port2: phy@1 {
reg = <1>;
};
phy_port3: phy@2 {
reg = <2>;
};
phy_port4: phy@3 {
reg = <3>;
};
phy_port5: phy@4 {
reg = <4>;
};
};
};
};
&eth0 {
status = "okay";
pll-data = <0x8f000000 0xa0000101 0xa0001313>;
fixed-link {
speed = <1000>;
full-duplex;
};
};
&mdio1 {
status = "okay";
phy_sfp: ethernet-phy@0 {
reg = <0>;
phy-mode = "sgmii";
sfp = <&sfp1>;
};
};
&eth1 {
status = "okay";
phy-handle = <&phy_sfp>;
pll-data = <0x03000000 0x00000101 0x00001616>;
qca955x-sgmii-fixup;
gmac-config {
device = <&gmac>;
};
fixed-link {
speed = <1000>;
full-duplex;
};
};
&spi {
status = "okay";
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <40000000>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
compatible = "mikrotik,routerboot-partitions";
label = "RouterBoot";
reg = <0x0 0x20000>;
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "bootloader1";
reg = <0x0 0x0>;
read-only;
};
hard_config {
read-only;
};
bios {
size = <0x1000>;
read-only;
};
partition@10000 {
label = "bootloader2";
reg = <0x10000 0x0>;
read-only;
};
soft_config {
};
};
partition@20000 {
compatible = "mikrotik,minor";
label = "firmware";
reg = <0x020000 0xfe0000>;
};
};
};
};
&usb0 {
status = "okay";
};
&usb_phy0 {
status = "okay";
};
+10
View File
@@ -128,6 +128,16 @@ define Device/mikrotik_routerboard-952ui-5ac2nd
endef
TARGET_DEVICES += mikrotik_routerboard-952ui-5ac2nd
define Device/mikrotik_routerboard-960pgs
$(Device/mikrotik_nor)
SOC := qca9557
DEVICE_MODEL := RouterBOARD 960PGS (hEX PoE/PowerBox Pro)
IMAGE_SIZE := 16256k
DEVICE_PACKAGES += kmod-usb2 kmod-i2c-gpio kmod-sfp kmod-dsa-qca8k -swconfig \
-kmod-switch-ar8xxx -iwinfo -kmod-ath9k -wpad-basic-mbedtls
endef
TARGET_DEVICES += mikrotik_routerboard-960pgs
define Device/mikrotik_routerboard-962uigs-5hact2hnt
$(Device/mikrotik_nor)
SOC := qca9558
@@ -47,6 +47,9 @@ ath79_setup_interfaces()
mikrotik,routerboard-map-2nd)
ucidef_set_interface_lan "eth0 eth1"
;;
mikrotik,routerboard-960pgs)
ucidef_set_interfaces_lan_wan "lan2 lan3 lan4 lan5" "eth1 lan1"
;;
mikrotik,routerboard-951g-2hnd|\
mikrotik,routerboard-962uigs-5hact2hnt)
ucidef_add_switch "switch0" \
@@ -90,6 +93,15 @@ ath79_setup_macs()
lan_mac="$mac_base"
wan_mac=$(macaddr_add $mac_base 1)
;;
mikrotik,routerboard-960pgs)
label_mac=$mac_base
ucidef_set_network_device_mac "eth1" "$mac_base"
ucidef_set_network_device_mac "lan1" "$(macaddr_add $mac_base 1)"
ucidef_set_network_device_mac "lan2" "$(macaddr_add $mac_base 2)"
ucidef_set_network_device_mac "lan3" "$(macaddr_add $mac_base 3)"
ucidef_set_network_device_mac "lan4" "$(macaddr_add $mac_base 4)"
ucidef_set_network_device_mac "lan5" "$(macaddr_add $mac_base 5)"
;;
*)
label_mac="$mac_base"
wan_mac="$mac_base"
@@ -1,3 +1,4 @@
# CONFIG_AR8216_PHY is not set
CONFIG_CRC16=y
CONFIG_CRYPTO_DEFLATE=y
CONFIG_CRYPTO_HASH_INFO=y
@@ -38,6 +39,7 @@ CONFIG_NET_DSA=y
CONFIG_PHYLINK=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
CONFIG_SPI_RB4XX=y
# CONFIG_SWCONFIG is not set
CONFIG_UBIFS_FS=y
CONFIG_WATCHDOG_CORE=y
CONFIG_XXHASH=y
+1 -1
View File
@@ -3,7 +3,7 @@ FEATURES += minor nand
KERNELNAME := vmlinux vmlinuz
IMAGES_DIR := ../../..
DEFAULT_PACKAGES += wpad-openssl yafut
DEFAULT_PACKAGES += wpad-openssl yafut kmod-switch-ar8xxx
define Target/Description
Build firmware images for MikroTik devices based on Qualcomm Atheros
@@ -15,7 +15,7 @@ Signed-off-by: John Crispin <john@phrozen.org>
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -25,6 +25,22 @@ config GENERIC_PHY_MIPI_DPHY
@@ -47,6 +47,22 @@ config GENERIC_PHY_MIPI_DPHY
Provides a number of helpers a core functions for MIPI D-PHY
drivers to us.
@@ -40,9 +40,9 @@ Signed-off-by: John Crispin <john@phrozen.org>
depends on OF && (ARCH_LPC18XX || COMPILE_TEST)
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -4,6 +4,8 @@
#
@@ -6,6 +6,8 @@
obj-$(CONFIG_PHY_COMMON_PROPS) += phy-common-props.o
obj-$(CONFIG_PHY_COMMON_PROPS_TEST) += phy-common-props-test.o
obj-$(CONFIG_GENERIC_PHY) += phy-core.o
+obj-$(CONFIG_PHY_AR7100_USB) += phy-ar7100-usb.o
+obj-$(CONFIG_PHY_AR7200_USB) += phy-ar7200-usb.o
+1 -2
View File
@@ -11,8 +11,7 @@ FEATURES:=ext4 squashfs usbgadget
KERNELNAME:=Image dtbs
SUBTARGETS:=generic
KERNEL_PATCHVER:=6.12
KERNEL_TESTING_PATCHVER:=6.18
KERNEL_PATCHVER:=6.18
include $(INCLUDE_DIR)/target.mk
-489
View File
@@ -1,489 +0,0 @@
CONFIG_64BIT=y
# CONFIG_ACPI is not set
# CONFIG_ARCH_CANAAN is not set
CONFIG_ARCH_DMA_ADDR_T_64BIT=y
CONFIG_ARCH_DMA_DEFAULT_COHERENT=y
CONFIG_ARCH_HIBERNATION_POSSIBLE=y
CONFIG_ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE=y
# CONFIG_ARCH_MICROCHIP is not set
CONFIG_ARCH_MMAP_RND_BITS=18
CONFIG_ARCH_MMAP_RND_BITS_MAX=24
CONFIG_ARCH_MMAP_RND_BITS_MIN=18
CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=17
CONFIG_ARCH_OPTIONAL_KERNEL_RWX=y
CONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT=y
CONFIG_ARCH_PROC_KCORE_TEXT=y
CONFIG_ARCH_RV64I=y
CONFIG_ARCH_SELECT_MEMORY_MODEL=y
# CONFIG_ARCH_SIFIVE is not set
# CONFIG_ARCH_SOPHGO is not set
CONFIG_ARCH_SPARSEMEM_ENABLE=y
CONFIG_ARCH_STACKWALK=y
CONFIG_ARCH_SUNXI=y
CONFIG_ARCH_SUSPEND_POSSIBLE=y
# CONFIG_ARCH_THEAD is not set
CONFIG_ARCH_WANTS_NO_INSTR=y
CONFIG_ARCH_WANTS_THP_SWAP=y
CONFIG_ASN1=y
# CONFIG_AX45MP_L2_CACHE is not set
CONFIG_BLK_DEV_LOOP=y
CONFIG_BLK_PM=y
CONFIG_BUFFER_HEAD=y
CONFIG_CC_HAVE_STACKPROTECTOR_TLS=y
CONFIG_CLKSRC_MMIO=y
CONFIG_CLONE_BACKWARDS=y
CONFIG_CMODEL_MEDANY=y
# CONFIG_CMODEL_MEDLOW is not set
CONFIG_COMMON_CLK=y
CONFIG_COMPACT_UNEVICTABLE_DEFAULT=1
# CONFIG_COMPAT_32BIT_TIME is not set
CONFIG_COMPAT_BRK=y
CONFIG_CONTEXT_TRACKING=y
CONFIG_CONTEXT_TRACKING_IDLE=y
CONFIG_COREDUMP=y
CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=y
CONFIG_CPU_ISOLATION=y
CONFIG_CPU_MITIGATIONS=y
CONFIG_CRC16=y
CONFIG_CRC7=y
CONFIG_CRC_ITU_T=y
CONFIG_CRYPTO_CBC=y
CONFIG_CRYPTO_CRC32C=y
CONFIG_CRYPTO_DES=y
CONFIG_CRYPTO_DEV_ALLWINNER=y
# CONFIG_CRYPTO_DEV_SUN4I_SS is not set
CONFIG_CRYPTO_DEV_SUN8I_CE=y
# CONFIG_CRYPTO_DEV_SUN8I_CE_DEBUG is not set
CONFIG_CRYPTO_DEV_SUN8I_CE_HASH=y
CONFIG_CRYPTO_DEV_SUN8I_CE_PRNG=y
CONFIG_CRYPTO_DEV_SUN8I_CE_TRNG=y
# CONFIG_CRYPTO_DEV_SUN8I_SS is not set
CONFIG_CRYPTO_ECB=y
CONFIG_CRYPTO_ENGINE=y
CONFIG_CRYPTO_HW=y
CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y
CONFIG_CRYPTO_LIB_DES=y
CONFIG_CRYPTO_LIB_GF128MUL=y
CONFIG_CRYPTO_LIB_POLY1305_RSIZE=1
CONFIG_CRYPTO_LIB_SHA1=y
CONFIG_CRYPTO_LIB_SHA256=y
CONFIG_CRYPTO_LIB_UTILS=y
# CONFIG_CRYPTO_MANAGER_DISABLE_TESTS is not set
# CONFIG_CRYPTO_MANAGER_EXTRA_TESTS is not set
CONFIG_CRYPTO_MD5=y
CONFIG_CRYPTO_RNG=y
CONFIG_CRYPTO_RNG2=y
CONFIG_CRYPTO_SHA1=y
CONFIG_CRYPTO_SHA256=y
CONFIG_CRYPTO_SHA512=y
CONFIG_CRYPTO_SIG2=y
CONFIG_DEBUG_INFO=y
CONFIG_DMADEVICES=y
CONFIG_DMA_BOUNCE_UNALIGNED_KMALLOC=y
CONFIG_DMA_DIRECT_REMAP=y
CONFIG_DMA_ENGINE=y
CONFIG_DMA_NEED_SYNC=y
CONFIG_DMA_OF=y
CONFIG_DMA_SUN6I=y
CONFIG_DMA_VIRTUAL_CHANNELS=y
CONFIG_DMI=y
CONFIG_DMIID=y
# CONFIG_DMI_SYSFS is not set
CONFIG_DTC=y
CONFIG_DWMAC_GENERIC=y
CONFIG_DWMAC_SUN8I=y
CONFIG_DWMAC_SUNXI=y
CONFIG_DYNAMIC_SIGFRAME=y
CONFIG_EDAC_SUPPORT=y
CONFIG_EFI=y
CONFIG_EFIVAR_FS=m
# CONFIG_EFI_BOOTLOADER_CONTROL is not set
# CONFIG_EFI_CAPSULE_LOADER is not set
# CONFIG_EFI_COCO_SECRET is not set
# CONFIG_EFI_DISABLE_PCI_DMA is not set
# CONFIG_EFI_DISABLE_RUNTIME is not set
CONFIG_EFI_EARLYCON=y
CONFIG_EFI_ESRT=y
CONFIG_EFI_GENERIC_STUB=y
CONFIG_EFI_PARAMS_FROM_FDT=y
CONFIG_EFI_RUNTIME_WRAPPERS=y
CONFIG_EFI_STUB=y
# CONFIG_EFI_TEST is not set
# CONFIG_EFI_ZBOOT is not set
CONFIG_ELF_CORE=y
# CONFIG_ERRATA_ANDES is not set
# CONFIG_ERRATA_SIFIVE is not set
CONFIG_ERRATA_THEAD=y
CONFIG_ERRATA_THEAD_CMO=y
CONFIG_ERRATA_THEAD_MAE=y
CONFIG_EXCLUSIVE_SYSTEM_RAM=y
CONFIG_EXT4_FS=y
CONFIG_EXTCON=y
CONFIG_FAILOVER=y
CONFIG_FHANDLE=y
CONFIG_FIXED_PHY=y
CONFIG_FIX_EARLYCON_MEM=y
CONFIG_FONT_8x16=y
CONFIG_FONT_AUTOSELECT=y
CONFIG_FONT_SUPPORT=y
CONFIG_FPU=y
CONFIG_FRAME_POINTER=y
CONFIG_FS_IOMAP=y
CONFIG_FS_MBCACHE=y
CONFIG_FUNCTION_ALIGNMENT=0
CONFIG_FWNODE_MDIO=y
CONFIG_FW_LOADER_PAGED_BUF=y
CONFIG_FW_LOADER_SYSFS=y
CONFIG_GCC_SUPPORTS_DYNAMIC_FTRACE=y
CONFIG_GENERIC_ALLOCATOR=y
CONFIG_GENERIC_ARCH_TOPOLOGY=y
CONFIG_GENERIC_BUG=y
CONFIG_GENERIC_BUG_RELATIVE_POINTERS=y
CONFIG_GENERIC_CLOCKEVENTS=y
CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y
CONFIG_GENERIC_CPU_DEVICES=y
CONFIG_GENERIC_CPU_VULNERABILITIES=y
CONFIG_GENERIC_CSUM=y
CONFIG_GENERIC_EARLY_IOREMAP=y
CONFIG_GENERIC_ENTRY=y
CONFIG_GENERIC_GETTIMEOFDAY=y
CONFIG_GENERIC_IDLE_POLL_SETUP=y
CONFIG_GENERIC_IOREMAP=y
CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y
CONFIG_GENERIC_IRQ_IPI_MUX=y
CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR=y
CONFIG_GENERIC_IRQ_MULTI_HANDLER=y
CONFIG_GENERIC_IRQ_SHOW=y
CONFIG_GENERIC_IRQ_SHOW_LEVEL=y
CONFIG_GENERIC_LIB_DEVMEM_IS_ALLOWED=y
CONFIG_GENERIC_MSI_IRQ=y
CONFIG_GENERIC_PCI_IOMAP=y
CONFIG_GENERIC_PHY=y
CONFIG_GENERIC_PINCONF=y
CONFIG_GENERIC_PINCTRL_GROUPS=y
CONFIG_GENERIC_PINMUX_FUNCTIONS=y
CONFIG_GENERIC_SCHED_CLOCK=y
CONFIG_GENERIC_SMP_IDLE_THREAD=y
CONFIG_GENERIC_STRNCPY_FROM_USER=y
CONFIG_GENERIC_STRNLEN_USER=y
CONFIG_GENERIC_TIME_VSYSCALL=y
CONFIG_GPIOLIB_IRQCHIP=y
CONFIG_GPIO_CDEV=y
CONFIG_GPIO_PCF857X=y
CONFIG_HARDIRQS_SW_RESEND=y
CONFIG_HAS_DMA=y
CONFIG_HAS_IOMEM=y
CONFIG_HAS_IOPORT=y
CONFIG_HAS_IOPORT_MAP=y
CONFIG_HW_RANDOM=y
CONFIG_HZ_PERIODIC=y
CONFIG_I2C=y
CONFIG_I2C_BOARDINFO=y
CONFIG_I2C_HELPER_AUTO=y
CONFIG_I2C_MV64XXX=y
CONFIG_I2C_OCORES=y
CONFIG_IIO=y
CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000
CONFIG_INITRAMFS_SOURCE=""
CONFIG_INPUT=y
CONFIG_INPUT_KEYBOARD=y
# CONFIG_IOMMUFD is not set
CONFIG_IOMMU_API=y
# CONFIG_IOMMU_DEBUGFS is not set
# CONFIG_IOMMU_DEFAULT_DMA_LAZY is not set
CONFIG_IOMMU_DEFAULT_DMA_STRICT=y
# CONFIG_IOMMU_DEFAULT_PASSTHROUGH is not set
CONFIG_IOMMU_SUPPORT=y
CONFIG_IO_URING=y
CONFIG_IRQCHIP=y
CONFIG_IRQ_DOMAIN=y
CONFIG_IRQ_DOMAIN_HIERARCHY=y
CONFIG_IRQ_FORCED_THREADING=y
CONFIG_IRQ_STACKS=y
CONFIG_IRQ_WORK=y
CONFIG_JBD2=y
CONFIG_KALLSYMS=y
# CONFIG_KERNEL_UNCOMPRESSED is not set
# CONFIG_KEYBOARD_SUN4I_LRADC is not set
CONFIG_LEDS_SUN50I_A100=y
CONFIG_LEGACY_PTYS=y
CONFIG_LEGACY_PTY_COUNT=256
CONFIG_LIBFDT=y
CONFIG_LOCALVERSION_AUTO=y
CONFIG_LOCK_DEBUGGING_SUPPORT=y
CONFIG_LOCK_SPIN_ON_OWNER=y
CONFIG_MAILBOX=y
# CONFIG_MAILBOX_TEST is not set
CONFIG_MDIO_BUS=y
CONFIG_MDIO_BUS_MUX=y
CONFIG_MDIO_DEVICE=y
CONFIG_MDIO_DEVRES=y
# CONFIG_MDIO_SUN4I is not set
CONFIG_MFD_AXP20X=y
CONFIG_MFD_AXP20X_I2C=y
CONFIG_MFD_CORE=y
# CONFIG_MFD_SUN4I_GPADC is not set
CONFIG_MFD_SUN6I_PRCM=y
CONFIG_MFD_SYSCON=y
CONFIG_MIGRATION=y
CONFIG_MMC=y
CONFIG_MMC_BLOCK=y
CONFIG_MMC_SUNXI=y
CONFIG_MMIOWB=y
CONFIG_MMU_LAZY_TLB_REFCOUNT=y
CONFIG_MODULES_USE_ELF_RELA=y
CONFIG_MTD_SPI_NOR=y
CONFIG_MTD_SPI_NOR_USE_4K_SECTORS=y
# CONFIG_MUSB_PIO_ONLY is not set
CONFIG_MUTEX_SPIN_ON_OWNER=y
CONFIG_NEED_DMA_MAP_STATE=y
CONFIG_NET_EGRESS=y
CONFIG_NET_INGRESS=y
CONFIG_NET_PTP_CLASSIFY=y
CONFIG_NET_SELFTESTS=y
CONFIG_NET_VENDOR_ALLWINNER=y
CONFIG_NET_XGRESS=y
CONFIG_NLS=y
# CONFIG_NONPORTABLE is not set
CONFIG_NOP_USB_XCEIV=y
CONFIG_NR_CPUS=8
CONFIG_NVMEM=y
CONFIG_NVMEM_LAYOUTS=y
CONFIG_NVMEM_SUNXI_SID=y
CONFIG_NVMEM_SYSFS=y
CONFIG_OF=y
CONFIG_OF_ADDRESS=y
CONFIG_OF_EARLY_FLATTREE=y
CONFIG_OF_FLATTREE=y
CONFIG_OF_GPIO=y
CONFIG_OF_IOMMU=y
CONFIG_OF_IRQ=y
CONFIG_OF_KOBJ=y
CONFIG_OF_MDIO=y
CONFIG_OID_REGISTRY=y
CONFIG_PADATA=y
CONFIG_PAGE_OFFSET=0xff60000000000000
CONFIG_PAGE_POOL=y
CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
CONFIG_PANIC_TIMEOUT=0
CONFIG_PCS_XPCS=y
CONFIG_PER_VMA_LOCK=y
CONFIG_PGTABLE_LEVELS=5
CONFIG_PHYLIB=y
CONFIG_PHYLIB_LEDS=y
CONFIG_PHYLINK=y
CONFIG_PHYS_ADDR_T_64BIT=y
CONFIG_PHY_SUN4I_USB=y
CONFIG_PHY_SUN50I_USB3=y
# CONFIG_PHY_SUN6I_MIPI_DPHY is not set
# CONFIG_PHY_SUN9I_USB is not set
CONFIG_PINCTRL=y
CONFIG_PINCTRL_SUN20I_D1=y
# CONFIG_PINCTRL_SUN4I_A10 is not set
# CONFIG_PINCTRL_SUN50I_A100 is not set
# CONFIG_PINCTRL_SUN50I_A100_R is not set
# CONFIG_PINCTRL_SUN50I_A64 is not set
# CONFIG_PINCTRL_SUN50I_A64_R is not set
# CONFIG_PINCTRL_SUN50I_H5 is not set
# CONFIG_PINCTRL_SUN50I_H6 is not set
# CONFIG_PINCTRL_SUN50I_H616 is not set
# CONFIG_PINCTRL_SUN50I_H616_R is not set
# CONFIG_PINCTRL_SUN50I_H6_R is not set
# CONFIG_PINCTRL_SUN5I is not set
# CONFIG_PINCTRL_SUN6I_A31 is not set
# CONFIG_PINCTRL_SUN6I_A31_R is not set
# CONFIG_PINCTRL_SUN8I_A23 is not set
# CONFIG_PINCTRL_SUN8I_A23_R is not set
# CONFIG_PINCTRL_SUN8I_A33 is not set
# CONFIG_PINCTRL_SUN8I_A83T is not set
# CONFIG_PINCTRL_SUN8I_A83T_R is not set
# CONFIG_PINCTRL_SUN8I_H3 is not set
# CONFIG_PINCTRL_SUN8I_H3_R is not set
# CONFIG_PINCTRL_SUN8I_V3S is not set
# CONFIG_PINCTRL_SUN9I_A80 is not set
# CONFIG_PINCTRL_SUN9I_A80_R is not set
CONFIG_PINCTRL_SUNXI=y
CONFIG_PM=y
CONFIG_PM_CLK=y
CONFIG_PM_GENERIC_DOMAINS=y
CONFIG_PM_GENERIC_DOMAINS_OF=y
CONFIG_PORTABLE=y
CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y
CONFIG_POWER_RESET=y
CONFIG_POWER_RESET_SYSCON=y
CONFIG_POWER_RESET_SYSCON_POWEROFF=y
CONFIG_POWER_SUPPLY=y
CONFIG_PPS=y
CONFIG_PRINTK_TIME=y
CONFIG_PTP_1588_CLOCK=y
CONFIG_PTP_1588_CLOCK_OPTIONAL=y
CONFIG_PWM=y
# CONFIG_PWM_SIFIVE is not set
# CONFIG_PWM_SUN4I is not set
CONFIG_QUEUED_RWLOCKS=y
CONFIG_RANDSTRUCT_NONE=y
CONFIG_RATIONAL=y
CONFIG_RCU_TRACE=y
CONFIG_REALTEK_PHY=y
CONFIG_REGMAP=y
CONFIG_REGMAP_I2C=y
CONFIG_REGMAP_IRQ=y
CONFIG_REGMAP_MMIO=y
CONFIG_REGMAP_SPI=y
CONFIG_REGULATOR=y
# CONFIG_REGULATOR_AXP20X is not set
CONFIG_REGULATOR_FIXED_VOLTAGE=y
CONFIG_REGULATOR_SUN20I=y
# CONFIG_RESET_ATTACK_MITIGATION is not set
CONFIG_RESET_CONTROLLER=y
CONFIG_RESET_SIMPLE=y
CONFIG_RESET_SUNXI=y
CONFIG_RISCV=y
CONFIG_RISCV_ALTERNATIVE=y
CONFIG_RISCV_ALTERNATIVE_EARLY=y
CONFIG_RISCV_APLIC=y
CONFIG_RISCV_APLIC_MSI=y
CONFIG_RISCV_BOOT_SPINWAIT=y
CONFIG_RISCV_DMA_NONCOHERENT=y
# CONFIG_RISCV_EMULATED_UNALIGNED_ACCESS is not set
CONFIG_RISCV_IMSIC=y
CONFIG_RISCV_INTC=y
CONFIG_RISCV_ISA_C=y
CONFIG_RISCV_ISA_FALLBACK=y
CONFIG_RISCV_ISA_SVNAPOT=y
CONFIG_RISCV_ISA_SVPBMT=y
CONFIG_RISCV_ISA_V=y
CONFIG_RISCV_ISA_VENDOR_EXT=y
CONFIG_RISCV_ISA_VENDOR_EXT_ANDES=y
CONFIG_RISCV_ISA_V_DEFAULT_ENABLE=y
CONFIG_RISCV_ISA_V_UCOPY_THRESHOLD=768
CONFIG_RISCV_ISA_ZAWRS=y
CONFIG_RISCV_ISA_ZBA=y
CONFIG_RISCV_ISA_ZBB=y
CONFIG_RISCV_ISA_ZBC=y
CONFIG_RISCV_ISA_ZICBOM=y
CONFIG_RISCV_ISA_ZICBOZ=y
CONFIG_RISCV_MISALIGNED=y
CONFIG_RISCV_NONSTANDARD_CACHE_OPS=y
CONFIG_RISCV_PROBE_UNALIGNED_ACCESS=y
CONFIG_RISCV_SBI=y
CONFIG_RISCV_SBI_V01=y
CONFIG_RISCV_TIMER=y
CONFIG_RISCV_USE_LINKER_RELAXATION=y
# CONFIG_RPS is not set
CONFIG_RTC_CLASS=y
CONFIG_RTC_DRV_GOLDFISH=y
CONFIG_RTC_DRV_SUN6I=y
CONFIG_RTC_I2C_AND_SPI=y
# CONFIG_RUNTIME_KERNEL_TESTING_MENU is not set
CONFIG_RWSEM_SPIN_ON_OWNER=y
CONFIG_SCHED_DEBUG=y
CONFIG_SERIAL_8250_DEPRECATED_OPTIONS=y
CONFIG_SERIAL_8250_DW=y
CONFIG_SERIAL_8250_DWLIB=y
CONFIG_SERIAL_8250_NR_UARTS=4
CONFIG_SERIAL_8250_RUNTIME_UARTS=4
CONFIG_SERIAL_EARLYCON_RISCV_SBI=y
CONFIG_SERIAL_MCTRL_GPIO=y
CONFIG_SERIAL_OF_PLATFORM=y
CONFIG_SERIO=y
CONFIG_SERIO_SERPORT=y
CONFIG_SGL_ALLOC=y
CONFIG_SIFIVE_PLIC=y
CONFIG_SLUB_DEBUG=y
CONFIG_SMP=y
CONFIG_SND=y
CONFIG_SND_COMPRESS_OFFLOAD=y
CONFIG_SND_JACK=y
CONFIG_SND_JACK_INPUT_DEV=y
CONFIG_SND_PCM=y
CONFIG_SND_SOC=y
CONFIG_SND_SOC_I2C_AND_SPI=y
# CONFIG_SND_SUN20I_D1_CODEC_ANALOG is not set
# CONFIG_SND_SUN4I_I2S is not set
# CONFIG_SND_SUN4I_SPDIF is not set
# CONFIG_SND_SUN50I_DMIC is not set
CONFIG_SOCK_RX_QUEUE_MAPPING=y
# CONFIG_SOC_STARFIVE is not set
CONFIG_SOFTIRQ_ON_OWN_STACK=y
CONFIG_SOUND=y
CONFIG_SOUND_OSS_CORE=y
CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y
CONFIG_SPARSE_IRQ=y
CONFIG_SPI=y
CONFIG_SPI_BITBANG=y
CONFIG_SPI_MASTER=y
CONFIG_SPI_MEM=y
# CONFIG_SPI_SUN4I is not set
CONFIG_SPI_SUN6I=y
CONFIG_SPLIT_PMD_PTLOCKS=y
CONFIG_SPLIT_PTE_PTLOCKS=y
CONFIG_STACKDEPOT=y
CONFIG_STACKTRACE=y
CONFIG_STMMAC_ETH=y
CONFIG_STMMAC_PLATFORM=y
CONFIG_SUN20I_D1_CCU=y
CONFIG_SUN20I_D1_R_CCU=y
CONFIG_SUN20I_GPADC=y
CONFIG_SUN20I_PPU=y
# CONFIG_SUN4I_EMAC is not set
CONFIG_SUN4I_TIMER=y
CONFIG_SUN50I_IOMMU=y
CONFIG_SUN6I_MSGBOX=y
CONFIG_SUN6I_RTC_CCU=y
CONFIG_SUN8I_DE2_CCU=y
CONFIG_SUN8I_THERMAL=y
CONFIG_SUNXI_CCU=y
# CONFIG_SUNXI_RSB is not set
CONFIG_SUNXI_SRAM=y
CONFIG_SUNXI_WATCHDOG=y
CONFIG_SWIOTLB=y
CONFIG_SWPHY=y
CONFIG_SYSCTL_ARCH_UNALIGN_ALLOW=y
CONFIG_SYSCTL_EXCEPTION_TRACE=y
# CONFIG_SYSFB_SIMPLEFB is not set
CONFIG_SYSFS_SYSCALL=y
CONFIG_THERMAL=y
CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y
CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS=0
CONFIG_THERMAL_GOV_STEP_WISE=y
CONFIG_THERMAL_OF=y
CONFIG_THREAD_INFO_IN_TASK=y
CONFIG_THREAD_SIZE_ORDER=2
CONFIG_TICK_CPU_ACCOUNTING=y
CONFIG_TIMER_OF=y
CONFIG_TIMER_PROBE=y
CONFIG_TOOLCHAIN_HAS_V=y
CONFIG_TOOLCHAIN_HAS_VECTOR_CRYPTO=y
CONFIG_TOOLCHAIN_HAS_ZBB=y
CONFIG_TOOLCHAIN_HAS_ZBC=y
CONFIG_TOOLCHAIN_NEEDS_EXPLICIT_ZICSR_ZIFENCEI=y
CONFIG_TRACE_CLOCK=y
CONFIG_TREE_RCU=y
CONFIG_TREE_SRCU=y
CONFIG_TUNE_GENERIC=y
CONFIG_UCS2_STRING=y
CONFIG_UEVENT_HELPER_PATH=""
CONFIG_USB=y
CONFIG_USB_COMMON=y
CONFIG_USB_EHCI_HCD=y
CONFIG_USB_EHCI_HCD_PLATFORM=y
CONFIG_USB_GADGET=y
CONFIG_USB_MUSB_DUAL_ROLE=y
CONFIG_USB_MUSB_HDRC=y
CONFIG_USB_MUSB_SUNXI=y
CONFIG_USB_OHCI_HCD=y
CONFIG_USB_OHCI_HCD_PLATFORM=y
CONFIG_USB_PHY=y
CONFIG_USB_SUPPORT=y
CONFIG_USB_XHCI_HCD=y
# CONFIG_USB_XHCI_PLATFORM is not set
CONFIG_USER_STACKTRACE_SUPPORT=y
# CONFIG_VHOST_MENU is not set
# CONFIG_VIRTIO_MENU is not set
CONFIG_VMAP_STACK=y
CONFIG_VM_EVENT_COUNTERS=y
CONFIG_WATCHDOG_CORE=y
CONFIG_XPS=y
CONFIG_ZONE_DMA32=y
+1
View File
@@ -128,6 +128,7 @@ CONFIG_FONT_AUTOSELECT=y
CONFIG_FONT_SUPPORT=y
CONFIG_FPU=y
CONFIG_FRAME_POINTER=y
CONFIG_F2FS_FS=y
CONFIG_FS_IOMAP=y
CONFIG_FS_MBCACHE=y
CONFIG_FUNCTION_ALIGNMENT=0
@@ -1,116 +0,0 @@
From 3341f884d75929a009801d4299d219e64c64a33c Mon Sep 17 00:00:00 2001
From: Maksim Kiselev <bigunclemax@gmail.com>
Date: Sat, 5 Aug 2023 21:05:01 +0300
Subject: [PATCH 07/14] ASoC: dt-bindings: sun4i-a10-codec: Add binding for
Allwinner D1 SoC
The Allwinner D1 SoC has a internal audio codec that similar to previous
ones, but it contains a three ADC channels instead of two, and also has
a separate clocks for ADC and DAC modules.
Signed-off-by: Maksim Kiselev <bigunclemax@gmail.com>
Reviewed-by: Rob Herring <robh@kernel.org>
---
.../sound/allwinner,sun4i-a10-codec.yaml | 64 ++++++++++++++++---
1 file changed, 56 insertions(+), 8 deletions(-)
--- a/Documentation/devicetree/bindings/sound/allwinner,sun4i-a10-codec.yaml
+++ b/Documentation/devicetree/bindings/sound/allwinner,sun4i-a10-codec.yaml
@@ -22,6 +22,7 @@ properties:
- allwinner,sun8i-a23-codec
- allwinner,sun8i-h3-codec
- allwinner,sun8i-v3s-codec
+ - allwinner,sun20i-d1-codec
reg:
maxItems: 1
@@ -29,15 +30,9 @@ properties:
interrupts:
maxItems: 1
- clocks:
- items:
- - description: Bus Clock
- - description: Module Clock
+ clocks: true
- clock-names:
- items:
- - const: apb
- - const: codec
+ clock-names: true
dmas:
items:
@@ -106,11 +101,42 @@ allOf:
- if:
properties:
compatible:
+ const: allwinner,sun20i-d1-codec
+ then:
+ properties:
+ clocks:
+ items:
+ - description: Bus Clock
+ - description: ADC Module Clock
+ - description: DAC Module Clock
+
+ clock-names:
+ items:
+ - const: apb
+ - const: adc
+ - const: dac
+
+ else:
+ properties:
+ clocks:
+ items:
+ - description: Bus Clock
+ - description: Module Clock
+
+ clock-names:
+ items:
+ - const: apb
+ - const: codec
+
+ - if:
+ properties:
+ compatible:
enum:
- allwinner,sun6i-a31-codec
- allwinner,sun8i-a23-codec
- allwinner,sun8i-h3-codec
- allwinner,sun8i-v3s-codec
+ - allwinner,sun20i-d1-codec
then:
if:
@@ -225,6 +251,28 @@ allOf:
- Headphone
- Headset Mic
- Line In
+ - Line Out
+ - Mic
+ - Speaker
+
+ - if:
+ properties:
+ compatible:
+ enum:
+ - allwinner,sun20i-d1-codec
+
+ then:
+ properties:
+ allwinner,audio-routing:
+ items:
+ enum:
+ - HP
+ - LINEIN
+ - MIC3
+ - MBIAS
+ - Headphone
+ - Headset Mic
+ - Line In
- Line Out
- Mic
- Speaker
@@ -1,51 +0,0 @@
From 64efc9cc704d27c60dc9c96a02d842f22dbdfeae Mon Sep 17 00:00:00 2001
From: Maksim Kiselev <bigunclemax@gmail.com>
Date: Sat, 5 Aug 2023 21:05:02 +0300
Subject: [PATCH 08/14] ASoC: dt-bindings: Add schema for
"allwinner,sun20i-d1-codec-analog"
Add a DT schema to describe the analog part of the Allwinner D1/T113s
internal audio codec.
Signed-off-by: Maksim Kiselev <bigunclemax@gmail.com>
---
.../allwinner,sun20i-d1-codec-analog.yaml | 33 +++++++++++++++++++
1 file changed, 33 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/allwinner,sun20i-d1-codec-analog.yaml
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/allwinner,sun20i-d1-codec-analog.yaml
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/allwinner,sun20i-d1-codec-analog.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Allwinner D1 Analog Codec
+
+maintainers:
+ - Maksim Kiselev <bigunclemax@gmail.com>
+
+properties:
+ compatible:
+ const: allwinner,sun20i-d1-codec-analog
+
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ codec_analog: codec-analog@2030300 {
+ compatible = "allwinner,sun20i-d1-codec-analog";
+ reg = <0x02030300 0xd00>;
+ };
+
+...
+
@@ -1,615 +0,0 @@
From 0963766bc665769aebf370d44ee3a97facfbca57 Mon Sep 17 00:00:00 2001
From: Maksim Kiselev <bigunclemax@gmail.com>
Date: Sat, 5 Aug 2023 21:05:03 +0300
Subject: [PATCH 09/14] ASoC: sunxi: sun4i-codec: add basic support for D1
audio codec
Allwinner D1 has an audio codec similar to earlier ones, but it comes
with 3 channel ADC instead of 2, and many registers are moved.
Add basic support for it.
Signed-off-by: Maksim Kiselev <bigunclemax@gmail.com>
---
sound/soc/sunxi/sun4i-codec.c | 364 ++++++++++++++++++++++++++++------
1 file changed, 300 insertions(+), 64 deletions(-)
--- a/sound/soc/sunxi/sun4i-codec.c
+++ b/sound/soc/sunxi/sun4i-codec.c
@@ -229,16 +229,66 @@
/* TODO H3 DAP (Digital Audio Processing) bits */
+/*
+ * sun20i D1 and similar codecs specific registers
+ *
+ * Almost all registers moved on D1, including ADC digital controls,
+ * FIFO and RX data registers. Only DAC control are at the same offset.
+ */
+
+#define SUN20I_D1_CODEC_DAC_VOL_CTRL (0x04)
+#define SUN20I_D1_CODEC_DAC_VOL_SEL (16)
+#define SUN20I_D1_CODEC_DAC_VOL_L (8)
+#define SUN20I_D1_CODEC_DAC_VOL_R (0)
+#define SUN20I_D1_CODEC_DAC_FIFOC (0x10)
+#define SUN20I_D1_CODEC_ADC_FIFOC (0x30)
+#define SUN20I_D1_CODEC_ADC_FIFOC_EN_AD (28)
+#define SUN20I_D1_CODEC_ADC_FIFOC_RX_SAMPLE_BITS (16)
+#define SUN20I_D1_CODEC_ADC_FIFOC_RX_TRIG_LEVEL (4)
+#define SUN20I_D1_CODEC_ADC_FIFOC_ADC_DRQ_EN (3)
+#define SUN20I_D1_CODEC_ADC_VOL_CTRL1 (0x34)
+#define SUN20I_D1_CODEC_ADC_VOL_CTRL1_ADC3_VOL (16)
+#define SUN20I_D1_CODEC_ADC_VOL_CTRL1_ADC2_VOL (8)
+#define SUN20I_D1_CODEC_ADC_VOL_CTRL1_ADC1_VOL (0)
+#define SUN20I_D1_CODEC_ADC_RXDATA (0x40)
+#define SUN20I_D1_CODEC_ADC_DIG_CTRL (0x50)
+#define SUN20I_D1_CODEC_ADC_DIG_CTRL_ADC3_CH_EN (2)
+#define SUN20I_D1_CODEC_ADC_DIG_CTRL_ADC2_CH_EN (1)
+#define SUN20I_D1_CODEC_ADC_DIG_CTRL_ADC1_CH_EN (0)
+#define SUN20I_D1_CODEC_VRA1SPEEDUP_DOWN_CTRL (0x54)
+
+/* TODO D1 DAP (Digital Audio Processing) bits */
+
+struct sun4i_codec;
+
+struct sun4i_codec_quirks {
+ const struct regmap_config *regmap_config;
+ const struct snd_soc_component_driver *codec;
+ struct snd_soc_card * (*create_card)(struct device *dev);
+ struct reg_field reg_dac_fifoc; /* used for regmap_field */
+ struct reg_field reg_adc_fifoc; /* used for regmap_field */
+ unsigned int adc_drq_en;
+ unsigned int rx_sample_bits;
+ unsigned int rx_trig_level;
+ unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */
+ unsigned int reg_adc_rxdata; /* RX FIFO offset for DMA config */
+ bool has_reset;
+ bool has_dual_clock;
+};
+
struct sun4i_codec {
struct device *dev;
struct regmap *regmap;
struct clk *clk_apb;
- struct clk *clk_module;
+ struct clk *clk_module; /* used for ADC if clocks are separate */
+ struct clk *clk_module_dac;
struct reset_control *rst;
struct gpio_desc *gpio_pa;
struct gpio_desc *gpio_hp;
+ const struct sun4i_codec_quirks *quirks;
- /* ADC_FIFOC register is at different offset on different SoCs */
+ /* DAC/ADC FIFOC registers are at different offset on different SoCs */
+ struct regmap_field *reg_dac_fifoc;
struct regmap_field *reg_adc_fifoc;
struct snd_dmaengine_dai_dma_data capture_dma_data;
@@ -248,33 +298,33 @@ struct sun4i_codec {
static void sun4i_codec_start_playback(struct sun4i_codec *scodec)
{
/* Flush TX FIFO */
- regmap_set_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH));
+ regmap_field_set_bits(scodec->reg_dac_fifoc,
+ BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH));
/* Enable DAC DRQ */
- regmap_set_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN));
+ regmap_field_set_bits(scodec->reg_dac_fifoc,
+ BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN));
}
static void sun4i_codec_stop_playback(struct sun4i_codec *scodec)
{
/* Disable DAC DRQ */
- regmap_clear_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN));
+ regmap_field_clear_bits(scodec->reg_dac_fifoc,
+ BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN));
}
static void sun4i_codec_start_capture(struct sun4i_codec *scodec)
{
/* Enable ADC DRQ */
regmap_field_set_bits(scodec->reg_adc_fifoc,
- BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN));
+ BIT(scodec->quirks->adc_drq_en));
}
static void sun4i_codec_stop_capture(struct sun4i_codec *scodec)
{
/* Disable ADC DRQ */
regmap_field_clear_bits(scodec->reg_adc_fifoc,
- BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN));
+ BIT(scodec->quirks->adc_drq_en));
}
static int sun4i_codec_trigger(struct snd_pcm_substream *substream, int cmd,
@@ -323,8 +373,8 @@ static int sun4i_codec_prepare_capture(s
/* Set RX FIFO trigger level */
regmap_field_update_bits(scodec->reg_adc_fifoc,
- 0xf << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL,
- 0x7 << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL);
+ 0xf << scodec->quirks->rx_trig_level,
+ 0x7 << scodec->quirks->rx_trig_level);
/*
* FIXME: Undocumented in the datasheet, but
@@ -358,13 +408,13 @@ static int sun4i_codec_prepare_playback(
u32 val;
/* Flush the TX FIFO */
- regmap_set_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH));
+ regmap_field_set_bits(scodec->reg_dac_fifoc,
+ BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH));
/* Set TX FIFO Empty Trigger Level */
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- 0x3f << SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL,
- 0xf << SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL);
+ regmap_field_update_bits(scodec->reg_dac_fifoc,
+ 0x3f << SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL,
+ 0xf << SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL);
if (substream->runtime->rate > 32000)
/* Use 64 bits FIR filter */
@@ -373,13 +423,12 @@ static int sun4i_codec_prepare_playback(
/* Use 32 bits FIR filter */
val = BIT(SUN4I_CODEC_DAC_FIFOC_FIR_VERSION);
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- BIT(SUN4I_CODEC_DAC_FIFOC_FIR_VERSION),
- val);
+ regmap_field_update_bits(scodec->reg_dac_fifoc,
+ BIT(SUN4I_CODEC_DAC_FIFOC_FIR_VERSION), val);
/* Send zeros when we have an underrun */
- regmap_clear_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- BIT(SUN4I_CODEC_DAC_FIFOC_SEND_LASAT));
+ regmap_field_clear_bits(scodec->reg_dac_fifoc,
+ BIT(SUN4I_CODEC_DAC_FIFOC_SEND_LASAT));
return 0;
};
@@ -474,30 +523,32 @@ static int sun4i_codec_hw_params_capture
7 << SUN4I_CODEC_ADC_FIFOC_ADC_FS,
hwrate << SUN4I_CODEC_ADC_FIFOC_ADC_FS);
- /* Set the number of channels we want to use */
- if (params_channels(params) == 1)
- regmap_field_set_bits(scodec->reg_adc_fifoc,
- BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN));
- else
- regmap_field_clear_bits(scodec->reg_adc_fifoc,
- BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN));
+ if (!scodec->quirks->has_dual_clock) {
+ /* Set the number of channels we want to use */
+ if (params_channels(params) == 1)
+ regmap_field_set_bits(scodec->reg_adc_fifoc,
+ BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN));
+ else
+ regmap_field_clear_bits(scodec->reg_adc_fifoc,
+ BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN));
+ }
/* Set the number of sample bits to either 16 or 24 bits */
if (hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min == 32) {
regmap_field_set_bits(scodec->reg_adc_fifoc,
- BIT(SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS));
+ BIT(scodec->quirks->rx_sample_bits));
regmap_field_clear_bits(scodec->reg_adc_fifoc,
- BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE));
+ BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE));
scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
} else {
regmap_field_clear_bits(scodec->reg_adc_fifoc,
- BIT(SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS));
+ BIT(scodec->quirks->rx_sample_bits));
/* Fill most significant bits with valid data MSB */
regmap_field_set_bits(scodec->reg_adc_fifoc,
- BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE));
+ BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE));
scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
}
@@ -512,9 +563,9 @@ static int sun4i_codec_hw_params_playbac
u32 val;
/* Set DAC sample rate */
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- 7 << SUN4I_CODEC_DAC_FIFOC_DAC_FS,
- hwrate << SUN4I_CODEC_DAC_FIFOC_DAC_FS);
+ regmap_field_update_bits(scodec->reg_dac_fifoc,
+ 7 << SUN4I_CODEC_DAC_FIFOC_DAC_FS,
+ hwrate << SUN4I_CODEC_DAC_FIFOC_DAC_FS);
/* Set the number of channels we want to use */
if (params_channels(params) == 1)
@@ -522,27 +573,26 @@ static int sun4i_codec_hw_params_playbac
else
val = 0;
- regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- BIT(SUN4I_CODEC_DAC_FIFOC_MONO_EN),
- val);
+ regmap_field_update_bits(scodec->reg_dac_fifoc,
+ BIT(SUN4I_CODEC_DAC_FIFOC_MONO_EN), val);
/* Set the number of sample bits to either 16 or 24 bits */
if (hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min == 32) {
- regmap_set_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS));
+ regmap_field_set_bits(scodec->reg_dac_fifoc,
+ BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS));
/* Set TX FIFO mode to padding the LSBs with 0 */
- regmap_clear_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE));
+ regmap_field_clear_bits(scodec->reg_dac_fifoc,
+ BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE));
scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
} else {
- regmap_clear_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS));
+ regmap_field_clear_bits(scodec->reg_dac_fifoc,
+ BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS));
/* Set TX FIFO mode to repeat the MSB */
- regmap_set_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE));
+ regmap_field_set_bits(scodec->reg_dac_fifoc,
+ BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE));
scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
}
@@ -563,7 +613,11 @@ static int sun4i_codec_hw_params(struct
if (!clk_freq)
return -EINVAL;
- ret = clk_set_rate(scodec->clk_module, clk_freq);
+ if (scodec->clk_module_dac &&
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = clk_set_rate(scodec->clk_module_dac, clk_freq);
+ else
+ ret = clk_set_rate(scodec->clk_module, clk_freq);
if (ret)
return ret;
@@ -589,10 +643,14 @@ static int sun4i_codec_startup(struct sn
* Stop issuing DRQ when we have room for less than 16 samples
* in our TX FIFO
*/
- regmap_set_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC,
- 3 << SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT);
+ regmap_field_set_bits(scodec->reg_dac_fifoc,
+ 3 << SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT);
- return clk_prepare_enable(scodec->clk_module);
+ if (scodec->clk_module_dac &&
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return clk_prepare_enable(scodec->clk_module_dac);
+ else
+ return clk_prepare_enable(scodec->clk_module);
}
static void sun4i_codec_shutdown(struct snd_pcm_substream *substream,
@@ -601,7 +659,11 @@ static void sun4i_codec_shutdown(struct
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card);
- clk_disable_unprepare(scodec->clk_module);
+ if (scodec->clk_module_dac &&
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ clk_disable_unprepare(scodec->clk_module_dac);
+ else
+ clk_disable_unprepare(scodec->clk_module);
}
static const struct snd_soc_dai_ops sun4i_codec_dai_ops = {
@@ -1218,6 +1280,55 @@ static const struct snd_soc_component_dr
.endianness = 1,
};
+/* sun20i D1 codec */
+static const DECLARE_TLV_DB_SCALE(sun20i_d1_codec_dvol_scale, -12000, 75, 1);
+
+static const struct snd_kcontrol_new sun20i_d1_codec_codec_controls[] = {
+ SOC_SINGLE_TLV("DAC Playback Volume", SUN4I_CODEC_DAC_DPC,
+ SUN4I_CODEC_DAC_DPC_DVOL, 0x3f, 1,
+ sun6i_codec_dvol_scale),
+ SOC_DOUBLE_TLV("DAC Front Playback Volume", SUN20I_D1_CODEC_DAC_VOL_CTRL,
+ SUN20I_D1_CODEC_DAC_VOL_L, SUN20I_D1_CODEC_DAC_VOL_R,
+ 0xFF, 0, sun20i_d1_codec_dvol_scale),
+
+ SOC_SINGLE_TLV("ADC1 Capture Volume", SUN20I_D1_CODEC_ADC_VOL_CTRL1,
+ SUN20I_D1_CODEC_ADC_VOL_CTRL1_ADC1_VOL, 0xff, 0,
+ sun20i_d1_codec_dvol_scale),
+ SOC_SINGLE_TLV("ADC2 Capture Volume", SUN20I_D1_CODEC_ADC_VOL_CTRL1,
+ SUN20I_D1_CODEC_ADC_VOL_CTRL1_ADC2_VOL, 0xff, 0,
+ sun20i_d1_codec_dvol_scale),
+ SOC_SINGLE_TLV("ADC3 Capture Volume", SUN20I_D1_CODEC_ADC_VOL_CTRL1,
+ SUN20I_D1_CODEC_ADC_VOL_CTRL1_ADC3_VOL, 0xff, 0,
+ sun20i_d1_codec_dvol_scale),
+};
+
+static const struct snd_soc_dapm_widget sun20i_d1_codec_codec_widgets[] = {
+ /* Digital parts of the ADCs */
+ SND_SOC_DAPM_SUPPLY("ADC Enable", SUN20I_D1_CODEC_ADC_FIFOC,
+ SUN20I_D1_CODEC_ADC_FIFOC_EN_AD, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("ADC1 CH Enable", SUN20I_D1_CODEC_ADC_DIG_CTRL,
+ SUN20I_D1_CODEC_ADC_DIG_CTRL_ADC1_CH_EN, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("ADC2 CH Enable", SUN20I_D1_CODEC_ADC_DIG_CTRL,
+ SUN20I_D1_CODEC_ADC_DIG_CTRL_ADC2_CH_EN, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("ADC3 CH Enable", SUN20I_D1_CODEC_ADC_DIG_CTRL,
+ SUN20I_D1_CODEC_ADC_DIG_CTRL_ADC3_CH_EN, 0, NULL, 0),
+ /* Digital parts of the DACs */
+ SND_SOC_DAPM_SUPPLY("DAC Enable", SUN4I_CODEC_DAC_DPC,
+ SUN4I_CODEC_DAC_DPC_EN_DA, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("DAC VOL_SEL Enable", SUN20I_D1_CODEC_DAC_VOL_CTRL,
+ SUN20I_D1_CODEC_DAC_VOL_SEL, 0, NULL, 0),
+};
+
+static const struct snd_soc_component_driver sun20i_d1_codec_codec = {
+ .controls = sun20i_d1_codec_codec_controls,
+ .num_controls = ARRAY_SIZE(sun20i_d1_codec_codec_controls),
+ .dapm_widgets = sun20i_d1_codec_codec_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sun20i_d1_codec_codec_widgets),
+ .idle_bias_on = 1,
+ .use_pmdown_time = 1,
+ .endianness = 1,
+};
+
static const struct snd_soc_component_driver sun4i_codec_component = {
.name = "sun4i-codec",
.legacy_dai_naming = 1,
@@ -1564,6 +1675,66 @@ static struct snd_soc_card *sun8i_v3s_co
return card;
};
+static const struct snd_soc_dapm_route sun20i_d1_codec_card_routes[] = {
+ /* ADC Routes */
+ { "ADC1", NULL, "ADC Enable" },
+ { "ADC2", NULL, "ADC Enable" },
+ { "ADC3", NULL, "ADC Enable" },
+ { "ADC1", NULL, "ADC1 CH Enable" },
+ { "ADC2", NULL, "ADC2 CH Enable" },
+ { "ADC3", NULL, "ADC3 CH Enable" },
+ { "Codec Capture", NULL, "ADC1" },
+ { "Codec Capture", NULL, "ADC2" },
+ { "Codec Capture", NULL, "ADC3" },
+
+ /* DAC Routes */
+ { "Left DAC", NULL, "DAC Enable" },
+ { "Right DAC", NULL, "DAC Enable" },
+ { "Left DAC", NULL, "DAC VOL_SEL Enable" },
+ { "Right DAC", NULL, "DAC VOL_SEL Enable" },
+ { "Left DAC", NULL, "Codec Playback" },
+ { "Right DAC", NULL, "Codec Playback" },
+};
+
+static struct snd_soc_card *sun20i_d1_codec_create_card(struct device *dev)
+{
+ struct snd_soc_card *card;
+ int ret;
+
+ card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return ERR_PTR(-ENOMEM);
+
+ aux_dev.dlc.of_node = of_parse_phandle(dev->of_node,
+ "allwinner,codec-analog-controls",
+ 0);
+ if (!aux_dev.dlc.of_node) {
+ dev_err(dev, "Can't find analog controls for codec.\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ card->dai_link = sun4i_codec_create_link(dev, &card->num_links);
+ if (!card->dai_link)
+ return ERR_PTR(-ENOMEM);
+
+ card->dev = dev;
+ card->owner = THIS_MODULE;
+ card->name = "D1 Audio Codec";
+ card->dapm_widgets = sun6i_codec_card_dapm_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets);
+ card->dapm_routes = sun20i_d1_codec_card_routes;
+ card->num_dapm_routes = ARRAY_SIZE(sun20i_d1_codec_card_routes);
+ card->aux_dev = &aux_dev;
+ card->num_aux_devs = 1;
+ card->fully_routed = true;
+
+ ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing");
+ if (ret)
+ dev_warn(dev, "failed to parse audio-routing: %d\n", ret);
+
+ return card;
+};
+
static const struct regmap_config sun4i_codec_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
@@ -1606,21 +1777,22 @@ static const struct regmap_config sun8i_
.max_register = SUN8I_H3_CODEC_ADC_DBG,
};
-struct sun4i_codec_quirks {
- const struct regmap_config *regmap_config;
- const struct snd_soc_component_driver *codec;
- struct snd_soc_card * (*create_card)(struct device *dev);
- struct reg_field reg_adc_fifoc; /* used for regmap_field */
- unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */
- unsigned int reg_adc_rxdata; /* RX FIFO offset for DMA config */
- bool has_reset;
+static const struct regmap_config sun20i_d1_codec_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = SUN20I_D1_CODEC_VRA1SPEEDUP_DOWN_CTRL,
};
static const struct sun4i_codec_quirks sun4i_codec_quirks = {
.regmap_config = &sun4i_codec_regmap_config,
.codec = &sun4i_codec_codec,
.create_card = sun4i_codec_create_card,
+ .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31),
.reg_adc_fifoc = REG_FIELD(SUN4I_CODEC_ADC_FIFOC, 0, 31),
+ .adc_drq_en = SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN,
+ .rx_sample_bits = SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS,
+ .rx_trig_level = SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL,
.reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA,
.reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA,
};
@@ -1629,7 +1801,11 @@ static const struct sun4i_codec_quirks s
.regmap_config = &sun6i_codec_regmap_config,
.codec = &sun6i_codec_codec,
.create_card = sun6i_codec_create_card,
+ .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31),
.reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31),
+ .adc_drq_en = SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN,
+ .rx_sample_bits = SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS,
+ .rx_trig_level = SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL,
.reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA,
.reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA,
.has_reset = true,
@@ -1639,7 +1815,11 @@ static const struct sun4i_codec_quirks s
.regmap_config = &sun7i_codec_regmap_config,
.codec = &sun7i_codec_codec,
.create_card = sun4i_codec_create_card,
+ .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31),
.reg_adc_fifoc = REG_FIELD(SUN4I_CODEC_ADC_FIFOC, 0, 31),
+ .adc_drq_en = SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN,
+ .rx_sample_bits = SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS,
+ .rx_trig_level = SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL,
.reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA,
.reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA,
};
@@ -1648,7 +1828,11 @@ static const struct sun4i_codec_quirks s
.regmap_config = &sun8i_a23_codec_regmap_config,
.codec = &sun8i_a23_codec_codec,
.create_card = sun8i_a23_codec_create_card,
+ .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31),
.reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31),
+ .adc_drq_en = SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN,
+ .rx_sample_bits = SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS,
+ .rx_trig_level = SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL,
.reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA,
.reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA,
.has_reset = true,
@@ -1663,7 +1847,11 @@ static const struct sun4i_codec_quirks s
*/
.codec = &sun8i_a23_codec_codec,
.create_card = sun8i_h3_codec_create_card,
+ .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31),
.reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31),
+ .adc_drq_en = SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN,
+ .rx_sample_bits = SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS,
+ .rx_trig_level = SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL,
.reg_dac_txdata = SUN8I_H3_CODEC_DAC_TXDATA,
.reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA,
.has_reset = true,
@@ -1677,12 +1865,31 @@ static const struct sun4i_codec_quirks s
*/
.codec = &sun8i_a23_codec_codec,
.create_card = sun8i_v3s_codec_create_card,
+ .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31),
.reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31),
+ .adc_drq_en = SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN,
+ .rx_sample_bits = SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS,
+ .rx_trig_level = SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL,
.reg_dac_txdata = SUN8I_H3_CODEC_DAC_TXDATA,
.reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA,
.has_reset = true,
};
+static const struct sun4i_codec_quirks sun20i_d1_codec_quirks = {
+ .regmap_config = &sun20i_d1_codec_regmap_config,
+ .codec = &sun20i_d1_codec_codec,
+ .create_card = sun20i_d1_codec_create_card,
+ .reg_dac_fifoc = REG_FIELD(SUN20I_D1_CODEC_DAC_FIFOC, 0, 31),
+ .reg_adc_fifoc = REG_FIELD(SUN20I_D1_CODEC_ADC_FIFOC, 0, 31),
+ .adc_drq_en = SUN20I_D1_CODEC_ADC_FIFOC_ADC_DRQ_EN,
+ .rx_sample_bits = SUN20I_D1_CODEC_ADC_FIFOC_RX_SAMPLE_BITS,
+ .rx_trig_level = SUN20I_D1_CODEC_ADC_FIFOC_RX_TRIG_LEVEL,
+ .reg_dac_txdata = SUN8I_H3_CODEC_DAC_TXDATA,
+ .reg_adc_rxdata = SUN20I_D1_CODEC_ADC_RXDATA,
+ .has_reset = true,
+ .has_dual_clock = true,
+};
+
static const struct of_device_id sun4i_codec_of_match[] = {
{
.compatible = "allwinner,sun4i-a10-codec",
@@ -1708,6 +1915,10 @@ static const struct of_device_id sun4i_c
.compatible = "allwinner,sun8i-v3s-codec",
.data = &sun8i_v3s_codec_quirks,
},
+ {
+ .compatible = "allwinner,sun20i-d1-codec",
+ .data = &sun20i_d1_codec_quirks,
+ },
{}
};
MODULE_DEVICE_TABLE(of, sun4i_codec_of_match);
@@ -1736,6 +1947,7 @@ static int sun4i_codec_probe(struct plat
dev_err(&pdev->dev, "Failed to determine the quirks to use\n");
return -ENODEV;
}
+ scodec->quirks = quirks;
scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base,
quirks->regmap_config);
@@ -1751,10 +1963,24 @@ static int sun4i_codec_probe(struct plat
return PTR_ERR(scodec->clk_apb);
}
- scodec->clk_module = devm_clk_get(&pdev->dev, "codec");
- if (IS_ERR(scodec->clk_module)) {
- dev_err(&pdev->dev, "Failed to get the module clock\n");
- return PTR_ERR(scodec->clk_module);
+ if (quirks->has_dual_clock) {
+ scodec->clk_module = devm_clk_get(&pdev->dev, "adc");
+ if (IS_ERR(scodec->clk_module)) {
+ dev_err(&pdev->dev, "Failed to get the ADC module clock\n");
+ return PTR_ERR(scodec->clk_module);
+ }
+
+ scodec->clk_module_dac = devm_clk_get(&pdev->dev, "dac");
+ if (IS_ERR(scodec->clk_module_dac)) {
+ dev_err(&pdev->dev, "Failed to get the DAC module clock\n");
+ return PTR_ERR(scodec->clk_module_dac);
+ }
+ } else {
+ scodec->clk_module = devm_clk_get(&pdev->dev, "codec");
+ if (IS_ERR(scodec->clk_module)) {
+ dev_err(&pdev->dev, "Failed to get the module clock\n");
+ return PTR_ERR(scodec->clk_module);
+ }
}
if (quirks->has_reset) {
@@ -1790,6 +2016,16 @@ static int sun4i_codec_probe(struct plat
dev_err(&pdev->dev, "Failed to create regmap fields: %d\n",
ret);
return ret;
+ }
+
+ scodec->reg_dac_fifoc = devm_regmap_field_alloc(&pdev->dev,
+ scodec->regmap,
+ quirks->reg_dac_fifoc);
+ if (IS_ERR(scodec->reg_dac_fifoc)) {
+ ret = PTR_ERR(scodec->reg_dac_fifoc);
+ dev_err(&pdev->dev, "Failed to create regmap fields: %d\n",
+ ret);
+ return ret;
}
/* Enable the bus clock */
@@ -1,274 +0,0 @@
From c8c3c516ca5c38e7858055ce0137efde17a07190 Mon Sep 17 00:00:00 2001
From: Maksim Kiselev <bigunclemax@gmail.com>
Date: Sat, 5 Aug 2023 21:05:04 +0300
Subject: [PATCH 10/14] ASoC: sunxi: Add new driver for Allwinner D1/T113s
codec's analog path controls
The internal codec on D1/T113s is split into 2 parts like the previous
ones. But now analog path controls registers are mapped directly
on the bus, right after the registers of the digital part.
Add an ASoC component driver for it. This should be tied to the codec
audio card as an auxiliary device.
Signed-off-by: Maksim Kiselev <bigunclemax@gmail.com>
---
sound/soc/sunxi/Kconfig | 11 ++
sound/soc/sunxi/Makefile | 1 +
sound/soc/sunxi/sun20i-d1-codec-analog.c | 220 +++++++++++++++++++++++
3 files changed, 232 insertions(+)
create mode 100644 sound/soc/sunxi/sun20i-d1-codec-analog.c
--- a/sound/soc/sunxi/Kconfig
+++ b/sound/soc/sunxi/Kconfig
@@ -38,6 +38,17 @@ config SND_SUN50I_CODEC_ANALOG
Say Y or M if you want to add support for the analog controls for
the codec embedded in Allwinner A64 SoC.
+config SND_SUN20I_D1_CODEC_ANALOG
+ tristate "Allwinner D1 Codec Analog Controls Support"
+ depends on ARCH_SUNXI || COMPILE_TEST
+ select REGMAP_MMIO
+ help
+ This option enables the analog controls part of the internal audio
+ codec for Allwinner D1/T113s SoCs family.
+
+ Say Y or M if you want to add support for the analog part of
+ the D1/T113s audio codec.
+
config SND_SUN4I_I2S
tristate "Allwinner A10 I2S Support"
select SND_SOC_GENERIC_DMAENGINE_PCM
--- a/sound/soc/sunxi/Makefile
+++ b/sound/soc/sunxi/Makefile
@@ -4,6 +4,7 @@ obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s
obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o
obj-$(CONFIG_SND_SUN8I_CODEC_ANALOG) += sun8i-codec-analog.o
obj-$(CONFIG_SND_SUN50I_CODEC_ANALOG) += sun50i-codec-analog.o
+obj-$(CONFIG_SND_SUN20I_D1_CODEC_ANALOG) += sun20i-d1-codec-analog.o
obj-$(CONFIG_SND_SUN8I_CODEC) += sun8i-codec.o
obj-$(CONFIG_SND_SUN8I_ADDA_PR_REGMAP) += sun8i-adda-pr-regmap.o
obj-$(CONFIG_SND_SUN50I_DMIC) += sun50i-dmic.o
--- /dev/null
+++ b/sound/soc/sunxi/sun20i-d1-codec-analog.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * This driver supports the analog controls for the internal codec
+ * found in Allwinner's D1/T113s SoCs family.
+ *
+ * Based on sun50i-codec-analog.c
+ */
+
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+
+/* Codec analog control register offsets and bit fields */
+#define SUN20I_D1_ADDA_ADC1 (0x00)
+#define SUN20I_D1_ADDA_ADC2 (0x04)
+#define SUN20I_D1_ADDA_ADC3 (0x08)
+#define SUN20I_D1_ADDA_ADC_EN (31)
+#define SUN20I_D1_ADDA_ADC_PGA_EN (30)
+#define SUN20I_D1_ADDA_ADC_MIC_SIN_EN (28)
+#define SUN20I_D1_ADDA_ADC_LINEINLEN (23)
+#define SUN20I_D1_ADDA_ADC_PGA_GAIN (8)
+
+#define SUN20I_D1_ADDA_DAC (0x10)
+#define SUN20I_D1_ADDA_DAC_DACL_EN (15)
+#define SUN20I_D1_ADDA_DAC_DACR_EN (14)
+
+#define SUN20I_D1_ADDA_MICBIAS (0x18)
+#define SUN20I_D1_ADDA_MICBIAS_MMICBIASEN (7)
+
+#define SUN20I_D1_ADDA_RAMP (0x1C)
+#define SUN20I_D1_ADDA_RAMP_RD_EN (0)
+
+#define SUN20I_D1_ADDA_HP2 (0x40)
+#define SUN20I_D1_ADDA_HP2_HEADPHONE_GAIN (28)
+
+#define SUN20I_D1_ADDA_ADC_CUR_REG (0x4C)
+
+static const DECLARE_TLV_DB_RANGE(sun20i_d1_codec_adc_gain_scale,
+ 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1),
+ 1, 3, TLV_DB_SCALE_ITEM(600, 0, 0),
+ 4, 4, TLV_DB_SCALE_ITEM(900, 0, 0),
+ 5, 31, TLV_DB_SCALE_ITEM(1000, 100, 0),
+);
+
+static const DECLARE_TLV_DB_SCALE(sun20i_d1_codec_hp_vol_scale, -4200, 600, 0);
+
+/* volume controls */
+static const struct snd_kcontrol_new sun20i_d1_codec_controls[] = {
+ SOC_SINGLE_TLV("Headphone Playback Volume",
+ SUN20I_D1_ADDA_HP2,
+ SUN20I_D1_ADDA_HP2_HEADPHONE_GAIN, 0x7, 1,
+ sun20i_d1_codec_hp_vol_scale),
+ SOC_SINGLE_TLV("ADC1 Gain Capture Volume",
+ SUN20I_D1_ADDA_ADC1,
+ SUN20I_D1_ADDA_ADC_PGA_GAIN, 0x1f, 0,
+ sun20i_d1_codec_adc_gain_scale),
+ SOC_SINGLE_TLV("ADC2 Gain Capture Volume",
+ SUN20I_D1_ADDA_ADC2,
+ SUN20I_D1_ADDA_ADC_PGA_GAIN, 0x1f, 0,
+ sun20i_d1_codec_adc_gain_scale),
+ SOC_SINGLE_TLV("ADC3 Gain Capture Volume",
+ SUN20I_D1_ADDA_ADC3,
+ SUN20I_D1_ADDA_ADC_PGA_GAIN, 0x1f, 0,
+ sun20i_d1_codec_adc_gain_scale),
+};
+
+/* ADC mixer controls */
+static const struct snd_kcontrol_new sun20i_d1_codec_mixer_controls[] = {
+ SOC_DAPM_DOUBLE_R("Line In Switch",
+ SUN20I_D1_ADDA_ADC1,
+ SUN20I_D1_ADDA_ADC2,
+ SUN20I_D1_ADDA_ADC_LINEINLEN, 1, 0),
+};
+
+static const char * const sun20i_d1_codec_mic3_src_enum_text[] = {
+ "Differential", "Single",
+};
+
+static SOC_ENUM_SINGLE_DECL(sun20i_d1_codec_mic3_src_enum,
+ SUN20I_D1_ADDA_ADC3,
+ SUN20I_D1_ADDA_ADC_MIC_SIN_EN,
+ sun20i_d1_codec_mic3_src_enum_text);
+
+static const struct snd_kcontrol_new sun20i_d1_codec_mic3_input_src[] = {
+ SOC_DAPM_ENUM("MIC3 Source Capture Route",
+ sun20i_d1_codec_mic3_src_enum),
+};
+
+static const struct snd_soc_dapm_widget sun20i_d1_codec_widgets[] = {
+ /* DAC */
+ SND_SOC_DAPM_DAC("Left DAC", NULL, SUN20I_D1_ADDA_DAC,
+ SUN20I_D1_ADDA_DAC_DACL_EN, 0),
+ SND_SOC_DAPM_DAC("Right DAC", NULL, SUN20I_D1_ADDA_DAC,
+ SUN20I_D1_ADDA_DAC_DACR_EN, 0),
+ /* ADC */
+ SND_SOC_DAPM_ADC("ADC1", NULL, SUN20I_D1_ADDA_ADC1,
+ SUN20I_D1_ADDA_ADC_EN, 0),
+ SND_SOC_DAPM_ADC("ADC2", NULL, SUN20I_D1_ADDA_ADC2,
+ SUN20I_D1_ADDA_ADC_EN, 0),
+ SND_SOC_DAPM_ADC("ADC3", NULL, SUN20I_D1_ADDA_ADC3,
+ SUN20I_D1_ADDA_ADC_EN, 0),
+
+ /* ADC Mixers */
+ SND_SOC_DAPM_MIXER("ADC1 Mixer", SND_SOC_NOPM, 0, 0,
+ sun20i_d1_codec_mixer_controls,
+ ARRAY_SIZE(sun20i_d1_codec_mixer_controls)),
+ SND_SOC_DAPM_MIXER("ADC2 Mixer", SND_SOC_NOPM, 0, 0,
+ sun20i_d1_codec_mixer_controls,
+ ARRAY_SIZE(sun20i_d1_codec_mixer_controls)),
+
+ /* Headphone */
+ SND_SOC_DAPM_OUTPUT("HP"),
+ SND_SOC_DAPM_SUPPLY("RAMP Enable", SUN20I_D1_ADDA_RAMP,
+ SUN20I_D1_ADDA_RAMP_RD_EN, 0, NULL, 0),
+
+ /* Line input */
+ SND_SOC_DAPM_INPUT("LINEIN"),
+
+ /* Microphone input */
+ SND_SOC_DAPM_INPUT("MIC3"),
+
+ /* Microphone input path */
+ SND_SOC_DAPM_MUX("MIC3 Source Capture Route", SND_SOC_NOPM, 0, 0,
+ sun20i_d1_codec_mic3_input_src),
+
+ SND_SOC_DAPM_PGA("Mic3 Amplifier", SUN20I_D1_ADDA_ADC3,
+ SUN20I_D1_ADDA_ADC_PGA_EN, 0, NULL, 0),
+
+ /* Microphone Bias */
+ SND_SOC_DAPM_SUPPLY("MBIAS", SUN20I_D1_ADDA_MICBIAS,
+ SUN20I_D1_ADDA_MICBIAS_MMICBIASEN, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route sun20i_d1_codec_routes[] = {
+ /* Headphone Routes */
+ { "HP", NULL, "Left DAC" },
+ { "HP", NULL, "Right DAC" },
+ { "HP", NULL, "RAMP Enable" },
+
+ /* Line input Routes */
+ { "ADC1", NULL, "ADC1 Mixer" },
+ { "ADC2", NULL, "ADC2 Mixer" },
+ { "ADC1 Mixer", "Line In Switch", "LINEIN" },
+ { "ADC2 Mixer", "Line In Switch", "LINEIN" },
+
+ /* Microphone Routes */
+ { "MIC3 Source Capture Route", "Differential", "MIC3" },
+ { "MIC3 Source Capture Route", "Single", "MIC3" },
+ { "Mic3 Amplifier", NULL, "MIC3 Source Capture Route" },
+ { "ADC3", NULL, "Mic3 Amplifier" },
+};
+
+static const struct snd_soc_component_driver sun20i_d1_codec_analog_cmpnt_drv = {
+ .controls = sun20i_d1_codec_controls,
+ .num_controls = ARRAY_SIZE(sun20i_d1_codec_controls),
+ .dapm_widgets = sun20i_d1_codec_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sun20i_d1_codec_widgets),
+ .dapm_routes = sun20i_d1_codec_routes,
+ .num_dapm_routes = ARRAY_SIZE(sun20i_d1_codec_routes),
+};
+
+static const struct of_device_id sun20i_d1_codec_analog_of_match[] = {
+ {
+ .compatible = "allwinner,sun20i-d1-codec-analog",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sun20i_d1_codec_analog_of_match);
+
+static const struct regmap_config sun20i_d1_codec_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = SUN20I_D1_ADDA_ADC_CUR_REG,
+};
+
+static int sun20i_d1_codec_analog_probe(struct platform_device *pdev)
+{
+ struct regmap *regmap;
+ void __iomem *base;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base)) {
+ dev_err(&pdev->dev, "Failed to map the registers\n");
+ return PTR_ERR(base);
+ }
+
+ regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &sun20i_d1_codec_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err(&pdev->dev, "Failed to create regmap\n");
+ return PTR_ERR(regmap);
+ }
+
+ return devm_snd_soc_register_component(&pdev->dev,
+ &sun20i_d1_codec_analog_cmpnt_drv,
+ NULL, 0);
+}
+
+static struct platform_driver sun20i_d1_codec_analog_driver = {
+ .driver = {
+ .name = "sun20i-d1-codec-analog",
+ .of_match_table = sun20i_d1_codec_analog_of_match,
+ },
+ .probe = sun20i_d1_codec_analog_probe,
+};
+module_platform_driver(sun20i_d1_codec_analog_driver);
+
+MODULE_DESCRIPTION("Allwinner internal codec analog controls driver for D1");
+MODULE_AUTHOR("Maksim Kiselev <bigunclemax@gmail.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sun20i-d1-codec-analog");
@@ -1,44 +0,0 @@
From edebcc9d47f0bfe9bd769a2c578dda16acbfbef2 Mon Sep 17 00:00:00 2001
From: Maksim Kiselev <bigunclemax@gmail.com>
Date: Sat, 5 Aug 2023 21:05:05 +0300
Subject: [PATCH 14/14] riscv: dts: allwinner: d1: Add device nodes for
internal audio codec
Add DT nodes for the internal D1/T113s audio codec and its analog part.
Signed-off-by: Maksim Kiselev <bigunclemax@gmail.com>
---
.../boot/dts/allwinner/sunxi-d1s-t113.dtsi | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
--- a/arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi
+++ b/arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi
@@ -166,6 +166,28 @@
#io-channel-cells = <1>;
};
+ codec: codec@2030000 {
+ #sound-dai-cells = <0>;
+ compatible = "allwinner,sun20i-d1-codec";
+ reg = <0x02030000 0x300>;
+ interrupts = <SOC_PERIPHERAL_IRQ(25) IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_AUDIO>,
+ <&ccu CLK_AUDIO_ADC>,
+ <&ccu CLK_AUDIO_DAC>;
+ clock-names = "apb", "adc", "dac";
+ resets = <&ccu RST_BUS_AUDIO>;
+ dmas = <&dma 7>, <&dma 7>;
+ dma-names = "rx", "tx";
+ allwinner,codec-analog-controls = <&codec_analog>;
+ status = "disabled";
+ };
+
+ codec_analog: codec-analog@2030300 {
+ compatible = "allwinner,sun20i-d1-codec-analog";
+ reg = <0x02030300 0xd00>;
+ status = "disabled";
+ };
+
dmic: dmic@2031000 {
compatible = "allwinner,sun20i-d1-dmic",
"allwinner,sun50i-h6-dmic";
@@ -1,60 +0,0 @@
From: Samuel Holland <samuel@sholland.org>
Subject: riscv: dts: allwinner: d1: Add LED controller node
Date: Sun, 29 Oct 2023 16:26:58 -0500
Allwinner D1 contains an LED controller. Add its devicetree node, as
well as the pinmux used by the reference board design.
Acked-by: Guo Ren <guoren@kernel.org>
Reviewed-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Tested-by: Trevor Woerner <twoerner@gmail.com>
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
(no changes since v5)
Changes in v5:
- New patch for v5
arch/riscv/boot/dts/allwinner/sun20i-d1.dtsi | 6 ++++++
arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi | 15 +++++++++++++++
2 files changed, 21 insertions(+)
--- a/arch/riscv/boot/dts/allwinner/sun20i-d1.dtsi
+++ b/arch/riscv/boot/dts/allwinner/sun20i-d1.dtsi
@@ -59,6 +59,12 @@
};
/omit-if-no-ref/
+ ledc_pc0_pin: ledc-pc0-pin {
+ pins = "PC0";
+ function = "ledc";
+ };
+
+ /omit-if-no-ref/
uart0_pb8_pins: uart0-pb8-pins {
pins = "PB8", "PB9";
function = "uart0";
--- a/arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi
+++ b/arch/riscv/boot/dts/allwinner/sunxi-d1s-t113.dtsi
@@ -156,6 +156,21 @@
#reset-cells = <1>;
};
+ ledc: led-controller@2008000 {
+ compatible = "allwinner,sun20i-d1-ledc",
+ "allwinner,sun50i-a100-ledc";
+ reg = <0x2008000 0x400>;
+ interrupts = <SOC_PERIPHERAL_IRQ(20) IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_LEDC>, <&ccu CLK_LEDC>;
+ clock-names = "bus", "mod";
+ resets = <&ccu RST_BUS_LEDC>;
+ dmas = <&dma 42>;
+ dma-names = "tx";
+ status = "disabled";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+
gpadc: adc@2009000 {
compatible = "allwinner,sun20i-d1-gpadc";
reg = <0x2009000 0x400>;
@@ -1,70 +0,0 @@
From: Samuel Holland <samuel@sholland.org>
Subject: riscv: dts: allwinner: d1: Add RGB LEDs to boards
Date: Sun, 29 Oct 2023 16:26:59 -0500
Some D1-based boards feature an onboard RGB LED. Enable them.
Acked-by: Guo Ren <guoren@kernel.org>
Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com>
Tested-by: Trevor Woerner <twoerner@gmail.com>
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
(no changes since v5)
Changes in v5:
- New patch for v5
.../boot/dts/allwinner/sun20i-d1-lichee-rv-dock.dts | 12 ++++++++++++
arch/riscv/boot/dts/allwinner/sun20i-d1-nezha.dts | 13 +++++++++++++
2 files changed, 25 insertions(+)
--- a/arch/riscv/boot/dts/allwinner/sun20i-d1-lichee-rv-dock.dts
+++ b/arch/riscv/boot/dts/allwinner/sun20i-d1-lichee-rv-dock.dts
@@ -59,6 +59,18 @@
status = "okay";
};
+&ledc {
+ pinctrl-0 = <&ledc_pc0_pin>;
+ pinctrl-names = "default";
+ status = "okay";
+
+ multi-led@0 {
+ reg = <0x0>;
+ color = <LED_COLOR_ID_RGB>;
+ function = LED_FUNCTION_STATUS;
+ };
+};
+
&mmc1 {
bus-width = <4>;
mmc-pwrseq = <&wifi_pwrseq>;
--- a/arch/riscv/boot/dts/allwinner/sun20i-d1-nezha.dts
+++ b/arch/riscv/boot/dts/allwinner/sun20i-d1-nezha.dts
@@ -22,6 +22,7 @@
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
+#include <dt-bindings/leds/common.h>
/dts-v1/;
@@ -121,6 +122,18 @@
};
};
+&ledc {
+ pinctrl-0 = <&ledc_pc0_pin>;
+ pinctrl-names = "default";
+ status = "okay";
+
+ multi-led@0 {
+ reg = <0x0>;
+ color = <LED_COLOR_ID_RGB>;
+ function = LED_FUNCTION_STATUS;
+ };
+};
+
&mdio {
ext_rgmii_phy: ethernet-phy@1 {
compatible = "ethernet-phy-ieee802.3-c22";
@@ -29,7 +29,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
@@ -298,6 +298,15 @@ config DWMAC_LOONGSON
@@ -298,6 +298,14 @@ config DWMAC_LOONGSON
This selects the LOONGSON PCI bus support for the stmmac driver,
Support for ethernet controller on Loongson-2K1000 SoC and LS7A1000 bridge.
@@ -37,7 +37,6 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+ tristate "Motorcomm PCI DWMAC support"
+ depends on PCI
+ select MOTORCOMM_PHY
+ select STMMAC_LIBPCI
+ help
+ This enables glue driver for Motorcomm DWMAC-based PCI Ethernet
+ controllers. Currently only YT6801 is supported.
@@ -1,13 +1,16 @@
From 3fd163f5bb88de426ca9847549f94b4296170ef0 Mon Sep 17 00:00:00 2001
From e9abf1da0af3f787a03b249945e5ca726c1b8013 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Mon, 30 Mar 2026 23:40:53 +0100
Date: Mon, 30 Mar 2026 23:52:09 +0100
Subject: [PATCH] net: dsa: mxl862xx: cancel pending work on probe error
Call mxl862xx_host_shutdown() in case dsa_register_switch() returns
an error, so any still pending crc_err_work get canceled.
Fixes: a319d0c8c8ced ("net: dsa: mxl862xx: add CRC for MDIO communication)"
Fixes: a319d0c8c8ce ("net: dsa: mxl862xx: add CRC for MDIO communication")
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/3fd163f5bb88de426ca9847549f94b4296170ef0.1774911025.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
@@ -1,7 +1,7 @@
From cd698f1ae94c16499e2714b31dd6048e6f9f068d Mon Sep 17 00:00:00 2001
From b0a79590d10847f190ed377d2664377d7068191d Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 17:54:11 +0000
Subject: [PATCH 01/26] net: dsa: move dsa_bridge_ports() helper to dsa.h
Date: Wed, 1 Apr 2026 14:34:30 +0100
Subject: [PATCH 1/4] net: dsa: move dsa_bridge_ports() helper to dsa.h
The yt921x driver contains a helper to create a bitmap of ports
which are members of a bridge.
@@ -10,8 +10,10 @@ Move the helper as static inline function into dsa.h, so other driver
can make use of it as well.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/4f8bbfce3e4e3a02064fc4dc366263136c6e0383.1775049897.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/net/dsa.h | 13 +++++++++++++
include/net/dsa.h | 13 +++++++++++++
2 files changed, 13 insertions(+), 13 deletions(-)
--- a/include/net/dsa.h
@@ -1,7 +1,7 @@
From c161533e1605a7282563c139323a3913890fdb72 Mon Sep 17 00:00:00 2001
From f259e08494c47c614ce7b6d3079d1e0d3f30ae66 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 17:54:41 +0000
Subject: [PATCH 02/26] net: dsa: add bridge member iteration macro
Date: Wed, 1 Apr 2026 14:34:42 +0100
Subject: [PATCH 2/4] net: dsa: add bridge member iteration macro
Drivers that offload bridges need to iterate over the ports that are
members of a given bridge, for example to rebuild per-port forwarding
@@ -15,6 +15,8 @@ directly, and use it for the existing dsa_bridge_ports() inline
helper.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/e7136aaa26773f39e805a00fe4ecf13cd2b83fc0.1775049897.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/net/dsa.h | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
@@ -1,7 +1,7 @@
From 753efe27a9afee52c4ad42098a9b9278366d63cc Mon Sep 17 00:00:00 2001
From 4250ff1640ea1ede99bfe02ca949acbcc6c0927f Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 17:54:52 +0000
Subject: [PATCH 03/26] dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
Date: Wed, 1 Apr 2026 14:34:52 +0100
Subject: [PATCH 3/4] dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
The MxL862xx offloads bridge forwarding in hardware, so set
dsa_default_offload_fwd_mark() to avoid duplicate forwarding of
@@ -11,6 +11,8 @@ Link-local frames are directly trapped to the CPU port only, so don't
set dsa_default_offload_fwd_mark() on those.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/e1161c90894ddc519c57dc0224b3a0f6bfa1d2d6.1775049897.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
net/dsa/tag_mxl862xx.c | 3 +++
1 file changed, 3 insertions(+)
@@ -1,7 +1,7 @@
From ce0664ff8f75c3ab01101c3f0f8569924d948775 Mon Sep 17 00:00:00 2001
From 340bdf984613c4a9241d678915e513824f5a9b19 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 17:55:08 +0000
Subject: [PATCH 04/26] net: dsa: mxl862xx: implement bridge offloading
Date: Wed, 1 Apr 2026 14:35:01 +0100
Subject: [PATCH 4/4] net: dsa: mxl862xx: implement bridge offloading
Implement joining and leaving bridges as well as add, delete and dump
operations on isolated FDBs, port MDB membership management, and
@@ -24,9 +24,17 @@ dsa_switch_for_each_bridge_member().
As there are now more users of the BRIDGEPORT_CONFIG_SET API and the
state of each port is cached locally, introduce a helper function
mxl862xx_set_bridge_port(struct dsa_switch *ds, int port) which is
then used to replace the direct calls to the API in
mxl862xx_setup_cpu_bridge() and mxl862xx_add_single_port_bridge().
mxl862xx_set_bridge_port(struct dsa_switch *ds, int port) which
applies the cached per-port state to hardware. For standalone user
ports (dp->bridge == NULL), it additionally resets the port to
single-port bridge state: CPU-only portmap, learning and flooding
disabled. The CPU port path sets its state explicitly before calling
this helper and is therefore not affected by the reset.
Note that MASK_VLAN_BASED_MAC_LEARNING is intentionally absent from
the firmware write mask. After mxl862xx_reset(), the firmware
initialises all VLAN-based MAC learning fields to 0 (disabled), so
SVL is the active mode by default without having to set it explicitly.
Note that there is no convenient way to control flooding on per-port
level, so the driver is using a 0-rate QoS meter setup as a stopper in
@@ -36,12 +44,14 @@ registers -- without that at least one 64-byte packet could still
pass before the meter would change from 'yellow' into 'red' state.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/dd079180e2098e5f9626fcd149b9bad9a1b5a1b2.1775049897.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 ++++++-
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 +++++++-
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 20 +-
drivers/net/dsa/mxl862xx/mxl862xx.c | 743 ++++++++++++++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 133 +++++
4 files changed, 1076 insertions(+), 45 deletions(-)
drivers/net/dsa/mxl862xx/mxl862xx.c | 736 ++++++++++++++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 99 ++++
4 files changed, 1022 insertions(+), 58 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -384,7 +394,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol m)
@@ -168,6 +182,213 @@ static int mxl862xx_setup_mdio(struct ds
@@ -168,6 +182,199 @@ static int mxl862xx_setup_mdio(struct ds
return ret;
}
@@ -479,11 +489,40 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
+ u16 bridge_id = dp->bridge ?
+ priv->bridges[dp->bridge->num] : p->fid;
+ struct dsa_port *member_dp;
+ u16 bridge_id;
+ bool enable;
+ int i, idx;
+
+ if (!p->setup_done)
+ return 0;
+
+ if (dsa_port_is_cpu(dp)) {
+ dsa_switch_for_each_user_port(member_dp, ds) {
+ if (member_dp->cpu_dp->index != port)
+ continue;
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ member_dp->index);
+ }
+ } else if (dp->bridge) {
+ dsa_switch_for_each_bridge_member(member_dp, ds,
+ dp->bridge->dev) {
+ if (member_dp->index == port)
+ continue;
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ member_dp->index);
+ }
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ dp->cpu_dp->index);
+ } else {
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ dp->cpu_dp->index);
+ p->flood_block = 0;
+ p->learning = false;
+ }
+
+ bridge_id = dp->bridge ? priv->bridges[dp->bridge->num] : p->fid;
+
+ br_port_cfg.bridge_port_id = cpu_to_le16(port);
+ br_port_cfg.bridge_id = cpu_to_le16(bridge_id);
+ br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
@@ -492,8 +531,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER);
+ br_port_cfg.src_mac_learning_disable = !p->learning;
+
+ mxl862xx_fw_portmap_from_bitmap(br_port_cfg.bridge_port_map, p->portmap);
+
+ for (i = 0; i < ARRAY_SIZE(mxl862xx_flood_meters); i++) {
+ idx = mxl862xx_flood_meters[i];
+ enable = !!(p->flood_block & BIT(idx));
@@ -510,24 +547,11 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+static int mxl862xx_sync_bridge_members(struct dsa_switch *ds,
+ const struct dsa_bridge *bridge)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct dsa_port *dp, *member_dp;
+ int port, err, ret = 0;
+ struct dsa_port *dp;
+ int ret = 0, err;
+
+ dsa_switch_for_each_bridge_member(dp, ds, bridge->dev) {
+ port = dp->index;
+
+ bitmap_zero(priv->ports[port].portmap,
+ MXL862XX_MAX_BRIDGE_PORTS);
+
+ dsa_switch_for_each_bridge_member(member_dp, ds, bridge->dev) {
+ if (member_dp->index != port)
+ __set_bit(member_dp->index,
+ priv->ports[port].portmap);
+ }
+ __set_bit(dp->cpu_dp->index, priv->ports[port].portmap);
+
+ err = mxl862xx_set_bridge_port(ds, port);
+ err = mxl862xx_set_bridge_port(ds, dp->index);
+ if (err)
+ ret = err;
+ }
@@ -535,7 +559,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return ret;
+}
+
+static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv, u16 *bridge_id)
+static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv)
+{
+ struct mxl862xx_bridge_alloc br_alloc = {};
+ int ret;
@@ -544,8 +568,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ if (ret)
+ return ret;
+
+ *bridge_id = le16_to_cpu(br_alloc.bridge_id);
+ return 0;
+ return le16_to_cpu(br_alloc.bridge_id);
+}
+
+static void mxl862xx_free_bridge(struct dsa_switch *ds,
@@ -567,38 +590,11 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+
+ priv->bridges[bridge->num] = 0;
+}
+
+static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct mxl862xx_priv *priv = ds->priv;
+ int ret;
+
+ ret = mxl862xx_allocate_bridge(priv, &priv->ports[port].fid);
+ if (ret) {
+ dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
+ return ret;
+ }
+
+ priv->ports[port].learning = false;
+ bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
+ __set_bit(dp->cpu_dp->index, priv->ports[port].portmap);
+
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
+ return ret;
+
+ /* Standalone ports should not flood unknown unicast or multicast
+ * towards the CPU by default; only broadcast is needed initially.
+ */
+ return mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
+ false, false, true);
+}
+
static int mxl862xx_setup(struct dsa_switch *ds)
{
struct mxl862xx_priv *priv = ds->priv;
@@ -181,6 +402,10 @@ static int mxl862xx_setup(struct dsa_swi
@@ -181,6 +388,10 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
@@ -609,66 +605,49 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
return mxl862xx_setup_mdio(ds);
}
@@ -260,66 +485,87 @@ static int mxl862xx_configure_sp_tag_pro
@@ -260,99 +471,137 @@ static int mxl862xx_configure_sp_tag_pro
static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port)
{
- struct mxl862xx_bridge_port_config br_port_cfg = {};
struct mxl862xx_priv *priv = ds->priv;
- u16 bridge_port_map = 0;
struct dsa_port *dp;
- struct dsa_port *dp;
- /* CPU port bridge setup */
- br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
- MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
- MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
-
+ priv->ports[port].fid = MXL862XX_DEFAULT_BRIDGE;
+ priv->ports[port].learning = true;
- br_port_cfg.bridge_port_id = cpu_to_le16(port);
- br_port_cfg.src_mac_learning_disable = false;
- br_port_cfg.vlan_src_mac_vid_enable = true;
- br_port_cfg.vlan_dst_mac_vid_enable = true;
+ priv->ports[port].fid = MXL862XX_DEFAULT_BRIDGE;
+ priv->ports[port].learning = true;
/* include all assigned user ports in the CPU portmap */
+ bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
dsa_switch_for_each_user_port(dp, ds) {
/* it's safe to rely on cpu_dp being valid for user ports */
if (dp->cpu_dp->index != port)
continue;
- bridge_port_map |= BIT(dp->index);
+ __set_bit(dp->index, priv->ports[port].portmap);
}
- br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map);
- return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
+ return mxl862xx_set_bridge_port(ds, port);
}
+}
-static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
- /* include all assigned user ports in the CPU portmap */
- dsa_switch_for_each_user_port(dp, ds) {
- /* it's safe to rely on cpu_dp being valid for user ports */
- if (dp->cpu_dp->index != port)
- continue;
+static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
+ const struct dsa_bridge bridge,
+ bool *tx_fwd_offload,
+ struct netlink_ext_ack *extack)
{
- struct mxl862xx_bridge_port_config br_port_cfg = {};
- struct dsa_port *dp = dsa_to_port(ds, port);
- struct mxl862xx_bridge_alloc br_alloc = {};
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ u16 fw_id;
int ret;
+ int ret;
- ret = MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc);
- if (ret) {
- dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
- return ret;
- bridge_port_map |= BIT(dp->index);
+ if (!priv->bridges[bridge.num]) {
+ ret = mxl862xx_allocate_bridge(priv, &fw_id);
+ if (ret)
+ ret = mxl862xx_allocate_bridge(priv);
+ if (ret < 0)
+ return ret;
+
+ priv->bridges[bridge.num] = fw_id;
+ priv->bridges[bridge.num] = ret;
+
+ /* Free bridge here on error, DSA rollback won't. */
+ ret = mxl862xx_sync_bridge_members(ds, &bridge);
@@ -679,6 +658,32 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+
+ return 0;
}
- br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map);
- return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
+ return mxl862xx_sync_bridge_members(ds, &bridge);
}
-static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
+static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
+ const struct dsa_bridge bridge)
{
- struct mxl862xx_bridge_port_config br_port_cfg = {};
- struct dsa_port *dp = dsa_to_port(ds, port);
- struct mxl862xx_bridge_alloc br_alloc = {};
- int ret;
+ int err;
- ret = MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc);
- if (ret) {
- dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
- return ret;
- }
+ err = mxl862xx_sync_bridge_members(ds, &bridge);
+ if (err)
+ dev_err(ds->dev,
+ "failed to sync bridge members after port %d left: %pe\n",
+ port, ERR_PTR(err));
- br_port_cfg.bridge_id = br_alloc.bridge_id;
- br_port_cfg.bridge_port_id = cpu_to_le16(port);
@@ -691,30 +696,10 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
- br_port_cfg.vlan_dst_mac_vid_enable = false;
- /* As this function is only called for user ports it is safe to rely on
- * cpu_dp being valid
+ return mxl862xx_sync_bridge_members(ds, &bridge);
+}
+
+static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
+ const struct dsa_bridge bridge)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
+ int err;
+
+ err = mxl862xx_sync_bridge_members(ds, &bridge);
+ if (err)
+ dev_err(ds->dev,
+ "failed to sync bridge members after port %d left: %pe\n",
+ port, ERR_PTR(err));
+
+ /* Revert leaving port, omitted by the sync above, to its
+ * single-port bridge
*/
- br_port_cfg.bridge_port_map[0] = cpu_to_le16(BIT(dp->cpu_dp->index));
+ bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
+ __set_bit(dp->cpu_dp->index, p->portmap);
+ p->flood_block = 0;
+ err = mxl862xx_set_bridge_port(ds, port);
+ if (err)
+ dev_err(ds->dev,
@@ -732,12 +717,54 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
struct dsa_port *dp = dsa_to_port(ds, port);
bool is_cpu_port = dsa_port_is_cpu(dp);
int ret;
@@ -352,7 +598,31 @@ static int mxl862xx_port_setup(struct ds
- /* disable port and flush MAC entries */
ret = mxl862xx_port_state(ds, port, false);
if (ret)
return ret;
mxl862xx_port_fast_age(ds, port);
- /* skip setup for unused and DSA ports */
if (dsa_port_is_unused(dp) ||
dsa_port_is_dsa(dp))
return 0;
- /* configure tag protocol */
ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
if (ret)
return ret;
- /* assign CTP port IDs */
ret = mxl862xx_configure_ctp_port(ds, port, port,
is_cpu_port ? 32 - port : 1);
if (ret)
return ret;
if (is_cpu_port)
- /* assign user ports to CPU port bridge */
return mxl862xx_setup_cpu_bridge(ds, port);
/* setup single-port bridge for user ports */
- /* setup single-port bridge for user ports */
- return mxl862xx_add_single_port_bridge(ds, port);
+ ret = mxl862xx_add_single_port_bridge(ds, port);
+ /* setup single-port bridge for user ports.
+ * If this fails, the FID is leaked -- but the port then transitions
+ * to unused, and the FID pool is sized to tolerate this.
+ */
+ ret = mxl862xx_allocate_bridge(priv);
+ if (ret < 0) {
+ dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
+ return ret;
+ }
+ priv->ports[port].fid = ret;
+ /* Standalone ports should not flood unknown unicast or multicast
+ * towards the CPU by default; only broadcast is needed initially.
+ */
+ ret = mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
+ false, false, true);
+ if (ret)
+ return ret;
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
+ return ret;
+
@@ -765,7 +792,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
}
static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
@@ -365,14 +635,383 @@ static void mxl862xx_phylink_get_caps(st
@@ -365,14 +614,371 @@ static void mxl862xx_phylink_get_caps(st
config->supported_interfaces);
}
@@ -876,7 +903,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ if (fid < 0)
+ return fid;
+
+ /* Look up existing entry by {MAC, FID, TCI} */
+ ether_addr_copy(qparam.mac, mdb->addr);
+ qparam.fid = cpu_to_le16(fid);
+ qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
@@ -892,7 +918,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ aparam.static_entry = true;
+ aparam.port_id = cpu_to_le32(MXL862XX_PORTMAP_FLAG);
+
+ /* Merge with existing portmap if entry already exists */
+ if (qparam.found)
+ memcpy(aparam.port_map, qparam.port_map,
+ sizeof(aparam.port_map));
@@ -915,7 +940,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ if (fid < 0)
+ return fid;
+
+ /* Look up existing entry */
+ qparam.fid = cpu_to_le16(fid);
+ qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+ ether_addr_copy(qparam.mac, mdb->addr);
@@ -930,7 +954,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ mxl862xx_fw_portmap_clear_bit(qparam.port_map, port);
+
+ if (mxl862xx_fw_portmap_is_empty(qparam.port_map)) {
+ /* No ports left -- remove the entry entirely */
+ rparam.fid = cpu_to_le16(fid);
+ rparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+ ether_addr_copy(rparam.mac, mdb->addr);
@@ -1007,8 +1030,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * LEARNING or FORWARDING state (per 802.1D defaults).
+ * Re-apply the driver's intended learning and metering config so that
+ * standalone ports keep learning disabled.
+ * This is likely to get fixed in future firmware releases, however,
+ * the additional API call even then doesn't hurt much.
+ */
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
@@ -1031,8 +1052,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ host_flood_work);
+ struct mxl862xx_priv *priv = p->priv;
+ struct dsa_switch *ds = priv->ds;
+ int port = p - priv->ports;
+ bool uc, mc;
+
+ rtnl_lock();
+
@@ -1042,17 +1061,13 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return;
+ }
+
+ uc = p->host_flood_uc;
+ mc = p->host_flood_mc;
+
+ /* The hardware controls unknown-unicast/multicast forwarding per FID
+ * (bridge), not per source port. For bridged ports all members share
+ * one FID, so we cannot selectively suppress flooding to the CPU for
+ * one source port while allowing it for another. Silently ignore the
+ * request -- the excess flooding towards the CPU is harmless.
+ /* Always write to the standalone FID. When standalone it takes effect
+ * immediately; when bridged the port uses the shared bridge FID so the
+ * write is a no-op for current forwarding, but the state is preserved
+ * in hardware and is ready once the port returns to standalone.
+ */
+ if (!dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
+ mxl862xx_bridge_config_fwd(ds, p->fid, uc, mc, true);
+ mxl862xx_bridge_config_fwd(ds, p->fid, p->host_flood_uc,
+ p->host_flood_mc, true);
+
+ rtnl_unlock();
+}
@@ -1115,7 +1130,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ if (flags.mask & BR_LEARNING)
+ priv->ports[port].learning = !!(flags.val & BR_LEARNING);
+
+ if ((block != old_block) || (flags.mask & BR_LEARNING)) {
+ if (block != old_block || (flags.mask & BR_LEARNING)) {
+ priv->ports[port].flood_block = block;
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
@@ -1149,7 +1164,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
};
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
@@ -407,7 +1046,7 @@ static int mxl862xx_probe(struct mdio_de
@@ -407,7 +1013,7 @@ static int mxl862xx_probe(struct mdio_de
struct device *dev = &mdiodev->dev;
struct mxl862xx_priv *priv;
struct dsa_switch *ds;
@@ -1158,7 +1173,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
@@ -425,14 +1064,25 @@ static int mxl862xx_probe(struct mdio_de
@@ -425,14 +1031,25 @@ static int mxl862xx_probe(struct mdio_de
ds->ops = &mxl862xx_switch_ops;
ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops;
ds->num_ports = MXL862XX_MAX_PORTS;
@@ -1186,7 +1201,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
return err;
}
@@ -440,6 +1090,7 @@ static void mxl862xx_remove(struct mdio_
@@ -440,6 +1057,7 @@ static void mxl862xx_remove(struct mdio_
{
struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
struct mxl862xx_priv *priv;
@@ -1194,7 +1209,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
if (!ds)
return;
@@ -449,12 +1100,21 @@ static void mxl862xx_remove(struct mdio_
@@ -449,12 +1067,21 @@ static void mxl862xx_remove(struct mdio_
dsa_unregister_switch(ds);
mxl862xx_host_shutdown(priv);
@@ -1216,7 +1231,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
if (!ds)
return;
@@ -465,6 +1125,9 @@ static void mxl862xx_shutdown(struct mdi
@@ -465,6 +1092,9 @@ static void mxl862xx_shutdown(struct mdi
mxl862xx_host_shutdown(priv);
@@ -1228,7 +1243,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -4,15 +4,148 @@
@@ -4,15 +4,114 @@
#define __MXL862XX_H
#include <linux/mdio.h>
@@ -1246,37 +1261,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+#define MXL862XX_FW_PORTMAP_WORDS (MXL862XX_MAX_BRIDGE_PORTS / 16)
+
+/**
+ * mxl862xx_fw_portmap_from_bitmap - convert a kernel bitmap to a firmware
+ * portmap (__le16[8])
+ * @dst: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ * @src: kernel bitmap of at least MXL862XX_MAX_BRIDGE_PORTS bits
+ */
+static inline void
+mxl862xx_fw_portmap_from_bitmap(__le16 *dst, const unsigned long *src)
+{
+ int i;
+
+ for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
+ dst[i] = cpu_to_le16(bitmap_read(src, i * 16, 16));
+}
+
+/**
+ * mxl862xx_fw_portmap_to_bitmap - convert a firmware portmap (__le16[8]) to
+ * a kernel bitmap
+ * @dst: kernel bitmap of at least MXL862XX_MAX_BRIDGE_PORTS bits
+ * @src: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ */
+static inline void
+mxl862xx_fw_portmap_to_bitmap(unsigned long *dst, const __le16 *src)
+{
+ int i;
+
+ bitmap_zero(dst, MXL862XX_MAX_BRIDGE_PORTS);
+ for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
+ bitmap_write(dst, le16_to_cpu(src[i]), i * 16, 16);
+}
+
+/**
+ * mxl862xx_fw_portmap_set_bit - set a single port bit in a firmware portmap
+ * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ * @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1)
@@ -1320,8 +1304,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * @fid: firmware FID for the permanent single-port bridge;
+ * kept alive for the lifetime of the port so traffic is
+ * never forwarded while the port is unbridged
+ * @portmap: bitmap of switch port indices that share the current
+ * bridge with this port
+ * @flood_block: bitmask of firmware meter indices that are currently
+ * rate-limiting flood traffic on this port (zero-rate
+ * meters used to block flooding)
@@ -1343,7 +1325,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+struct mxl862xx_port {
+ struct mxl862xx_priv *priv;
+ u16 fid;
+ DECLARE_BITMAP(portmap, MXL862XX_MAX_BRIDGE_PORTS);
+ unsigned long flood_block;
+ bool learning;
+ bool setup_done;
@@ -0,0 +1,50 @@
From 3a4056ec7ec8f71ae9722f86d3cfbc4589deeac4 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 7 Apr 2026 18:30:27 +0100
Subject: [PATCH 1/2] net: dsa: mxl862xx: reject DSA_PORT_TYPE_DSA
DSA links aren't supported by the mxl862xx driver.
Instead of returning early from .port_setup when called for
DSA_PORT_TYPE_DSA ports rather return -EOPNOTSUPP and show an error
message.
The desired side-effect is that the framework will switch the port to
DSA_PORT_TYPE_UNUSED, so we can stop caring about DSA_PORT_TYPE_DSA in
all other places.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/b686f3a22d8a6e7d470e7aa98da811a996a229b9.1775581804.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -544,10 +544,14 @@ static int mxl862xx_port_setup(struct ds
mxl862xx_port_fast_age(ds, port);
- if (dsa_port_is_unused(dp) ||
- dsa_port_is_dsa(dp))
+ if (dsa_port_is_unused(dp))
return 0;
+ if (dsa_port_is_dsa(dp)) {
+ dev_err(ds->dev, "port %d: DSA links not supported\n", port);
+ return -EOPNOTSUPP;
+ }
+
ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
if (ret)
return ret;
@@ -591,7 +595,7 @@ static void mxl862xx_port_teardown(struc
struct mxl862xx_priv *priv = ds->priv;
struct dsa_port *dp = dsa_to_port(ds, port);
- if (dsa_port_is_unused(dp) || dsa_port_is_dsa(dp))
+ if (dsa_port_is_unused(dp))
return;
/* Prevent deferred host_flood_work from acting on stale state.
@@ -0,0 +1,38 @@
From 71934b9e6f36b1786bd969c0e1d2de8f9bd65f0f Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 7 Apr 2026 18:30:35 +0100
Subject: [PATCH 2/2] net: dsa: mxl862xx: don't skip early bridge port
configuration
mxl862xx_bridge_port_set() is currently guarded by the
mxl8622_port->setup_done flag, as the early call to
mxl862xx_bridge_port_set() from mxl862xx_port_stp_state_set() would
otherwise cause a NULL-pointer dereference on unused ports which don't
have dp->cpu_dp despite not being a CPU port.
Using the setup_done flag (which is never set for unused ports),
however, also prevents mxl862xx_bridge_port_set() from configuring
user ports' single-port bridges early, which was unintended.
Fix this by returning early from mxl862xx_bridge_port_set() in case
dsa_port_is_unused().
Fixes: 340bdf984613c ("net: dsa: mxl862xx: implement bridge offloading")
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/15962aac29ebe0a6eb77565451acff880c41ef33.1775581804.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -278,7 +278,7 @@ static int mxl862xx_set_bridge_port(stru
bool enable;
int i, idx;
- if (!p->setup_done)
+ if (dsa_port_is_unused(dp))
return 0;
if (dsa_port_is_cpu(dp)) {
@@ -1,7 +1,7 @@
From 0d88d02cc9dccad01ff88f54e1beee867107b942 Mon Sep 17 00:00:00 2001
From d587f9b6dcc98c1e8aeb5c189a7bfac60d6d29ac Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 10 Mar 2026 02:36:00 +0000
Subject: [PATCH 05/26] net: dsa: mxl862xx: implement VLAN functionality
Date: Tue, 7 Apr 2026 18:31:01 +0100
Subject: [PATCH] net: dsa: mxl862xx: implement VLAN functionality
Add VLAN support using both the Extended VLAN (EVLAN) engine and the
VLAN Filter (VF) engine in a hybrid architecture that allows a higher
@@ -16,8 +16,8 @@ egress rules at all, so they consume only a VF entry.
Both engines draw from shared 1024-entry hardware pools. The VF pool
is divided equally among user ports for VID membership, while the
EVLAN pool is partitioned into small fixed-size ingress blocks (7
entries of catchall rules per port) and variable-size egress blocks
for tag stripping.
entries of catchall rules per port) and fixed-size egress blocks for
tag stripping.
With 5 user ports this yields up to 204 VIDs per port (limited by VF),
of which up to 98 can be untagged (limited by EVLAN egress budget).
@@ -34,12 +34,14 @@ rather than worst-case pre-allocation, or by sharing EVLAN egress and
VLAN Filter blocks across ports with identical VID sets.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/9be29637675342b109a85fa08f5378800d9f7b78.1775581804.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 329 +++++++++
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 329 ++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 12 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 915 +++++++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.h | 104 ++-
4 files changed, 1344 insertions(+), 16 deletions(-)
drivers/net/dsa/mxl862xx/mxl862xx.c | 781 +++++++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.h | 103 +++-
4 files changed, 1211 insertions(+), 14 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -101,14 +103,14 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+/**
+ * enum mxl862xx_extended_vlan_treatment_priority - Treatment priority mode
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_PRIORITY_VAL: Use explicit value
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRORITY: Copy from inner tag
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRORITY: Copy from outer tag
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRIORITY: Copy from inner tag
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRIORITY: Copy from outer tag
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_DSCP: Derive from DSCP
+ */
+enum mxl862xx_extended_vlan_treatment_priority {
+ MXL862XX_EXTENDEDVLAN_TREATMENT_PRIORITY_VAL = 0,
+ MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRORITY = 1,
+ MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRORITY = 2,
+ MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRIORITY = 1,
+ MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRIORITY = 2,
+ MXL862XX_EXTENDEDVLAN_TREATMENT_DSCP = 3,
+};
+
@@ -409,16 +411,14 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#define MXL862XX_STP_PORTCFGSET (MXL862XX_STP_MAGIC + 0x2)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -50,6 +50,88 @@ static const int mxl862xx_flood_meters[]
@@ -50,6 +50,85 @@ static const int mxl862xx_flood_meters[]
MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST,
};
+enum mxl862xx_evlan_action {
+ EVLAN_ACCEPT, /* pass-through, no tag removal */
+ EVLAN_STRIP_IF_UNTAGGED, /* remove 1 tag if entry's untagged flag set */
+ EVLAN_DISCARD, /* discard upstream */
+ EVLAN_PVID_OR_DISCARD, /* insert PVID tag or discard if no PVID */
+ EVLAN_PVID_OR_PASS, /* insert PVID tag or pass-through */
+ EVLAN_STRIP1_AND_PVID_OR_DISCARD,/* strip 1 tag + insert PVID, or discard */
+};
+
@@ -457,7 +457,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * regardless of TPID, so without the ACCEPT guard, it would also
+ * catch standard 802.1Q VID>0 frames and corrupt them. With the
+ * guard, 802.1Q VID>0 frames match the ACCEPT rules first and
+ * pass through untouched; only non-8021Q TPID frames fall through
+ * pass through untouched; only non-8021Q TPID frames pass through
+ * to the NO_FILTER catchalls.
+ */
+static const struct mxl862xx_evlan_rule_desc ingress_aware_final[] = {
@@ -486,27 +486,26 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+};
+
+/*
+ * VID-specific accept rules for VLAN-unaware egress.
+ * The HW sees the MxL tag as outer, real VLAN tag as inner.
+ * match on inner VID with outer=NO_FILTER.
+ * Egress tag-stripping rules for VLAN-unaware mode (2 per untagged VID).
+ * The HW sees the MxL tag as outer; the real VLAN tag, if any, is inner.
+ */
+static const struct mxl862xx_evlan_rule_desc vid_accept_egress_unaware[] = {
+ { FT_NO_FILTER, FT_NORMAL, TP_NONE, TP_8021Q, true, EVLAN_STRIP_IF_UNTAGGED },
+ { FT_NO_FILTER, FT_NO_TAG, TP_NONE, TP_NONE, true, EVLAN_STRIP_IF_UNTAGGED },
+ { FT_NO_FILTER, FT_NORMAL, TP_NONE, TP_8021Q, true, EVLAN_STRIP_IF_UNTAGGED },
+ { FT_NO_FILTER, FT_NO_TAG, TP_NONE, TP_NONE, false, EVLAN_STRIP_IF_UNTAGGED },
+};
+
static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol m)
@@ -275,6 +357,7 @@ static int mxl862xx_set_bridge_port(stru
@@ -275,6 +354,7 @@ static int mxl862xx_set_bridge_port(stru
struct mxl862xx_port *p = &priv->ports[port];
u16 bridge_id = dp->bridge ?
priv->bridges[dp->bridge->num] : p->fid;
struct dsa_port *member_dp;
u16 bridge_id;
+ u16 vf_scan;
bool enable;
int i, idx;
@@ -283,9 +366,69 @@ static int mxl862xx_set_bridge_port(stru
@@ -312,9 +392,69 @@ static int mxl862xx_set_bridge_port(stru
br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
@@ -574,23 +573,13 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ br_port_cfg.vlan_src_mac_vid_enable = p->vlan_filtering;
+ br_port_cfg.vlan_dst_mac_vid_enable = p->vlan_filtering;
+
mxl862xx_fw_portmap_from_bitmap(br_port_cfg.bridge_port_map, p->portmap);
for (i = 0; i < ARRAY_SIZE(mxl862xx_flood_meters); i++) {
@@ -329,6 +472,91 @@ static int mxl862xx_sync_bridge_members(
idx = mxl862xx_flood_meters[i];
enable = !!(p->flood_block & BIT(idx));
@@ -343,6 +483,72 @@ static int mxl862xx_sync_bridge_members(
return ret;
}
+static void mxl862xx_evlan_block_init(struct mxl862xx_evlan_block *blk,
+ u16 size)
+{
+ blk->allocated = false;
+ blk->in_use = false;
+ blk->block_id = 0;
+ blk->block_size = size;
+ blk->n_active = 0;
+}
+
+static int mxl862xx_evlan_block_alloc(struct mxl862xx_priv *priv,
+ struct mxl862xx_evlan_block *blk)
+{
@@ -609,15 +598,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return 0;
+}
+
+static void mxl862xx_vf_init(struct mxl862xx_vf_block *vf, u16 size)
+{
+ vf->allocated = false;
+ vf->block_id = 0;
+ vf->block_size = size;
+ vf->active_count = 0;
+ INIT_LIST_HEAD(&vf->vids);
+}
+
+static int mxl862xx_vf_block_alloc(struct mxl862xx_priv *priv,
+ u16 size, u16 *block_id)
+{
@@ -666,10 +646,10 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return mxl862xx_vf_entry_discard(priv, vf->block_id, 0);
+}
+
static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv, u16 *bridge_id)
static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv)
{
struct mxl862xx_bridge_alloc br_alloc = {};
@@ -392,6 +620,9 @@ static int mxl862xx_add_single_port_brid
@@ -378,6 +584,9 @@ static void mxl862xx_free_bridge(struct
static int mxl862xx_setup(struct dsa_switch *ds)
{
struct mxl862xx_priv *priv = ds->priv;
@@ -679,7 +659,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
int ret;
ret = mxl862xx_reset(priv);
@@ -402,6 +633,50 @@ static int mxl862xx_setup(struct dsa_swi
@@ -388,6 +597,50 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
@@ -695,7 +675,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * through without EVLAN processing.
+ *
+ * Total EVLAN budget:
+ * n_user_ports * (ingress + egress) 1024.
+ * n_user_ports * (ingress + egress) <= 1024.
+ * Ingress blocks are small (7 entries), so almost all capacity
+ * goes to egress VID rules.
+ */
@@ -730,23 +710,10 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
ret = mxl862xx_setup_drop_meter(ds);
if (ret)
return ret;
@@ -483,27 +758,616 @@ static int mxl862xx_configure_sp_tag_pro
@@ -469,12 +722,509 @@ static int mxl862xx_configure_sp_tag_pro
return MXL862XX_API_WRITE(ds->priv, MXL862XX_SS_SPTAG_SET, tag);
}
+/**
+ * mxl862xx_evlan_write_rule - Write a single Extended VLAN rule to hardware
+ * @priv: driver private data
+ * @block_id: HW Extended VLAN block ID
+ * @entry_index: entry index within the block
+ * @desc: rule descriptor (filter type + action)
+ * @vid: VLAN ID for VID-specific rules (ignored when !desc->match_vid)
+ * @untagged: strip tag on egress for EVLAN_STRIP_IF_UNTAGGED action
+ * @pvid: port VLAN ID for PVID insertion rules (0 = no PVID)
+ *
+ * Translates a compact rule descriptor into a full firmware
+ * mxl862xx_extendedvlan_config struct and writes it via the API.
+ */
+static int mxl862xx_evlan_write_rule(struct mxl862xx_priv *priv,
+ u16 block_id, u16 entry_index,
+ const struct mxl862xx_evlan_rule_desc *desc,
@@ -788,11 +755,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
+ break;
+
+ case EVLAN_DISCARD:
+ cfg.treatment.remove_tag =
+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_DISCARD_UPSTREAM);
+ break;
+
+ case EVLAN_PVID_OR_DISCARD:
+ if (pvid) {
+ cfg.treatment.remove_tag =
@@ -809,22 +771,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ }
+ break;
+
+ case EVLAN_PVID_OR_PASS:
+ if (pvid) {
+ cfg.treatment.remove_tag =
+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
+ cfg.treatment.add_outer_vlan = 1;
+ cfg.treatment.outer_vlan.vid_mode =
+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_VID_VAL);
+ cfg.treatment.outer_vlan.vid_val = cpu_to_le32(pvid);
+ cfg.treatment.outer_vlan.tpid =
+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_8021Q);
+ } else {
+ cfg.treatment.remove_tag =
+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
+ }
+ break;
+
+ case EVLAN_STRIP1_AND_PVID_OR_DISCARD:
+ if (pvid) {
+ cfg.treatment.remove_tag =
@@ -845,15 +791,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return MXL862XX_API_WRITE(priv, MXL862XX_EXTENDEDVLAN_SET, cfg);
+}
+
+/**
+ * mxl862xx_evlan_deactivate_entry - Reset an Extended VLAN entry to no-op
+ * @priv: driver private data
+ * @block_id: HW Extended VLAN block ID
+ * @entry_index: entry index within the block
+ *
+ * Writes a zeroed-out config to the firmware, which deactivates the
+ * rule (making it transparent / no-op).
+ */
+static int mxl862xx_evlan_deactivate_entry(struct mxl862xx_priv *priv,
+ u16 block_id, u16 entry_index)
+{
@@ -876,19 +813,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return MXL862XX_API_WRITE(priv, MXL862XX_EXTENDEDVLAN_SET, cfg);
+}
+
+/**
+ * mxl862xx_evlan_write_final_rules - Write catchall rules to the ingress block
+ * @priv: driver private data
+ * @blk: Extended VLAN block (already allocated)
+ * @rules: array of rule descriptors for the final rules
+ * @n_rules: number of final rules
+ * @pvid: port VLAN ID (for PVID insertion rules)
+ *
+ * Writes final catchall rules starting at block_size - n_rules. With
+ * VLAN Filter handling VID membership, only the ingress block uses
+ * finals, and the block is sized to exactly fit them (no VID entries),
+ * so the rules fill the entire block.
+ */
+static int mxl862xx_evlan_write_final_rules(struct mxl862xx_priv *priv,
+ struct mxl862xx_evlan_block *blk,
+ const struct mxl862xx_evlan_rule_desc *rules,
@@ -908,15 +832,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return 0;
+}
+
+/**
+ * mxl862xx_vf_entry_set - Write a single VLAN Filter entry
+ * @priv: driver private data
+ * @block_id: HW VLAN Filter block ID
+ * @index: entry index within the block
+ * @vid: VLAN ID to allow
+ *
+ * Writes an ALLOW entry (discard_matched=false) for the given VID.
+ */
+static int mxl862xx_vf_entry_set(struct mxl862xx_priv *priv,
+ u16 block_id, u16 index, u16 vid)
+{
@@ -931,13 +846,8 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return MXL862XX_API_WRITE(priv, MXL862XX_VLANFILTER_SET, cfg);
+}
+
+/**
+ * mxl862xx_vf_find_vid - Find a VID entry in a VF block
+ * @vf: VLAN Filter block to search
+ * @vid: VLAN ID to find
+ */
+static struct mxl862xx_vf_vid *
+mxl862xx_vf_find_vid(struct mxl862xx_vf_block *vf, u16 vid)
+static struct mxl862xx_vf_vid *mxl862xx_vf_find_vid(struct mxl862xx_vf_block *vf,
+ u16 vid)
+{
+ struct mxl862xx_vf_vid *ve;
+
@@ -948,17 +858,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return NULL;
+}
+
+/**
+ * mxl862xx_vf_add_vid - Add a VID to a port's VLAN Filter block
+ * @priv: driver private data
+ * @vf: VLAN Filter block
+ * @vid: VLAN ID to add
+ * @untagged: whether this VID should strip tags on egress
+ *
+ * Idempotent. Writes an ALLOW entry at active_count and increments
+ * active_count. If the VID already exists, only the untagged flag
+ * is updated. The HW block must be allocated before calling this.
+ */
+static int mxl862xx_vf_add_vid(struct mxl862xx_priv *priv,
+ struct mxl862xx_vf_block *vf,
+ u16 vid, bool untagged)
@@ -995,17 +894,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return 0;
+}
+
+/**
+ * mxl862xx_vf_del_vid - Remove a VID from a port's VLAN Filter block
+ * @priv: driver private data
+ * @vf: VLAN Filter block
+ * @vid: VLAN ID to remove
+ *
+ * Swap-compacts: the last active entry is moved into the gap,
+ * active_count is decremented, and the old last slot is plugged
+ * with DISCARD. When active_count drops to 0, a DISCARD sentinel
+ * is restored at index 0.
+ */
+static int mxl862xx_vf_del_vid(struct mxl862xx_priv *priv,
+ struct mxl862xx_vf_block *vf, u16 vid)
+{
@@ -1035,12 +923,11 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return ret;
+ } else if (gap < last) {
+ /* Swap: move the last ALLOW entry into the gap */
+ last_ve = NULL;
+ list_for_each_entry(last_ve, &vf->vids, list)
+ if (last_ve->index == last)
+ break;
+
+ if (WARN_ON(!last_ve || last_ve->index != last))
+ if (WARN_ON(list_entry_is_head(last_ve, &vf->vids, list)))
+ return -EINVAL;
+
+ ret = mxl862xx_vf_entry_set(priv, vf->block_id,
@@ -1049,16 +936,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return ret;
+
+ last_ve->index = gap;
+
+ /* Plug the old last slot with DISCARD */
+ ret = mxl862xx_vf_entry_discard(priv, vf->block_id, last);
+ if (ret)
+ return ret;
+ } else {
+ /* Deleting the last entry -- just plug it */
+ ret = mxl862xx_vf_entry_discard(priv, vf->block_id, last);
+ if (ret)
+ return ret;
+ }
+
+ list_del(&ve->list);
@@ -1068,20 +945,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return 0;
+}
+
+/**
+ * mxl862xx_evlan_program_ingress - Write the fixed ingress catchall rules
+ * @priv: driver private data
+ * @port: port number
+ *
+ * In VLAN-aware mode the ingress EVLAN block handles PVID insertion for
+ * untagged/priority-tagged frames, passes through standard 802.1Q
+ * tagged frames for VF membership checking, and treats non-8021Q TPID
+ * frames as untagged. The block is sized to exactly fit the 7 catchall
+ * rules and is rewritten whenever PVID changes.
+ *
+ * In VLAN-unaware mode the firmware passes frames through unchanged when
+ * no ingress block is assigned, so nothing is programmed.
+ */
+static int mxl862xx_evlan_program_ingress(struct mxl862xx_priv *priv, int port)
+{
+ struct mxl862xx_port *p = &priv->ports[port];
@@ -1099,19 +962,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ p->pvid);
+}
+
+/**
+ * mxl862xx_evlan_program_egress - Reprogram all egress tag-stripping rules
+ * @priv: driver private data
+ * @port: port number
+ *
+ * Walks the port's VF VID list and writes 2 EVLAN rules per VID that
+ * needs egress tag stripping. In VLAN-aware mode only untagged VIDs
+ * need rules (tagged VIDs pass through EVLAN untouched). In unaware
+ * mode every VID gets rules.
+ *
+ * Entries are packed starting at index 0, and the scan window
+ * (n_active) is narrowed so stale entries beyond it are never matched.
+ */
+static int mxl862xx_evlan_program_egress(struct mxl862xx_priv *priv, int port)
+{
+ struct mxl862xx_port *p = &priv->ports[port];
@@ -1131,10 +981,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ }
+
+ list_for_each_entry(vfv, &p->vf.vids, list) {
+ /* In VLAN-aware mode tagged-only VIDs need no EVLAN
+ * rules -- VLAN Filter handles membership.
+ */
+ if (p->vlan_filtering && !vfv->untagged)
+ if (!vfv->untagged)
+ continue;
+
+ if (idx + n_vid > blk->block_size)
@@ -1182,12 +1029,13 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
+ bool old_vlan_filtering = p->vlan_filtering;
+ bool old_in_use = p->ingress_evlan.in_use;
+ bool changed = (p->vlan_filtering != vlan_filtering);
+ int ret;
+
+ p->vlan_filtering = vlan_filtering;
+
+ /* Reprogram Extended VLAN rules if filtering mode changed */
+ if (changed) {
+ /* When leaving VLAN-aware mode, release the ingress HW
+ * block. The firmware passes frames through unchanged
@@ -1199,17 +1047,20 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+
+ ret = mxl862xx_evlan_program_ingress(priv, port);
+ if (ret)
+ return ret;
+ goto err_restore;
+
+ ret = mxl862xx_evlan_program_egress(priv, port);
+ if (ret)
+ return ret;
+ goto err_restore;
+ }
+
+ /* Push VLAN-based MAC learning flags and (possibly newly
+ * allocated) ingress block to hardware.
+ */
+ return mxl862xx_set_bridge_port(ds, port);
+
+ /* No HW rollback -- restoring SW state is sufficient for a correct retry. */
+err_restore:
+ p->vlan_filtering = old_vlan_filtering;
+ p->ingress_evlan.in_use = old_in_use;
+ return ret;
+}
+
+static int mxl862xx_port_vlan_add(struct dsa_switch *ds, int port,
@@ -1255,21 +1106,32 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ if (pvid_changed) {
+ ret = mxl862xx_evlan_program_ingress(priv, port);
+ if (ret)
+ goto err_pvid;
+ goto err_rollback;
+ }
+
+ /* Reprogram egress tag-stripping rules (walks VF VID list) */
+ ret = mxl862xx_evlan_program_egress(priv, port);
+ if (ret)
+ goto err_pvid;
+ goto err_rollback;
+
+ /* Apply VLAN block IDs and MAC learning flags to bridge port */
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
+ goto err_pvid;
+ goto err_rollback;
+
+ return 0;
+
+err_rollback:
+ /* Best-effort: undo VF add and restore consistent hardware state.
+ * A retry of port_vlan_add will converge since vf_add_vid is
+ * idempotent.
+ */
+ p->pvid = old_pvid;
+ mxl862xx_vf_del_vid(priv, &p->vf, vid);
+ mxl862xx_evlan_program_ingress(priv, port);
+ mxl862xx_evlan_program_egress(priv, port);
+ mxl862xx_set_bridge_port(ds, port);
+ return ret;
+err_pvid:
+ p->pvid = old_pvid;
+ return ret;
@@ -1280,13 +1142,22 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
+ u16 vid = vlan->vid;
+ struct mxl862xx_vf_vid *ve;
+ bool pvid_changed = false;
+ u16 vid = vlan->vid;
+ bool old_untagged;
+ u16 old_pvid;
+ int ret;
+
+ if (dsa_is_cpu_port(ds, port))
+ return 0;
+
+ ve = mxl862xx_vf_find_vid(&p->vf, vid);
+ if (!ve)
+ return 0;
+ old_untagged = ve->untagged;
+ old_pvid = p->pvid;
+
+ /* Clear PVID if we're deleting it */
+ if (p->pvid == vid) {
+ p->pvid = 0;
@@ -1299,28 +1170,45 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ */
+ ret = mxl862xx_vf_del_vid(priv, &p->vf, vid);
+ if (ret)
+ return ret;
+ goto err_pvid;
+
+ /* Reprogram egress tag-stripping rules (VID is now gone) */
+ ret = mxl862xx_evlan_program_egress(priv, port);
+ if (ret)
+ return ret;
+ goto err_rollback;
+
+ /* If PVID changed, reprogram ingress finals */
+ if (pvid_changed) {
+ ret = mxl862xx_evlan_program_ingress(priv, port);
+ if (ret)
+ return ret;
+ goto err_rollback;
+ }
+
+ return mxl862xx_set_bridge_port(ds, port);
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
+ goto err_rollback;
+
+ return 0;
+
+err_rollback:
+ /* Best-effort: re-add the VID and restore consistent hardware
+ * state. A retry of port_vlan_del will converge.
+ */
+ p->pvid = old_pvid;
+ mxl862xx_vf_add_vid(priv, &p->vf, vid, old_untagged);
+ mxl862xx_evlan_program_egress(priv, port);
+ mxl862xx_evlan_program_ingress(priv, port);
+ mxl862xx_set_bridge_port(ds, port);
+ return ret;
+err_pvid:
+ p->pvid = old_pvid;
+ return ret;
+}
+
static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port)
{
struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
struct dsa_port *dp;
- priv->ports[port].fid = MXL862XX_DEFAULT_BRIDGE;
- priv->ports[port].learning = true;
@@ -1332,41 +1220,21 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * assignment need to be configured.
+ */
/* include all assigned user ports in the CPU portmap */
- bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
+ bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
dsa_switch_for_each_user_port(dp, ds) {
/* it's safe to rely on cpu_dp being valid for user ports */
if (dp->cpu_dp->index != port)
continue;
- __set_bit(dp->index, priv->ports[port].portmap);
+ __set_bit(dp->index, p->portmap);
}
return mxl862xx_set_bridge_port(ds, port);
}
@@ -510,6 +1260,8 @@ static int mxl862xx_port_bridge_join(str
static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
const struct dsa_bridge bridge)
{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
int err;
+
static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
const struct dsa_bridge bridge,
bool *tx_fwd_offload,
@@ -553,6 +1417,22 @@ static void mxl862xx_port_bridge_leave(s
bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
__set_bit(dp->cpu_dp->index, p->portmap);
p->flood_block = 0;
+
+ /* Detach EVLAN and VF blocks from the bridge port BEFORE freeing
+ * them. The firmware tracks a usage count per block and rejects
+ * FREE while the count is non-zero.
+ *
+ * For EVLAN: setting in_use=false makes set_bridge_port send
+ * enable=false, which decrements the firmware refcount.
+ *
+ * For VF: set_bridge_port sees dp->bridge == NULL (DSA already
+ * cleared it) and sends vlan_filter_enable=0, which decrements
+ * the firmware VF refcount.
+ */
err = mxl862xx_sync_bridge_members(ds, &bridge);
@@ -521,6 +1273,10 @@ static void mxl862xx_port_bridge_leave(s
/* Revert leaving port, omitted by the sync above, to its
* single-port bridge
*/
+ p->pvid = 0;
+ p->ingress_evlan.in_use = false;
+ p->egress_evlan.in_use = false;
@@ -1374,28 +1242,22 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
err = mxl862xx_set_bridge_port(ds, port);
if (err)
dev_err(ds->dev,
@@ -602,6 +1482,28 @@ static int mxl862xx_port_setup(struct ds
@@ -585,6 +1341,22 @@ static int mxl862xx_port_setup(struct ds
if (ret)
return ret;
+ /* Initialize and pre-allocate per-port EVLAN and VF blocks for
+ * user ports. CPU ports do not use EVLAN or VF -- frames pass
+ * through without processing. Pre-allocation avoids firmware
+ * EVLAN table fragmentation and simplifies control flow.
+ */
+ mxl862xx_evlan_block_init(&priv->ports[port].ingress_evlan,
+ priv->evlan_ingress_size);
+ priv->ports[port].ingress_evlan.block_size = priv->evlan_ingress_size;
+ ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].ingress_evlan);
+ if (ret)
+ return ret;
+
+ mxl862xx_evlan_block_init(&priv->ports[port].egress_evlan,
+ priv->evlan_egress_size);
+ priv->ports[port].egress_evlan.block_size = priv->evlan_egress_size;
+ ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].egress_evlan);
+ if (ret)
+ return ret;
+
+ mxl862xx_vf_init(&priv->ports[port].vf, priv->vf_block_size);
+ priv->ports[port].vf.block_size = priv->vf_block_size;
+ INIT_LIST_HEAD(&priv->ports[port].vf.vids);
+ ret = mxl862xx_vf_alloc(priv, &priv->ports[port].vf);
+ if (ret)
+ return ret;
@@ -1403,7 +1265,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
priv->ports[port].setup_done = true;
return 0;
@@ -1012,6 +1914,9 @@ static const struct dsa_switch_ops mxl86
@@ -983,6 +1755,9 @@ static const struct dsa_switch_ops mxl86
.port_fdb_dump = mxl862xx_port_fdb_dump,
.port_mdb_add = mxl862xx_port_mdb_add,
.port_mdb_del = mxl862xx_port_mdb_del,
@@ -1424,7 +1286,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
/* Number of __le16 words in a firmware portmap (128-bit bitmap). */
#define MXL862XX_FW_PORTMAP_WORDS (MXL862XX_MAX_BRIDGE_PORTS / 16)
@@ -86,6 +88,66 @@ static inline bool mxl862xx_fw_portmap_i
@@ -55,6 +57,66 @@ static inline bool mxl862xx_fw_portmap_i
}
/**
@@ -1491,7 +1353,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* struct mxl862xx_port - per-port state tracked by the driver
* @priv: back-pointer to switch private data; needed by
* deferred work handlers to access ds and priv
@@ -101,6 +163,11 @@ static inline bool mxl862xx_fw_portmap_i
@@ -68,6 +130,11 @@ static inline bool mxl862xx_fw_portmap_i
* @setup_done: set at end of port_setup, cleared at start of
* port_teardown; guards deferred work against
* acting on torn-down state
@@ -1503,11 +1365,10 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* @host_flood_uc: desired host unicast flood state (true = flood);
* updated atomically by port_set_host_flood, consumed
* by the deferred host_flood_work
@@ -119,6 +186,12 @@ struct mxl862xx_port {
@@ -85,6 +152,11 @@ struct mxl862xx_port {
unsigned long flood_block;
bool learning;
bool setup_done;
+ /* VLAN state */
+ u16 pvid;
+ bool vlan_filtering;
+ struct mxl862xx_vf_block vf;
@@ -1516,7 +1377,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
bool host_flood_uc;
bool host_flood_mc;
struct work_struct host_flood_work;
@@ -126,17 +199,23 @@ struct mxl862xx_port {
@@ -92,17 +164,23 @@ struct mxl862xx_port {
/**
* struct mxl862xx_priv - driver private data for an MxL862xx switch
@@ -1551,7 +1412,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
*/
struct mxl862xx_priv {
struct dsa_switch *ds;
@@ -146,6 +225,9 @@ struct mxl862xx_priv {
@@ -112,6 +190,9 @@ struct mxl862xx_priv {
u16 drop_meter;
struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
u16 bridges[MXL862XX_MAX_BRIDGES + 1];
@@ -1,7 +1,7 @@
From 0067d79d10becfc5779fb50d5c0ac152cc5dc303 Mon Sep 17 00:00:00 2001
From e6295d124644b14a12b55edf5d3e89cf86a4a2ce Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Sun, 22 Mar 2026 00:57:33 +0000
Subject: [PATCH 06/26] net: dsa: mxl862xx: add ethtool statistics support
Date: Sun, 12 Apr 2026 01:01:57 +0100
Subject: [PATCH 1/2] net: dsa: mxl862xx: add ethtool statistics support
The MxL862xx firmware exposes per-port RMON counters through the
RMON_PORT_GET command, covering standard IEEE 802.3 MAC statistics
@@ -16,11 +16,13 @@ legacy ethtool -S support. Implement .get_eth_mac_stats,
IEEE 802.3 statistics interface.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/480be14d5ed51f3db7b1681b298044dbf8e87494.1775951347.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 142 ++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 142 +++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 3 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 168 ++++++++++++++++++++++++
3 files changed, 313 insertions(+)
drivers/net/dsa/mxl862xx/mxl862xx.c | 173 ++++++++++++++++++++++++
3 files changed, 318 insertions(+)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -120,7 +122,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * @tx_good_bytes: Transmitted good byte count (64-bit)
+ */
+struct mxl862xx_rmon_port_cnt {
+ enum mxl862xx_port_type port_type;
+ __le32 port_type; /* enum mxl862xx_port_type */
+ __le16 port_id;
+ __le16 sub_if_id_group;
+ u8 pce_bypass;
@@ -192,7 +194,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#define MXL862XX_MAC_TABLEENTRYQUERY (MXL862XX_SWMAC_MAGIC + 0x4)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -30,6 +30,64 @@
@@ -30,6 +30,38 @@
#define MXL862XX_API_READ_QUIET(dev, cmd, data) \
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
@@ -209,60 +211,34 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ .offset = offsetof(struct mxl862xx_rmon_port_cnt, _element) \
+}
+
+/* Hardware-specific counters not covered by any standardized stats callback. */
+static const struct mxl862xx_mib_desc mxl862xx_mib[] = {
+ MIB_DESC(1, "TxGoodPkts", tx_good_pkts),
+ MIB_DESC(1, "TxUnicastPkts", tx_unicast_pkts),
+ MIB_DESC(1, "TxBroadcastPkts", tx_broadcast_pkts),
+ MIB_DESC(1, "TxMulticastPkts", tx_multicast_pkts),
+ MIB_DESC(1, "Tx64BytePkts", tx64byte_pkts),
+ MIB_DESC(1, "Tx127BytePkts", tx127byte_pkts),
+ MIB_DESC(1, "Tx255BytePkts", tx255byte_pkts),
+ MIB_DESC(1, "Tx511BytePkts", tx511byte_pkts),
+ MIB_DESC(1, "Tx1023BytePkts", tx1023byte_pkts),
+ MIB_DESC(1, "TxMaxBytePkts", tx_max_byte_pkts),
+ MIB_DESC(1, "TxDroppedPkts", tx_dropped_pkts),
+ MIB_DESC(1, "TxAcmDroppedPkts", tx_acm_dropped_pkts),
+ MIB_DESC(2, "TxGoodBytes", tx_good_bytes),
+ MIB_DESC(1, "TxSingleCollCount", tx_single_coll_count),
+ MIB_DESC(1, "TxMultCollCount", tx_mult_coll_count),
+ MIB_DESC(1, "TxLateCollCount", tx_late_coll_count),
+ MIB_DESC(1, "TxExcessCollCount", tx_excess_coll_count),
+ MIB_DESC(1, "TxCollCount", tx_coll_count),
+ MIB_DESC(1, "TxPauseCount", tx_pause_count),
+ MIB_DESC(1, "RxGoodPkts", rx_good_pkts),
+ MIB_DESC(1, "RxUnicastPkts", rx_unicast_pkts),
+ MIB_DESC(1, "RxBroadcastPkts", rx_broadcast_pkts),
+ MIB_DESC(1, "RxMulticastPkts", rx_multicast_pkts),
+ MIB_DESC(1, "RxFCSErrorPkts", rx_fcserror_pkts),
+ MIB_DESC(1, "RxUnderSizeGoodPkts", rx_under_size_good_pkts),
+ MIB_DESC(1, "RxOversizeGoodPkts", rx_oversize_good_pkts),
+ MIB_DESC(1, "RxUnderSizeErrorPkts", rx_under_size_error_pkts),
+ MIB_DESC(1, "RxOversizeErrorPkts", rx_oversize_error_pkts),
+ MIB_DESC(1, "RxFilteredPkts", rx_filtered_pkts),
+ MIB_DESC(1, "Rx64BytePkts", rx64byte_pkts),
+ MIB_DESC(1, "Rx127BytePkts", rx127byte_pkts),
+ MIB_DESC(1, "Rx255BytePkts", rx255byte_pkts),
+ MIB_DESC(1, "Rx511BytePkts", rx511byte_pkts),
+ MIB_DESC(1, "Rx1023BytePkts", rx1023byte_pkts),
+ MIB_DESC(1, "RxMaxBytePkts", rx_max_byte_pkts),
+ MIB_DESC(1, "RxDroppedPkts", rx_dropped_pkts),
+ MIB_DESC(1, "RxExtendedVlanDiscardPkts", rx_extended_vlan_discard_pkts),
+ MIB_DESC(1, "MtuExceedDiscardPkts", mtu_exceed_discard_pkts),
+ MIB_DESC(2, "RxGoodBytes", rx_good_bytes),
+ MIB_DESC(2, "RxBadBytes", rx_bad_bytes),
+ MIB_DESC(1, "RxGoodPausePkts", rx_good_pause_pkts),
+ MIB_DESC(1, "RxAlignErrorPkts", rx_align_error_pkts),
+};
+
+static const struct ethtool_rmon_hist_range mxl862xx_rmon_ranges[] = {
+ { 0, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 10240 },
+ {}
+};
+
#define MXL862XX_SDMA_PCTRLP(p) (0xbc0 + ((p) * 0x6))
#define MXL862XX_SDMA_PCTRL_EN BIT(0)
@@ -1893,6 +1951,110 @@ static int mxl862xx_port_bridge_flags(st
@@ -1734,6 +1766,140 @@ static int mxl862xx_port_bridge_flags(st
return 0;
}
+static void mxl862xx_get_strings(struct dsa_switch *ds, int port,
+ u32 stringset, u8 *data)
+ u32 stringset, u8 *data)
+{
+ int i;
+
@@ -285,7 +261,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ struct mxl862xx_rmon_port_cnt *cnt)
+{
+ memset(cnt, 0, sizeof(*cnt));
+ cnt->port_type = MXL862XX_CTP_PORT;
+ cnt->port_type = cpu_to_le32(MXL862XX_CTP_PORT);
+ cnt->port_id = cpu_to_le16(port);
+
+ return MXL862XX_API_READ(ds->priv, MXL862XX_RMON_PORT_GET, *cnt);
@@ -365,10 +341,40 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ pause_stats->rx_pause_frames = le32_to_cpu(cnt.rx_good_pause_pkts);
+}
+
+static void mxl862xx_get_rmon_stats(struct dsa_switch *ds, int port,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct mxl862xx_rmon_port_cnt cnt;
+
+ if (mxl862xx_read_rmon(ds, port, &cnt))
+ return;
+
+ rmon_stats->undersize_pkts = le32_to_cpu(cnt.rx_under_size_good_pkts);
+ rmon_stats->oversize_pkts = le32_to_cpu(cnt.rx_oversize_good_pkts);
+ rmon_stats->fragments = le32_to_cpu(cnt.rx_under_size_error_pkts);
+ rmon_stats->jabbers = le32_to_cpu(cnt.rx_oversize_error_pkts);
+
+ rmon_stats->hist[0] = le32_to_cpu(cnt.rx64byte_pkts);
+ rmon_stats->hist[1] = le32_to_cpu(cnt.rx127byte_pkts);
+ rmon_stats->hist[2] = le32_to_cpu(cnt.rx255byte_pkts);
+ rmon_stats->hist[3] = le32_to_cpu(cnt.rx511byte_pkts);
+ rmon_stats->hist[4] = le32_to_cpu(cnt.rx1023byte_pkts);
+ rmon_stats->hist[5] = le32_to_cpu(cnt.rx_max_byte_pkts);
+
+ rmon_stats->hist_tx[0] = le32_to_cpu(cnt.tx64byte_pkts);
+ rmon_stats->hist_tx[1] = le32_to_cpu(cnt.tx127byte_pkts);
+ rmon_stats->hist_tx[2] = le32_to_cpu(cnt.tx255byte_pkts);
+ rmon_stats->hist_tx[3] = le32_to_cpu(cnt.tx511byte_pkts);
+ rmon_stats->hist_tx[4] = le32_to_cpu(cnt.tx1023byte_pkts);
+ rmon_stats->hist_tx[5] = le32_to_cpu(cnt.tx_max_byte_pkts);
+
+ *ranges = mxl862xx_rmon_ranges;
+}
static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_tag_protocol = mxl862xx_get_tag_protocol,
.setup = mxl862xx_setup,
@@ -1917,6 +2079,12 @@ static const struct dsa_switch_ops mxl86
@@ -1758,6 +1924,13 @@ static const struct dsa_switch_ops mxl86
.port_vlan_filtering = mxl862xx_port_vlan_filtering,
.port_vlan_add = mxl862xx_port_vlan_add,
.port_vlan_del = mxl862xx_port_vlan_del,
@@ -378,6 +384,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ .get_eth_mac_stats = mxl862xx_get_eth_mac_stats,
+ .get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
+ .get_pause_stats = mxl862xx_get_pause_stats,
+ .get_rmon_stats = mxl862xx_get_rmon_stats,
};
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
@@ -1,20 +1,22 @@
From bab5a69e3872a693069e430a1fa0d2825ea83b4f Mon Sep 17 00:00:00 2001
From a21d33a5265f0b31d935a8b9b2b6faefb5185911 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 04:14:38 +0000
Subject: [PATCH 07/26] net: dsa: mxl862xx: implement .get_stats64
Date: Sun, 12 Apr 2026 01:02:05 +0100
Subject: [PATCH 2/2] net: dsa: mxl862xx: implement .get_stats64
Poll free-running firmware RMON counters every 2 seconds and accumulate
deltas into 64-bit per-port statistics. 32-bit packet counters wrap
in ~880s at 2.5 Gbps line rate; the 2s polling interval provides a
comfortable margin. The .get_stats64 callback forces a fresh poll so
that counters are always up to date when queried.
in ~220s at 10 Gbps line rate with minimum-size frames; the 2s polling
interval provides a comfortable margin. The .get_stats64 callback
forces a fresh poll so that counters are always up to date when queried.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/fa38548ba05866879e8912721edc91947ce4ff12.1775951347.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 8 +-
drivers/net/dsa/mxl862xx/mxl862xx.c | 174 +++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx.h | 63 +++++++-
3 files changed, 238 insertions(+), 7 deletions(-)
drivers/net/dsa/mxl862xx/mxl862xx.c | 175 +++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx.h | 94 +++++++++++-
3 files changed, 270 insertions(+), 7 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -69,7 +71,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
struct mxl862xx_mib_desc {
unsigned int size;
unsigned int offset;
@@ -739,6 +745,9 @@ static int mxl862xx_setup(struct dsa_swi
@@ -677,6 +683,9 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
@@ -79,10 +81,11 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
return mxl862xx_setup_mdio(ds);
}
@@ -2055,6 +2064,158 @@ static void mxl862xx_get_pause_stats(str
pause_stats->rx_pause_frames = le32_to_cpu(cnt.rx_good_pause_pkts);
}
@@ -1900,6 +1909,159 @@ static void mxl862xx_get_rmon_stats(stru
*ranges = mxl862xx_rmon_ranges;
}
+
+/* Compute the delta between two 32-bit free-running counter snapshots,
+ * handling a single wrap-around correctly via unsigned subtraction.
+ */
@@ -238,15 +241,15 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_tag_protocol = mxl862xx_get_tag_protocol,
.setup = mxl862xx_setup,
@@ -2085,6 +2246,7 @@ static const struct dsa_switch_ops mxl86
.get_eth_mac_stats = mxl862xx_get_eth_mac_stats,
@@ -1931,6 +2093,7 @@ static const struct dsa_switch_ops mxl86
.get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
.get_pause_stats = mxl862xx_get_pause_stats,
.get_rmon_stats = mxl862xx_get_rmon_stats,
+ .get_stats64 = mxl862xx_get_stats64,
};
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
@@ -2146,16 +2308,22 @@ static int mxl862xx_probe(struct mdio_de
@@ -1992,16 +2155,22 @@ static int mxl862xx_probe(struct mdio_de
priv->ports[i].priv = priv;
INIT_WORK(&priv->ports[i].host_flood_work,
mxl862xx_host_flood_work_fn);
@@ -269,7 +272,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
return err;
}
@@ -2170,6 +2338,9 @@ static void mxl862xx_remove(struct mdio_
@@ -2016,6 +2185,9 @@ static void mxl862xx_remove(struct mdio_
priv = ds->priv;
@@ -279,7 +282,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
dsa_unregister_switch(ds);
mxl862xx_host_shutdown(priv);
@@ -2196,6 +2367,9 @@ static void mxl862xx_shutdown(struct mdi
@@ -2042,6 +2214,9 @@ static void mxl862xx_shutdown(struct mdi
dsa_switch_shutdown(ds);
@@ -291,11 +294,45 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
for (i = 0; i < MXL862XX_MAX_PORTS; i++)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -148,6 +148,47 @@ struct mxl862xx_evlan_block {
@@ -117,6 +117,79 @@ struct mxl862xx_evlan_block {
};
/**
+ * struct mxl862xx_port_stats - 64-bit accumulated hardware port statistics
+ * @rx_packets: total received packets
+ * @tx_packets: total transmitted packets
+ * @rx_bytes: total received bytes
+ * @tx_bytes: total transmitted bytes
+ * @rx_errors: total receive errors
+ * @tx_errors: total transmit errors
+ * @rx_dropped: total received packets dropped
+ * @tx_dropped: total transmitted packets dropped
+ * @multicast: total received multicast packets
+ * @collisions: total transmit collisions
+ * @rx_length_errors: received length errors (undersize + oversize)
+ * @rx_crc_errors: received FCS errors
+ * @rx_frame_errors: received alignment errors
+ * @prev_rx_good_pkts: previous snapshot of rx good packet counter
+ * @prev_tx_good_pkts: previous snapshot of tx good packet counter
+ * @prev_rx_good_bytes: previous snapshot of rx good byte counter
+ * @prev_tx_good_bytes: previous snapshot of tx good byte counter
+ * @prev_rx_fcserror_pkts: previous snapshot of rx FCS error counter
+ * @prev_rx_under_size_error_pkts: previous snapshot of rx undersize
+ * error counter
+ * @prev_rx_oversize_error_pkts: previous snapshot of rx oversize
+ * error counter
+ * @prev_rx_align_error_pkts: previous snapshot of rx alignment
+ * error counter
+ * @prev_tx_dropped_pkts: previous snapshot of tx dropped counter
+ * @prev_rx_dropped_pkts: previous snapshot of rx dropped counter
+ * @prev_rx_evlan_discard_pkts: previous snapshot of extended VLAN
+ * discard counter
+ * @prev_mtu_exceed_discard_pkts: previous snapshot of MTU exceed
+ * discard counter
+ * @prev_tx_acm_dropped_pkts: previous snapshot of tx ACM dropped
+ * counter
+ * @prev_rx_multicast_pkts: previous snapshot of rx multicast counter
+ * @prev_tx_coll_count: previous snapshot of tx collision counter
+ *
+ * The firmware RMON counters are 32-bit free-running (64-bit for byte
+ * counters). This structure holds 64-bit accumulators alongside the
@@ -303,7 +340,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * handling 32-bit wrap correctly via unsigned subtraction.
+ */
+struct mxl862xx_port_stats {
+ /* 64-bit accumulators */
+ u64 rx_packets;
+ u64 tx_packets;
+ u64 rx_bytes;
@@ -317,7 +353,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ u64 rx_length_errors;
+ u64 rx_crc_errors;
+ u64 rx_frame_errors;
+ /* Previous raw RMON values for delta computation */
+ u32 prev_rx_good_pkts;
+ u32 prev_tx_good_pkts;
+ u64 prev_rx_good_bytes;
@@ -339,7 +374,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* struct mxl862xx_port - per-port state tracked by the driver
* @priv: back-pointer to switch private data; needed by
* deferred work handlers to access ds and priv
@@ -178,6 +219,10 @@ struct mxl862xx_evlan_block {
@@ -145,6 +218,10 @@ struct mxl862xx_evlan_block {
* The worker acquires rtnl_lock() to serialize with
* DSA callbacks and checks @setup_done to avoid
* acting on torn-down ports.
@@ -350,13 +385,12 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
*/
struct mxl862xx_port {
struct mxl862xx_priv *priv;
@@ -195,16 +240,25 @@ struct mxl862xx_port {
@@ -160,16 +237,24 @@ struct mxl862xx_port {
bool host_flood_uc;
bool host_flood_mc;
struct work_struct host_flood_work;
+ /* Hardware stats accumulation */
+ struct mxl862xx_port_stats stats;
+ spinlock_t stats_lock;
+ spinlock_t stats_lock; /* protects stats accumulators */
};
+/* Bit indices for struct mxl862xx_priv::flags */
@@ -378,7 +412,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* @drop_meter: index of the single shared zero-rate firmware meter
* used to unconditionally drop traffic (used to block
* flooding)
@@ -216,18 +270,21 @@ struct mxl862xx_port {
@@ -181,18 +266,21 @@ struct mxl862xx_port {
* @evlan_ingress_size: per-port ingress Extended VLAN block size
* @evlan_egress_size: per-port egress Extended VLAN block size
* @vf_block_size: per-port VLAN Filter block size
@@ -0,0 +1,79 @@
From 0e4d7df2f3b2e59c1bccc09ea099b7a6c2dda886 Mon Sep 17 00:00:00 2001
From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>
Date: Wed, 28 Jan 2026 10:51:56 +0000
Subject: [PATCH] net: phylink: fix NULL pointer deref in
phylink_major_config()
When a MAC driver returns a PCS for an interface mode, and then we
attempt to switch to a different mode that doesn't require a PCS,
this causes phylink to oops:
Unable to handle kernel NULL pointer dereference at virtual address 0000000000000010
Mem abort info:
ESR = 0x0000000096000044
EC = 0x25: DABT (current EL), IL = 32 bits
SET = 0, FnV = 0
EA = 0, S1PTW = 0
FSC = 0x04: level 0 translation fault
Data abort info:
ISV = 0, ISS = 0x00000044, ISS2 = 0x00000000
CM = 0, WnR = 1, TnD = 0, TagAccess = 0
GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
user pgtable: 4k pages, 48-bit VAs, pgdp=0000000137f96000
[0000000000000010] pgd=0000000000000000, p4d=0000000000000000
Internal error: Oops: 0000000096000044 [#1] SMP
Modules linked in: --
CPU: 1 UID: 0 PID: 55 Comm: kworker/u33:0 Not tainted 6.19.0-rc5-00581-g73cb8467a63e #1 PREEMPT
Hardware name: Qualcomm Technologies, Inc. Lemans Ride Rev3 (DT)
Workqueue: events_power_efficient phylink_resolve
pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS +BTYPE=--)
pc : phylink_major_config+0x408/0x948
lr : phylink_major_config+0x3fc/0x948
sp : ffff800080353c60
x29: ffff800080353cb0 x28: ffffb305068a8a00 x27: ffffb305068a8000
x26: ffff000080092100 x25: 0000000000000000 x24: 0000000000000000
x23: 0000000000000001 x22: 0000000000000000 x21: ffffb3050555b3d0
x20: ffff800080353d10 x19: ffff0000b6059400 x18: 00000000ffffffff
x17: 74756f2f79687020 x16: ffffb305045e4f18 x15: 6769666e6f632072
x14: 6f6a616d203a3168 x13: 782d657361623030 x12: ffffb305068c6a98
x11: 0000000000000583 x10: 0000000000000018 x9 : ffffb305068c6a98
x8 : 0000000100006583 x7 : 0000000000000000 x6 : ffff00008083cc40
x5 : ffff00008083cc40 x4 : 0000000000000001 x3 : 0000000000000001
x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffff0000b269e5a8
Call trace:
phylink_major_config+0x408/0x948 (P)
phylink_resolve+0x294/0x6e4
process_one_work+0x148/0x28c
worker_thread+0x2d8/0x3d8
kthread+0x134/0x208
ret_from_fork+0x10/0x20
Code: d63f0020 f9400e60 b4000040 f900081f (f9000ad3)
---[ end trace 0000000000000000 ]---
This is caused by "pcs" being NULL when we attempt to execute:
pcs->phylink = pl;
Make this conditional on pcs being non-null.
Fixes: 486dc391ef43 ("net: phylink: allow mac_select_pcs() to remove a PCS")
Reported-by: Mohd Ayaan Anwar <mohd.anwar@oss.qualcomm.com>
Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
Link: https://patch.msgid.link/E1vl39Q-00000006utm-229h@rmk-PC.armlinux.org.uk
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/phy/phylink.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1287,7 +1287,8 @@ static void phylink_major_config(struct
if (pl->pcs)
pl->pcs->phylink = NULL;
- pcs->phylink = pl;
+ if (pcs)
+ pcs->phylink = pl;
pl->pcs = pcs;
}
@@ -0,0 +1,61 @@
From 7bf588dc62a05c1866efe098e1b188fd879aa2cf Mon Sep 17 00:00:00 2001
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: Mon, 19 Jan 2026 14:19:51 +0200
Subject: [PATCH] net: phylink: simplify phylink_resolve() ->
phylink_major_config() path
This is a trivial change with no functional effect which replaces the
pattern:
if (a) {
if (b) {
do_stuff();
}
}
with:
if (a && b) {
do_stuff();
};
The purpose is to reduce the delta of a subsequent functional change.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
Link: https://patch.msgid.link/20260119121954.1624535-2-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/phy/phylink.c | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -1681,18 +1681,16 @@ static void phylink_resolve(struct work_
if (pl->act_link_an_mode != MLO_AN_FIXED)
phylink_apply_manual_flow(pl, &link_state);
- if (mac_config) {
- if (link_state.interface != pl->link_config.interface) {
- /* The interface has changed, force the link down and
- * then reconfigure.
- */
- if (cur_link_state) {
- phylink_link_down(pl);
- cur_link_state = false;
- }
- phylink_major_config(pl, false, &link_state);
- pl->link_config.interface = link_state.interface;
+ if (mac_config && link_state.interface != pl->link_config.interface) {
+ /* The interface has changed, so force the link down and then
+ * reconfigure.
+ */
+ if (cur_link_state) {
+ phylink_link_down(pl);
+ cur_link_state = false;
}
+ phylink_major_config(pl, false, &link_state);
+ pl->link_config.interface = link_state.interface;
}
/* If configuration of the interface failed, force the link down
@@ -29,7 +29,7 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
@@ -358,6 +358,15 @@ config DWMAC_LOONGSON
@@ -358,6 +358,14 @@ config DWMAC_LOONGSON
This selects the LOONGSON PCI bus support for the stmmac driver,
Support for ethernet controller on Loongson-2K1000 SoC and LS7A1000 bridge.
@@ -37,7 +37,6 @@ Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+ tristate "Motorcomm PCI DWMAC support"
+ depends on PCI
+ select MOTORCOMM_PHY
+ select STMMAC_LIBPCI
+ help
+ This enables glue driver for Motorcomm DWMAC-based PCI Ethernet
+ controllers. Currently only YT6801 is supported.
@@ -0,0 +1,47 @@
From 911e2c050963ccf239faec6ae9dee0f5e8f1cc5c Mon Sep 17 00:00:00 2001
From: Jan Hoffmann <jan@3e8.eu>
Date: Sun, 29 Mar 2026 21:11:11 +0200
Subject: [PATCH] net: sfp: add quirk for ZOERAX SFP-2.5G-T
This is a 2.5G copper module which appears to be based on a Motorcomm
YT8821 PHY. There doesn't seem to be a usable way to to access the PHY
(I2C address 0x56 provides only read-only C22 access, and Rollball is
also not working).
The module does not report the correct extended compliance code for
2.5GBase-T, and instead claims to support SONET OC-48 and Fibre Channel:
Identifier : 0x03 (SFP)
Extended identifier : 0x04 (GBIC/SFP defined by 2-wire interface ID)
Connector : 0x07 (LC)
Transceiver codes : 0x00 0x01 0x00 0x00 0x40 0x40 0x04 0x00 0x00
Transceiver type : FC: Multimode, 50um (M5)
Encoding : 0x05 (SONET Scrambled)
BR Nominal : 2500MBd
Despite this, the kernel still enables the correct 2500Base-X interface
mode. However, for the module to actually work, it is also necessary to
disable inband auto-negotiation.
Enable the existing "sfp_quirk_oem_2_5g" for this module, which handles
that and also sets the bit for 2500Base-T link mode.
Signed-off-by: Jan Hoffmann <jan@3e8.eu>
Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
Link: https://patch.msgid.link/20260329191304.720160-1-jan@3e8.eu
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/phy/sfp.c | 2 ++
1 file changed, 2 insertions(+)
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -567,6 +567,8 @@ static const struct sfp_quirk sfp_quirks
SFP_QUIRK_F("Turris", "RTSFP-2.5G", sfp_fixup_rollball),
SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball),
SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball),
+
+ SFP_QUIRK_S("ZOERAX", "SFP-2.5G-T", sfp_quirk_oem_2_5g),
};
static size_t sfp_strlen(const char *str, size_t maxlen)
@@ -0,0 +1,56 @@
From 95aca8602ef70ffd3d971675751c81826e124f90 Mon Sep 17 00:00:00 2001
From: John Pavlick <jspavlick@posteo.net>
Date: Mon, 6 Apr 2026 13:23:33 +0000
Subject: [PATCH] net: sfp: add quirks for Hisense and HSGQ GPON ONT SFP
modules
Several GPON ONT SFP sticks based on Realtek RTL960x report
1000BASE-LX at 1300MBd in their EEPROM but can operate at 2500base-X.
On hosts capable of 2500base-X (e.g. Banana Pi R3 / MT7986), the
kernel negotiates only 1G because it trusts the incorrect EEPROM data.
Add quirks for:
- Hisense-Leox LXT-010S-H
- Hisense ZNID-GPON-2311NA
- HSGQ HSGQ-XPON-Stick
Each quirk advertises 2500base-X and ignores TX_FAULT during the
module's ~40s Linux boot time.
Tested on Banana Pi R3 (MT7986) with OpenWrt 25.12.1, confirmed
2.5Gbps link and full throughput with flow offloading.
Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
Suggested-by: Marcin Nita <marcin.nita@leolabs.pl>
Signed-off-by: John Pavlick <jspavlick@posteo.net>
Link: https://patch.msgid.link/20260406132321.72563-1-jspavlick@posteo.net
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/phy/sfp.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -538,6 +538,22 @@ static const struct sfp_quirk sfp_quirks
SFP_QUIRK("HUAWEI", "MA5671A", sfp_quirk_2500basex,
sfp_fixup_ignore_tx_fault_and_los),
+ // Hisense LXT-010S-H is a GPON ONT SFP (sold as LEOX LXT-010S-H) that
+ // can operate at 2500base-X, but reports 1000BASE-LX / 1300MBd in its
+ // EEPROM
+ SFP_QUIRK("Hisense-Leox", "LXT-010S-H", sfp_quirk_2500basex,
+ sfp_fixup_ignore_tx_fault),
+
+ // Hisense ZNID-GPON-2311NA can operate at 2500base-X, but reports
+ // 1000BASE-LX / 1300MBd in its EEPROM
+ SFP_QUIRK("Hisense", "ZNID-GPON-2311NA", sfp_quirk_2500basex,
+ sfp_fixup_ignore_tx_fault),
+
+ // HSGQ HSGQ-XPON-Stick can operate at 2500base-X, but reports
+ // 1000BASE-LX / 1300MBd in its EEPROM
+ SFP_QUIRK("HSGQ", "HSGQ-XPON-Stick", sfp_quirk_2500basex,
+ sfp_fixup_ignore_tx_fault),
+
// Lantech 8330-262D-E and 8330-265D can operate at 2500base-X, but
// incorrectly report 2500MBd NRZ in their EEPROM.
// Some 8330-265D modules have inverted LOS, while all of them report
@@ -1,13 +1,16 @@
From 3fd163f5bb88de426ca9847549f94b4296170ef0 Mon Sep 17 00:00:00 2001
From e9abf1da0af3f787a03b249945e5ca726c1b8013 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Mon, 30 Mar 2026 23:40:53 +0100
Date: Mon, 30 Mar 2026 23:52:09 +0100
Subject: [PATCH] net: dsa: mxl862xx: cancel pending work on probe error
Call mxl862xx_host_shutdown() in case dsa_register_switch() returns
an error, so any still pending crc_err_work get canceled.
Fixes: a319d0c8c8ced ("net: dsa: mxl862xx: add CRC for MDIO communication)"
Fixes: a319d0c8c8ce ("net: dsa: mxl862xx: add CRC for MDIO communication")
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/3fd163f5bb88de426ca9847549f94b4296170ef0.1774911025.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
@@ -1,7 +1,7 @@
From cd698f1ae94c16499e2714b31dd6048e6f9f068d Mon Sep 17 00:00:00 2001
From b0a79590d10847f190ed377d2664377d7068191d Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 17:54:11 +0000
Subject: [PATCH 01/26] net: dsa: move dsa_bridge_ports() helper to dsa.h
Date: Wed, 1 Apr 2026 14:34:30 +0100
Subject: [PATCH 1/4] net: dsa: move dsa_bridge_ports() helper to dsa.h
The yt921x driver contains a helper to create a bitmap of ports
which are members of a bridge.
@@ -10,9 +10,11 @@ Move the helper as static inline function into dsa.h, so other driver
can make use of it as well.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/4f8bbfce3e4e3a02064fc4dc366263136c6e0383.1775049897.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/net/dsa.h | 13 +++++++++++++
1 file changed, 13 insertions(+)
include/net/dsa.h | 13 +++++++++++++
2 files changed, 13 insertions(+), 13 deletions(-)
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -1,7 +1,7 @@
From c161533e1605a7282563c139323a3913890fdb72 Mon Sep 17 00:00:00 2001
From f259e08494c47c614ce7b6d3079d1e0d3f30ae66 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 17:54:41 +0000
Subject: [PATCH 02/26] net: dsa: add bridge member iteration macro
Date: Wed, 1 Apr 2026 14:34:42 +0100
Subject: [PATCH 2/4] net: dsa: add bridge member iteration macro
Drivers that offload bridges need to iterate over the ports that are
members of a given bridge, for example to rebuild per-port forwarding
@@ -15,6 +15,8 @@ directly, and use it for the existing dsa_bridge_ports() inline
helper.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/e7136aaa26773f39e805a00fe4ecf13cd2b83fc0.1775049897.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/net/dsa.h | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
@@ -1,7 +1,7 @@
From 753efe27a9afee52c4ad42098a9b9278366d63cc Mon Sep 17 00:00:00 2001
From 4250ff1640ea1ede99bfe02ca949acbcc6c0927f Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 17:54:52 +0000
Subject: [PATCH 03/26] dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
Date: Wed, 1 Apr 2026 14:34:52 +0100
Subject: [PATCH 3/4] dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
The MxL862xx offloads bridge forwarding in hardware, so set
dsa_default_offload_fwd_mark() to avoid duplicate forwarding of
@@ -11,6 +11,8 @@ Link-local frames are directly trapped to the CPU port only, so don't
set dsa_default_offload_fwd_mark() on those.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/e1161c90894ddc519c57dc0224b3a0f6bfa1d2d6.1775049897.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
net/dsa/tag_mxl862xx.c | 3 +++
1 file changed, 3 insertions(+)
@@ -1,7 +1,7 @@
From ce0664ff8f75c3ab01101c3f0f8569924d948775 Mon Sep 17 00:00:00 2001
From 340bdf984613c4a9241d678915e513824f5a9b19 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 17:55:08 +0000
Subject: [PATCH 04/26] net: dsa: mxl862xx: implement bridge offloading
Date: Wed, 1 Apr 2026 14:35:01 +0100
Subject: [PATCH 4/4] net: dsa: mxl862xx: implement bridge offloading
Implement joining and leaving bridges as well as add, delete and dump
operations on isolated FDBs, port MDB membership management, and
@@ -24,9 +24,17 @@ dsa_switch_for_each_bridge_member().
As there are now more users of the BRIDGEPORT_CONFIG_SET API and the
state of each port is cached locally, introduce a helper function
mxl862xx_set_bridge_port(struct dsa_switch *ds, int port) which is
then used to replace the direct calls to the API in
mxl862xx_setup_cpu_bridge() and mxl862xx_add_single_port_bridge().
mxl862xx_set_bridge_port(struct dsa_switch *ds, int port) which
applies the cached per-port state to hardware. For standalone user
ports (dp->bridge == NULL), it additionally resets the port to
single-port bridge state: CPU-only portmap, learning and flooding
disabled. The CPU port path sets its state explicitly before calling
this helper and is therefore not affected by the reset.
Note that MASK_VLAN_BASED_MAC_LEARNING is intentionally absent from
the firmware write mask. After mxl862xx_reset(), the firmware
initialises all VLAN-based MAC learning fields to 0 (disabled), so
SVL is the active mode by default without having to set it explicitly.
Note that there is no convenient way to control flooding on per-port
level, so the driver is using a 0-rate QoS meter setup as a stopper in
@@ -36,12 +44,14 @@ registers -- without that at least one 64-byte packet could still
pass before the meter would change from 'yellow' into 'red' state.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/dd079180e2098e5f9626fcd149b9bad9a1b5a1b2.1775049897.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 ++++++-
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 +++++++-
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 20 +-
drivers/net/dsa/mxl862xx/mxl862xx.c | 743 ++++++++++++++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 133 +++++
4 files changed, 1076 insertions(+), 45 deletions(-)
drivers/net/dsa/mxl862xx/mxl862xx.c | 736 ++++++++++++++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 99 ++++
4 files changed, 1022 insertions(+), 58 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -384,7 +394,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol m)
@@ -168,6 +182,213 @@ static int mxl862xx_setup_mdio(struct ds
@@ -168,6 +182,199 @@ static int mxl862xx_setup_mdio(struct ds
return ret;
}
@@ -479,11 +489,40 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
+ u16 bridge_id = dp->bridge ?
+ priv->bridges[dp->bridge->num] : p->fid;
+ struct dsa_port *member_dp;
+ u16 bridge_id;
+ bool enable;
+ int i, idx;
+
+ if (!p->setup_done)
+ return 0;
+
+ if (dsa_port_is_cpu(dp)) {
+ dsa_switch_for_each_user_port(member_dp, ds) {
+ if (member_dp->cpu_dp->index != port)
+ continue;
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ member_dp->index);
+ }
+ } else if (dp->bridge) {
+ dsa_switch_for_each_bridge_member(member_dp, ds,
+ dp->bridge->dev) {
+ if (member_dp->index == port)
+ continue;
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ member_dp->index);
+ }
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ dp->cpu_dp->index);
+ } else {
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ dp->cpu_dp->index);
+ p->flood_block = 0;
+ p->learning = false;
+ }
+
+ bridge_id = dp->bridge ? priv->bridges[dp->bridge->num] : p->fid;
+
+ br_port_cfg.bridge_port_id = cpu_to_le16(port);
+ br_port_cfg.bridge_id = cpu_to_le16(bridge_id);
+ br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
@@ -492,8 +531,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER);
+ br_port_cfg.src_mac_learning_disable = !p->learning;
+
+ mxl862xx_fw_portmap_from_bitmap(br_port_cfg.bridge_port_map, p->portmap);
+
+ for (i = 0; i < ARRAY_SIZE(mxl862xx_flood_meters); i++) {
+ idx = mxl862xx_flood_meters[i];
+ enable = !!(p->flood_block & BIT(idx));
@@ -510,24 +547,11 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+static int mxl862xx_sync_bridge_members(struct dsa_switch *ds,
+ const struct dsa_bridge *bridge)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct dsa_port *dp, *member_dp;
+ int port, err, ret = 0;
+ struct dsa_port *dp;
+ int ret = 0, err;
+
+ dsa_switch_for_each_bridge_member(dp, ds, bridge->dev) {
+ port = dp->index;
+
+ bitmap_zero(priv->ports[port].portmap,
+ MXL862XX_MAX_BRIDGE_PORTS);
+
+ dsa_switch_for_each_bridge_member(member_dp, ds, bridge->dev) {
+ if (member_dp->index != port)
+ __set_bit(member_dp->index,
+ priv->ports[port].portmap);
+ }
+ __set_bit(dp->cpu_dp->index, priv->ports[port].portmap);
+
+ err = mxl862xx_set_bridge_port(ds, port);
+ err = mxl862xx_set_bridge_port(ds, dp->index);
+ if (err)
+ ret = err;
+ }
@@ -535,7 +559,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return ret;
+}
+
+static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv, u16 *bridge_id)
+static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv)
+{
+ struct mxl862xx_bridge_alloc br_alloc = {};
+ int ret;
@@ -544,8 +568,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ if (ret)
+ return ret;
+
+ *bridge_id = le16_to_cpu(br_alloc.bridge_id);
+ return 0;
+ return le16_to_cpu(br_alloc.bridge_id);
+}
+
+static void mxl862xx_free_bridge(struct dsa_switch *ds,
@@ -567,38 +590,11 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+
+ priv->bridges[bridge->num] = 0;
+}
+
+static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct mxl862xx_priv *priv = ds->priv;
+ int ret;
+
+ ret = mxl862xx_allocate_bridge(priv, &priv->ports[port].fid);
+ if (ret) {
+ dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
+ return ret;
+ }
+
+ priv->ports[port].learning = false;
+ bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
+ __set_bit(dp->cpu_dp->index, priv->ports[port].portmap);
+
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
+ return ret;
+
+ /* Standalone ports should not flood unknown unicast or multicast
+ * towards the CPU by default; only broadcast is needed initially.
+ */
+ return mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
+ false, false, true);
+}
+
static int mxl862xx_setup(struct dsa_switch *ds)
{
struct mxl862xx_priv *priv = ds->priv;
@@ -181,6 +402,10 @@ static int mxl862xx_setup(struct dsa_swi
@@ -181,6 +388,10 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
@@ -609,66 +605,49 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
return mxl862xx_setup_mdio(ds);
}
@@ -260,66 +485,87 @@ static int mxl862xx_configure_sp_tag_pro
@@ -260,99 +471,137 @@ static int mxl862xx_configure_sp_tag_pro
static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port)
{
- struct mxl862xx_bridge_port_config br_port_cfg = {};
struct mxl862xx_priv *priv = ds->priv;
- u16 bridge_port_map = 0;
struct dsa_port *dp;
- struct dsa_port *dp;
- /* CPU port bridge setup */
- br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
- MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
- MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
-
+ priv->ports[port].fid = MXL862XX_DEFAULT_BRIDGE;
+ priv->ports[port].learning = true;
- br_port_cfg.bridge_port_id = cpu_to_le16(port);
- br_port_cfg.src_mac_learning_disable = false;
- br_port_cfg.vlan_src_mac_vid_enable = true;
- br_port_cfg.vlan_dst_mac_vid_enable = true;
+ priv->ports[port].fid = MXL862XX_DEFAULT_BRIDGE;
+ priv->ports[port].learning = true;
/* include all assigned user ports in the CPU portmap */
+ bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
dsa_switch_for_each_user_port(dp, ds) {
/* it's safe to rely on cpu_dp being valid for user ports */
if (dp->cpu_dp->index != port)
continue;
- bridge_port_map |= BIT(dp->index);
+ __set_bit(dp->index, priv->ports[port].portmap);
}
- br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map);
- return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
+ return mxl862xx_set_bridge_port(ds, port);
}
+}
-static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
- /* include all assigned user ports in the CPU portmap */
- dsa_switch_for_each_user_port(dp, ds) {
- /* it's safe to rely on cpu_dp being valid for user ports */
- if (dp->cpu_dp->index != port)
- continue;
+static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
+ const struct dsa_bridge bridge,
+ bool *tx_fwd_offload,
+ struct netlink_ext_ack *extack)
{
- struct mxl862xx_bridge_port_config br_port_cfg = {};
- struct dsa_port *dp = dsa_to_port(ds, port);
- struct mxl862xx_bridge_alloc br_alloc = {};
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ u16 fw_id;
int ret;
+ int ret;
- ret = MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc);
- if (ret) {
- dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
- return ret;
- bridge_port_map |= BIT(dp->index);
+ if (!priv->bridges[bridge.num]) {
+ ret = mxl862xx_allocate_bridge(priv, &fw_id);
+ if (ret)
+ ret = mxl862xx_allocate_bridge(priv);
+ if (ret < 0)
+ return ret;
+
+ priv->bridges[bridge.num] = fw_id;
+ priv->bridges[bridge.num] = ret;
+
+ /* Free bridge here on error, DSA rollback won't. */
+ ret = mxl862xx_sync_bridge_members(ds, &bridge);
@@ -679,6 +658,32 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+
+ return 0;
}
- br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map);
- return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
+ return mxl862xx_sync_bridge_members(ds, &bridge);
}
-static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
+static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
+ const struct dsa_bridge bridge)
{
- struct mxl862xx_bridge_port_config br_port_cfg = {};
- struct dsa_port *dp = dsa_to_port(ds, port);
- struct mxl862xx_bridge_alloc br_alloc = {};
- int ret;
+ int err;
- ret = MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc);
- if (ret) {
- dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
- return ret;
- }
+ err = mxl862xx_sync_bridge_members(ds, &bridge);
+ if (err)
+ dev_err(ds->dev,
+ "failed to sync bridge members after port %d left: %pe\n",
+ port, ERR_PTR(err));
- br_port_cfg.bridge_id = br_alloc.bridge_id;
- br_port_cfg.bridge_port_id = cpu_to_le16(port);
@@ -691,30 +696,10 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
- br_port_cfg.vlan_dst_mac_vid_enable = false;
- /* As this function is only called for user ports it is safe to rely on
- * cpu_dp being valid
+ return mxl862xx_sync_bridge_members(ds, &bridge);
+}
+
+static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
+ const struct dsa_bridge bridge)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
+ int err;
+
+ err = mxl862xx_sync_bridge_members(ds, &bridge);
+ if (err)
+ dev_err(ds->dev,
+ "failed to sync bridge members after port %d left: %pe\n",
+ port, ERR_PTR(err));
+
+ /* Revert leaving port, omitted by the sync above, to its
+ * single-port bridge
*/
- br_port_cfg.bridge_port_map[0] = cpu_to_le16(BIT(dp->cpu_dp->index));
+ bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
+ __set_bit(dp->cpu_dp->index, p->portmap);
+ p->flood_block = 0;
+ err = mxl862xx_set_bridge_port(ds, port);
+ if (err)
+ dev_err(ds->dev,
@@ -732,12 +717,54 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
struct dsa_port *dp = dsa_to_port(ds, port);
bool is_cpu_port = dsa_port_is_cpu(dp);
int ret;
@@ -352,7 +598,31 @@ static int mxl862xx_port_setup(struct ds
- /* disable port and flush MAC entries */
ret = mxl862xx_port_state(ds, port, false);
if (ret)
return ret;
mxl862xx_port_fast_age(ds, port);
- /* skip setup for unused and DSA ports */
if (dsa_port_is_unused(dp) ||
dsa_port_is_dsa(dp))
return 0;
- /* configure tag protocol */
ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
if (ret)
return ret;
- /* assign CTP port IDs */
ret = mxl862xx_configure_ctp_port(ds, port, port,
is_cpu_port ? 32 - port : 1);
if (ret)
return ret;
if (is_cpu_port)
- /* assign user ports to CPU port bridge */
return mxl862xx_setup_cpu_bridge(ds, port);
/* setup single-port bridge for user ports */
- /* setup single-port bridge for user ports */
- return mxl862xx_add_single_port_bridge(ds, port);
+ ret = mxl862xx_add_single_port_bridge(ds, port);
+ /* setup single-port bridge for user ports.
+ * If this fails, the FID is leaked -- but the port then transitions
+ * to unused, and the FID pool is sized to tolerate this.
+ */
+ ret = mxl862xx_allocate_bridge(priv);
+ if (ret < 0) {
+ dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
+ return ret;
+ }
+ priv->ports[port].fid = ret;
+ /* Standalone ports should not flood unknown unicast or multicast
+ * towards the CPU by default; only broadcast is needed initially.
+ */
+ ret = mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
+ false, false, true);
+ if (ret)
+ return ret;
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
+ return ret;
+
@@ -765,7 +792,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
}
static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
@@ -365,14 +635,383 @@ static void mxl862xx_phylink_get_caps(st
@@ -365,14 +614,371 @@ static void mxl862xx_phylink_get_caps(st
config->supported_interfaces);
}
@@ -876,7 +903,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ if (fid < 0)
+ return fid;
+
+ /* Look up existing entry by {MAC, FID, TCI} */
+ ether_addr_copy(qparam.mac, mdb->addr);
+ qparam.fid = cpu_to_le16(fid);
+ qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
@@ -892,7 +918,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ aparam.static_entry = true;
+ aparam.port_id = cpu_to_le32(MXL862XX_PORTMAP_FLAG);
+
+ /* Merge with existing portmap if entry already exists */
+ if (qparam.found)
+ memcpy(aparam.port_map, qparam.port_map,
+ sizeof(aparam.port_map));
@@ -915,7 +940,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ if (fid < 0)
+ return fid;
+
+ /* Look up existing entry */
+ qparam.fid = cpu_to_le16(fid);
+ qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+ ether_addr_copy(qparam.mac, mdb->addr);
@@ -930,7 +954,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ mxl862xx_fw_portmap_clear_bit(qparam.port_map, port);
+
+ if (mxl862xx_fw_portmap_is_empty(qparam.port_map)) {
+ /* No ports left -- remove the entry entirely */
+ rparam.fid = cpu_to_le16(fid);
+ rparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+ ether_addr_copy(rparam.mac, mdb->addr);
@@ -1007,8 +1030,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * LEARNING or FORWARDING state (per 802.1D defaults).
+ * Re-apply the driver's intended learning and metering config so that
+ * standalone ports keep learning disabled.
+ * This is likely to get fixed in future firmware releases, however,
+ * the additional API call even then doesn't hurt much.
+ */
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
@@ -1031,8 +1052,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ host_flood_work);
+ struct mxl862xx_priv *priv = p->priv;
+ struct dsa_switch *ds = priv->ds;
+ int port = p - priv->ports;
+ bool uc, mc;
+
+ rtnl_lock();
+
@@ -1042,17 +1061,13 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return;
+ }
+
+ uc = p->host_flood_uc;
+ mc = p->host_flood_mc;
+
+ /* The hardware controls unknown-unicast/multicast forwarding per FID
+ * (bridge), not per source port. For bridged ports all members share
+ * one FID, so we cannot selectively suppress flooding to the CPU for
+ * one source port while allowing it for another. Silently ignore the
+ * request -- the excess flooding towards the CPU is harmless.
+ /* Always write to the standalone FID. When standalone it takes effect
+ * immediately; when bridged the port uses the shared bridge FID so the
+ * write is a no-op for current forwarding, but the state is preserved
+ * in hardware and is ready once the port returns to standalone.
+ */
+ if (!dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
+ mxl862xx_bridge_config_fwd(ds, p->fid, uc, mc, true);
+ mxl862xx_bridge_config_fwd(ds, p->fid, p->host_flood_uc,
+ p->host_flood_mc, true);
+
+ rtnl_unlock();
+}
@@ -1115,7 +1130,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ if (flags.mask & BR_LEARNING)
+ priv->ports[port].learning = !!(flags.val & BR_LEARNING);
+
+ if ((block != old_block) || (flags.mask & BR_LEARNING)) {
+ if (block != old_block || (flags.mask & BR_LEARNING)) {
+ priv->ports[port].flood_block = block;
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
@@ -1149,7 +1164,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
};
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
@@ -407,7 +1046,7 @@ static int mxl862xx_probe(struct mdio_de
@@ -407,7 +1013,7 @@ static int mxl862xx_probe(struct mdio_de
struct device *dev = &mdiodev->dev;
struct mxl862xx_priv *priv;
struct dsa_switch *ds;
@@ -1158,7 +1173,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
@@ -425,14 +1064,25 @@ static int mxl862xx_probe(struct mdio_de
@@ -425,14 +1031,25 @@ static int mxl862xx_probe(struct mdio_de
ds->ops = &mxl862xx_switch_ops;
ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops;
ds->num_ports = MXL862XX_MAX_PORTS;
@@ -1186,7 +1201,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
return err;
}
@@ -440,6 +1090,7 @@ static void mxl862xx_remove(struct mdio_
@@ -440,6 +1057,7 @@ static void mxl862xx_remove(struct mdio_
{
struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
struct mxl862xx_priv *priv;
@@ -1194,7 +1209,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
if (!ds)
return;
@@ -449,12 +1100,21 @@ static void mxl862xx_remove(struct mdio_
@@ -449,12 +1067,21 @@ static void mxl862xx_remove(struct mdio_
dsa_unregister_switch(ds);
mxl862xx_host_shutdown(priv);
@@ -1216,7 +1231,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
if (!ds)
return;
@@ -465,6 +1125,9 @@ static void mxl862xx_shutdown(struct mdi
@@ -465,6 +1092,9 @@ static void mxl862xx_shutdown(struct mdi
mxl862xx_host_shutdown(priv);
@@ -1228,7 +1243,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -4,15 +4,148 @@
@@ -4,15 +4,114 @@
#define __MXL862XX_H
#include <linux/mdio.h>
@@ -1246,37 +1261,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+#define MXL862XX_FW_PORTMAP_WORDS (MXL862XX_MAX_BRIDGE_PORTS / 16)
+
+/**
+ * mxl862xx_fw_portmap_from_bitmap - convert a kernel bitmap to a firmware
+ * portmap (__le16[8])
+ * @dst: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ * @src: kernel bitmap of at least MXL862XX_MAX_BRIDGE_PORTS bits
+ */
+static inline void
+mxl862xx_fw_portmap_from_bitmap(__le16 *dst, const unsigned long *src)
+{
+ int i;
+
+ for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
+ dst[i] = cpu_to_le16(bitmap_read(src, i * 16, 16));
+}
+
+/**
+ * mxl862xx_fw_portmap_to_bitmap - convert a firmware portmap (__le16[8]) to
+ * a kernel bitmap
+ * @dst: kernel bitmap of at least MXL862XX_MAX_BRIDGE_PORTS bits
+ * @src: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ */
+static inline void
+mxl862xx_fw_portmap_to_bitmap(unsigned long *dst, const __le16 *src)
+{
+ int i;
+
+ bitmap_zero(dst, MXL862XX_MAX_BRIDGE_PORTS);
+ for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
+ bitmap_write(dst, le16_to_cpu(src[i]), i * 16, 16);
+}
+
+/**
+ * mxl862xx_fw_portmap_set_bit - set a single port bit in a firmware portmap
+ * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ * @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1)
@@ -1320,8 +1304,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * @fid: firmware FID for the permanent single-port bridge;
+ * kept alive for the lifetime of the port so traffic is
+ * never forwarded while the port is unbridged
+ * @portmap: bitmap of switch port indices that share the current
+ * bridge with this port
+ * @flood_block: bitmask of firmware meter indices that are currently
+ * rate-limiting flood traffic on this port (zero-rate
+ * meters used to block flooding)
@@ -1343,7 +1325,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+struct mxl862xx_port {
+ struct mxl862xx_priv *priv;
+ u16 fid;
+ DECLARE_BITMAP(portmap, MXL862XX_MAX_BRIDGE_PORTS);
+ unsigned long flood_block;
+ bool learning;
+ bool setup_done;
@@ -0,0 +1,50 @@
From 3a4056ec7ec8f71ae9722f86d3cfbc4589deeac4 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 7 Apr 2026 18:30:27 +0100
Subject: [PATCH 1/2] net: dsa: mxl862xx: reject DSA_PORT_TYPE_DSA
DSA links aren't supported by the mxl862xx driver.
Instead of returning early from .port_setup when called for
DSA_PORT_TYPE_DSA ports rather return -EOPNOTSUPP and show an error
message.
The desired side-effect is that the framework will switch the port to
DSA_PORT_TYPE_UNUSED, so we can stop caring about DSA_PORT_TYPE_DSA in
all other places.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/b686f3a22d8a6e7d470e7aa98da811a996a229b9.1775581804.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -544,10 +544,14 @@ static int mxl862xx_port_setup(struct ds
mxl862xx_port_fast_age(ds, port);
- if (dsa_port_is_unused(dp) ||
- dsa_port_is_dsa(dp))
+ if (dsa_port_is_unused(dp))
return 0;
+ if (dsa_port_is_dsa(dp)) {
+ dev_err(ds->dev, "port %d: DSA links not supported\n", port);
+ return -EOPNOTSUPP;
+ }
+
ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
if (ret)
return ret;
@@ -591,7 +595,7 @@ static void mxl862xx_port_teardown(struc
struct mxl862xx_priv *priv = ds->priv;
struct dsa_port *dp = dsa_to_port(ds, port);
- if (dsa_port_is_unused(dp) || dsa_port_is_dsa(dp))
+ if (dsa_port_is_unused(dp))
return;
/* Prevent deferred host_flood_work from acting on stale state.
@@ -0,0 +1,38 @@
From 71934b9e6f36b1786bd969c0e1d2de8f9bd65f0f Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 7 Apr 2026 18:30:35 +0100
Subject: [PATCH 2/2] net: dsa: mxl862xx: don't skip early bridge port
configuration
mxl862xx_bridge_port_set() is currently guarded by the
mxl8622_port->setup_done flag, as the early call to
mxl862xx_bridge_port_set() from mxl862xx_port_stp_state_set() would
otherwise cause a NULL-pointer dereference on unused ports which don't
have dp->cpu_dp despite not being a CPU port.
Using the setup_done flag (which is never set for unused ports),
however, also prevents mxl862xx_bridge_port_set() from configuring
user ports' single-port bridges early, which was unintended.
Fix this by returning early from mxl862xx_bridge_port_set() in case
dsa_port_is_unused().
Fixes: 340bdf984613c ("net: dsa: mxl862xx: implement bridge offloading")
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/15962aac29ebe0a6eb77565451acff880c41ef33.1775581804.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -278,7 +278,7 @@ static int mxl862xx_set_bridge_port(stru
bool enable;
int i, idx;
- if (!p->setup_done)
+ if (dsa_port_is_unused(dp))
return 0;
if (dsa_port_is_cpu(dp)) {
@@ -1,7 +1,7 @@
From 0d88d02cc9dccad01ff88f54e1beee867107b942 Mon Sep 17 00:00:00 2001
From d587f9b6dcc98c1e8aeb5c189a7bfac60d6d29ac Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 10 Mar 2026 02:36:00 +0000
Subject: [PATCH 05/26] net: dsa: mxl862xx: implement VLAN functionality
Date: Tue, 7 Apr 2026 18:31:01 +0100
Subject: [PATCH] net: dsa: mxl862xx: implement VLAN functionality
Add VLAN support using both the Extended VLAN (EVLAN) engine and the
VLAN Filter (VF) engine in a hybrid architecture that allows a higher
@@ -16,8 +16,8 @@ egress rules at all, so they consume only a VF entry.
Both engines draw from shared 1024-entry hardware pools. The VF pool
is divided equally among user ports for VID membership, while the
EVLAN pool is partitioned into small fixed-size ingress blocks (7
entries of catchall rules per port) and variable-size egress blocks
for tag stripping.
entries of catchall rules per port) and fixed-size egress blocks for
tag stripping.
With 5 user ports this yields up to 204 VIDs per port (limited by VF),
of which up to 98 can be untagged (limited by EVLAN egress budget).
@@ -34,12 +34,14 @@ rather than worst-case pre-allocation, or by sharing EVLAN egress and
VLAN Filter blocks across ports with identical VID sets.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/9be29637675342b109a85fa08f5378800d9f7b78.1775581804.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 329 +++++++++
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 329 ++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 12 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 915 +++++++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.h | 104 ++-
4 files changed, 1344 insertions(+), 16 deletions(-)
drivers/net/dsa/mxl862xx/mxl862xx.c | 781 +++++++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.h | 103 +++-
4 files changed, 1211 insertions(+), 14 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -101,14 +103,14 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+/**
+ * enum mxl862xx_extended_vlan_treatment_priority - Treatment priority mode
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_PRIORITY_VAL: Use explicit value
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRORITY: Copy from inner tag
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRORITY: Copy from outer tag
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRIORITY: Copy from inner tag
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRIORITY: Copy from outer tag
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_DSCP: Derive from DSCP
+ */
+enum mxl862xx_extended_vlan_treatment_priority {
+ MXL862XX_EXTENDEDVLAN_TREATMENT_PRIORITY_VAL = 0,
+ MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRORITY = 1,
+ MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRORITY = 2,
+ MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRIORITY = 1,
+ MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRIORITY = 2,
+ MXL862XX_EXTENDEDVLAN_TREATMENT_DSCP = 3,
+};
+
@@ -409,16 +411,14 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#define MXL862XX_STP_PORTCFGSET (MXL862XX_STP_MAGIC + 0x2)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -50,6 +50,88 @@ static const int mxl862xx_flood_meters[]
@@ -50,6 +50,85 @@ static const int mxl862xx_flood_meters[]
MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST,
};
+enum mxl862xx_evlan_action {
+ EVLAN_ACCEPT, /* pass-through, no tag removal */
+ EVLAN_STRIP_IF_UNTAGGED, /* remove 1 tag if entry's untagged flag set */
+ EVLAN_DISCARD, /* discard upstream */
+ EVLAN_PVID_OR_DISCARD, /* insert PVID tag or discard if no PVID */
+ EVLAN_PVID_OR_PASS, /* insert PVID tag or pass-through */
+ EVLAN_STRIP1_AND_PVID_OR_DISCARD,/* strip 1 tag + insert PVID, or discard */
+};
+
@@ -457,7 +457,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * regardless of TPID, so without the ACCEPT guard, it would also
+ * catch standard 802.1Q VID>0 frames and corrupt them. With the
+ * guard, 802.1Q VID>0 frames match the ACCEPT rules first and
+ * pass through untouched; only non-8021Q TPID frames fall through
+ * pass through untouched; only non-8021Q TPID frames pass through
+ * to the NO_FILTER catchalls.
+ */
+static const struct mxl862xx_evlan_rule_desc ingress_aware_final[] = {
@@ -486,27 +486,26 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+};
+
+/*
+ * VID-specific accept rules for VLAN-unaware egress.
+ * The HW sees the MxL tag as outer, real VLAN tag as inner.
+ * match on inner VID with outer=NO_FILTER.
+ * Egress tag-stripping rules for VLAN-unaware mode (2 per untagged VID).
+ * The HW sees the MxL tag as outer; the real VLAN tag, if any, is inner.
+ */
+static const struct mxl862xx_evlan_rule_desc vid_accept_egress_unaware[] = {
+ { FT_NO_FILTER, FT_NORMAL, TP_NONE, TP_8021Q, true, EVLAN_STRIP_IF_UNTAGGED },
+ { FT_NO_FILTER, FT_NO_TAG, TP_NONE, TP_NONE, true, EVLAN_STRIP_IF_UNTAGGED },
+ { FT_NO_FILTER, FT_NORMAL, TP_NONE, TP_8021Q, true, EVLAN_STRIP_IF_UNTAGGED },
+ { FT_NO_FILTER, FT_NO_TAG, TP_NONE, TP_NONE, false, EVLAN_STRIP_IF_UNTAGGED },
+};
+
static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol m)
@@ -275,6 +357,7 @@ static int mxl862xx_set_bridge_port(stru
@@ -275,6 +354,7 @@ static int mxl862xx_set_bridge_port(stru
struct mxl862xx_port *p = &priv->ports[port];
u16 bridge_id = dp->bridge ?
priv->bridges[dp->bridge->num] : p->fid;
struct dsa_port *member_dp;
u16 bridge_id;
+ u16 vf_scan;
bool enable;
int i, idx;
@@ -283,9 +366,69 @@ static int mxl862xx_set_bridge_port(stru
@@ -312,9 +392,69 @@ static int mxl862xx_set_bridge_port(stru
br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
@@ -574,23 +573,13 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ br_port_cfg.vlan_src_mac_vid_enable = p->vlan_filtering;
+ br_port_cfg.vlan_dst_mac_vid_enable = p->vlan_filtering;
+
mxl862xx_fw_portmap_from_bitmap(br_port_cfg.bridge_port_map, p->portmap);
for (i = 0; i < ARRAY_SIZE(mxl862xx_flood_meters); i++) {
@@ -329,6 +472,91 @@ static int mxl862xx_sync_bridge_members(
idx = mxl862xx_flood_meters[i];
enable = !!(p->flood_block & BIT(idx));
@@ -343,6 +483,72 @@ static int mxl862xx_sync_bridge_members(
return ret;
}
+static void mxl862xx_evlan_block_init(struct mxl862xx_evlan_block *blk,
+ u16 size)
+{
+ blk->allocated = false;
+ blk->in_use = false;
+ blk->block_id = 0;
+ blk->block_size = size;
+ blk->n_active = 0;
+}
+
+static int mxl862xx_evlan_block_alloc(struct mxl862xx_priv *priv,
+ struct mxl862xx_evlan_block *blk)
+{
@@ -609,15 +598,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return 0;
+}
+
+static void mxl862xx_vf_init(struct mxl862xx_vf_block *vf, u16 size)
+{
+ vf->allocated = false;
+ vf->block_id = 0;
+ vf->block_size = size;
+ vf->active_count = 0;
+ INIT_LIST_HEAD(&vf->vids);
+}
+
+static int mxl862xx_vf_block_alloc(struct mxl862xx_priv *priv,
+ u16 size, u16 *block_id)
+{
@@ -666,10 +646,10 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return mxl862xx_vf_entry_discard(priv, vf->block_id, 0);
+}
+
static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv, u16 *bridge_id)
static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv)
{
struct mxl862xx_bridge_alloc br_alloc = {};
@@ -392,6 +620,9 @@ static int mxl862xx_add_single_port_brid
@@ -378,6 +584,9 @@ static void mxl862xx_free_bridge(struct
static int mxl862xx_setup(struct dsa_switch *ds)
{
struct mxl862xx_priv *priv = ds->priv;
@@ -679,7 +659,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
int ret;
ret = mxl862xx_reset(priv);
@@ -402,6 +633,50 @@ static int mxl862xx_setup(struct dsa_swi
@@ -388,6 +597,50 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
@@ -695,7 +675,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * through without EVLAN processing.
+ *
+ * Total EVLAN budget:
+ * n_user_ports * (ingress + egress) 1024.
+ * n_user_ports * (ingress + egress) <= 1024.
+ * Ingress blocks are small (7 entries), so almost all capacity
+ * goes to egress VID rules.
+ */
@@ -730,23 +710,10 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
ret = mxl862xx_setup_drop_meter(ds);
if (ret)
return ret;
@@ -483,27 +758,616 @@ static int mxl862xx_configure_sp_tag_pro
@@ -469,12 +722,509 @@ static int mxl862xx_configure_sp_tag_pro
return MXL862XX_API_WRITE(ds->priv, MXL862XX_SS_SPTAG_SET, tag);
}
+/**
+ * mxl862xx_evlan_write_rule - Write a single Extended VLAN rule to hardware
+ * @priv: driver private data
+ * @block_id: HW Extended VLAN block ID
+ * @entry_index: entry index within the block
+ * @desc: rule descriptor (filter type + action)
+ * @vid: VLAN ID for VID-specific rules (ignored when !desc->match_vid)
+ * @untagged: strip tag on egress for EVLAN_STRIP_IF_UNTAGGED action
+ * @pvid: port VLAN ID for PVID insertion rules (0 = no PVID)
+ *
+ * Translates a compact rule descriptor into a full firmware
+ * mxl862xx_extendedvlan_config struct and writes it via the API.
+ */
+static int mxl862xx_evlan_write_rule(struct mxl862xx_priv *priv,
+ u16 block_id, u16 entry_index,
+ const struct mxl862xx_evlan_rule_desc *desc,
@@ -788,11 +755,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
+ break;
+
+ case EVLAN_DISCARD:
+ cfg.treatment.remove_tag =
+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_DISCARD_UPSTREAM);
+ break;
+
+ case EVLAN_PVID_OR_DISCARD:
+ if (pvid) {
+ cfg.treatment.remove_tag =
@@ -809,22 +771,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ }
+ break;
+
+ case EVLAN_PVID_OR_PASS:
+ if (pvid) {
+ cfg.treatment.remove_tag =
+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
+ cfg.treatment.add_outer_vlan = 1;
+ cfg.treatment.outer_vlan.vid_mode =
+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_VID_VAL);
+ cfg.treatment.outer_vlan.vid_val = cpu_to_le32(pvid);
+ cfg.treatment.outer_vlan.tpid =
+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_8021Q);
+ } else {
+ cfg.treatment.remove_tag =
+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
+ }
+ break;
+
+ case EVLAN_STRIP1_AND_PVID_OR_DISCARD:
+ if (pvid) {
+ cfg.treatment.remove_tag =
@@ -845,15 +791,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return MXL862XX_API_WRITE(priv, MXL862XX_EXTENDEDVLAN_SET, cfg);
+}
+
+/**
+ * mxl862xx_evlan_deactivate_entry - Reset an Extended VLAN entry to no-op
+ * @priv: driver private data
+ * @block_id: HW Extended VLAN block ID
+ * @entry_index: entry index within the block
+ *
+ * Writes a zeroed-out config to the firmware, which deactivates the
+ * rule (making it transparent / no-op).
+ */
+static int mxl862xx_evlan_deactivate_entry(struct mxl862xx_priv *priv,
+ u16 block_id, u16 entry_index)
+{
@@ -876,19 +813,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return MXL862XX_API_WRITE(priv, MXL862XX_EXTENDEDVLAN_SET, cfg);
+}
+
+/**
+ * mxl862xx_evlan_write_final_rules - Write catchall rules to the ingress block
+ * @priv: driver private data
+ * @blk: Extended VLAN block (already allocated)
+ * @rules: array of rule descriptors for the final rules
+ * @n_rules: number of final rules
+ * @pvid: port VLAN ID (for PVID insertion rules)
+ *
+ * Writes final catchall rules starting at block_size - n_rules. With
+ * VLAN Filter handling VID membership, only the ingress block uses
+ * finals, and the block is sized to exactly fit them (no VID entries),
+ * so the rules fill the entire block.
+ */
+static int mxl862xx_evlan_write_final_rules(struct mxl862xx_priv *priv,
+ struct mxl862xx_evlan_block *blk,
+ const struct mxl862xx_evlan_rule_desc *rules,
@@ -908,15 +832,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return 0;
+}
+
+/**
+ * mxl862xx_vf_entry_set - Write a single VLAN Filter entry
+ * @priv: driver private data
+ * @block_id: HW VLAN Filter block ID
+ * @index: entry index within the block
+ * @vid: VLAN ID to allow
+ *
+ * Writes an ALLOW entry (discard_matched=false) for the given VID.
+ */
+static int mxl862xx_vf_entry_set(struct mxl862xx_priv *priv,
+ u16 block_id, u16 index, u16 vid)
+{
@@ -931,13 +846,8 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return MXL862XX_API_WRITE(priv, MXL862XX_VLANFILTER_SET, cfg);
+}
+
+/**
+ * mxl862xx_vf_find_vid - Find a VID entry in a VF block
+ * @vf: VLAN Filter block to search
+ * @vid: VLAN ID to find
+ */
+static struct mxl862xx_vf_vid *
+mxl862xx_vf_find_vid(struct mxl862xx_vf_block *vf, u16 vid)
+static struct mxl862xx_vf_vid *mxl862xx_vf_find_vid(struct mxl862xx_vf_block *vf,
+ u16 vid)
+{
+ struct mxl862xx_vf_vid *ve;
+
@@ -948,17 +858,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return NULL;
+}
+
+/**
+ * mxl862xx_vf_add_vid - Add a VID to a port's VLAN Filter block
+ * @priv: driver private data
+ * @vf: VLAN Filter block
+ * @vid: VLAN ID to add
+ * @untagged: whether this VID should strip tags on egress
+ *
+ * Idempotent. Writes an ALLOW entry at active_count and increments
+ * active_count. If the VID already exists, only the untagged flag
+ * is updated. The HW block must be allocated before calling this.
+ */
+static int mxl862xx_vf_add_vid(struct mxl862xx_priv *priv,
+ struct mxl862xx_vf_block *vf,
+ u16 vid, bool untagged)
@@ -995,17 +894,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return 0;
+}
+
+/**
+ * mxl862xx_vf_del_vid - Remove a VID from a port's VLAN Filter block
+ * @priv: driver private data
+ * @vf: VLAN Filter block
+ * @vid: VLAN ID to remove
+ *
+ * Swap-compacts: the last active entry is moved into the gap,
+ * active_count is decremented, and the old last slot is plugged
+ * with DISCARD. When active_count drops to 0, a DISCARD sentinel
+ * is restored at index 0.
+ */
+static int mxl862xx_vf_del_vid(struct mxl862xx_priv *priv,
+ struct mxl862xx_vf_block *vf, u16 vid)
+{
@@ -1035,12 +923,11 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return ret;
+ } else if (gap < last) {
+ /* Swap: move the last ALLOW entry into the gap */
+ last_ve = NULL;
+ list_for_each_entry(last_ve, &vf->vids, list)
+ if (last_ve->index == last)
+ break;
+
+ if (WARN_ON(!last_ve || last_ve->index != last))
+ if (WARN_ON(list_entry_is_head(last_ve, &vf->vids, list)))
+ return -EINVAL;
+
+ ret = mxl862xx_vf_entry_set(priv, vf->block_id,
@@ -1049,16 +936,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return ret;
+
+ last_ve->index = gap;
+
+ /* Plug the old last slot with DISCARD */
+ ret = mxl862xx_vf_entry_discard(priv, vf->block_id, last);
+ if (ret)
+ return ret;
+ } else {
+ /* Deleting the last entry -- just plug it */
+ ret = mxl862xx_vf_entry_discard(priv, vf->block_id, last);
+ if (ret)
+ return ret;
+ }
+
+ list_del(&ve->list);
@@ -1068,20 +945,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return 0;
+}
+
+/**
+ * mxl862xx_evlan_program_ingress - Write the fixed ingress catchall rules
+ * @priv: driver private data
+ * @port: port number
+ *
+ * In VLAN-aware mode the ingress EVLAN block handles PVID insertion for
+ * untagged/priority-tagged frames, passes through standard 802.1Q
+ * tagged frames for VF membership checking, and treats non-8021Q TPID
+ * frames as untagged. The block is sized to exactly fit the 7 catchall
+ * rules and is rewritten whenever PVID changes.
+ *
+ * In VLAN-unaware mode the firmware passes frames through unchanged when
+ * no ingress block is assigned, so nothing is programmed.
+ */
+static int mxl862xx_evlan_program_ingress(struct mxl862xx_priv *priv, int port)
+{
+ struct mxl862xx_port *p = &priv->ports[port];
@@ -1099,19 +962,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ p->pvid);
+}
+
+/**
+ * mxl862xx_evlan_program_egress - Reprogram all egress tag-stripping rules
+ * @priv: driver private data
+ * @port: port number
+ *
+ * Walks the port's VF VID list and writes 2 EVLAN rules per VID that
+ * needs egress tag stripping. In VLAN-aware mode only untagged VIDs
+ * need rules (tagged VIDs pass through EVLAN untouched). In unaware
+ * mode every VID gets rules.
+ *
+ * Entries are packed starting at index 0, and the scan window
+ * (n_active) is narrowed so stale entries beyond it are never matched.
+ */
+static int mxl862xx_evlan_program_egress(struct mxl862xx_priv *priv, int port)
+{
+ struct mxl862xx_port *p = &priv->ports[port];
@@ -1131,10 +981,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ }
+
+ list_for_each_entry(vfv, &p->vf.vids, list) {
+ /* In VLAN-aware mode tagged-only VIDs need no EVLAN
+ * rules -- VLAN Filter handles membership.
+ */
+ if (p->vlan_filtering && !vfv->untagged)
+ if (!vfv->untagged)
+ continue;
+
+ if (idx + n_vid > blk->block_size)
@@ -1182,12 +1029,13 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
+ bool old_vlan_filtering = p->vlan_filtering;
+ bool old_in_use = p->ingress_evlan.in_use;
+ bool changed = (p->vlan_filtering != vlan_filtering);
+ int ret;
+
+ p->vlan_filtering = vlan_filtering;
+
+ /* Reprogram Extended VLAN rules if filtering mode changed */
+ if (changed) {
+ /* When leaving VLAN-aware mode, release the ingress HW
+ * block. The firmware passes frames through unchanged
@@ -1199,17 +1047,20 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+
+ ret = mxl862xx_evlan_program_ingress(priv, port);
+ if (ret)
+ return ret;
+ goto err_restore;
+
+ ret = mxl862xx_evlan_program_egress(priv, port);
+ if (ret)
+ return ret;
+ goto err_restore;
+ }
+
+ /* Push VLAN-based MAC learning flags and (possibly newly
+ * allocated) ingress block to hardware.
+ */
+ return mxl862xx_set_bridge_port(ds, port);
+
+ /* No HW rollback -- restoring SW state is sufficient for a correct retry. */
+err_restore:
+ p->vlan_filtering = old_vlan_filtering;
+ p->ingress_evlan.in_use = old_in_use;
+ return ret;
+}
+
+static int mxl862xx_port_vlan_add(struct dsa_switch *ds, int port,
@@ -1255,21 +1106,32 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ if (pvid_changed) {
+ ret = mxl862xx_evlan_program_ingress(priv, port);
+ if (ret)
+ goto err_pvid;
+ goto err_rollback;
+ }
+
+ /* Reprogram egress tag-stripping rules (walks VF VID list) */
+ ret = mxl862xx_evlan_program_egress(priv, port);
+ if (ret)
+ goto err_pvid;
+ goto err_rollback;
+
+ /* Apply VLAN block IDs and MAC learning flags to bridge port */
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
+ goto err_pvid;
+ goto err_rollback;
+
+ return 0;
+
+err_rollback:
+ /* Best-effort: undo VF add and restore consistent hardware state.
+ * A retry of port_vlan_add will converge since vf_add_vid is
+ * idempotent.
+ */
+ p->pvid = old_pvid;
+ mxl862xx_vf_del_vid(priv, &p->vf, vid);
+ mxl862xx_evlan_program_ingress(priv, port);
+ mxl862xx_evlan_program_egress(priv, port);
+ mxl862xx_set_bridge_port(ds, port);
+ return ret;
+err_pvid:
+ p->pvid = old_pvid;
+ return ret;
@@ -1280,13 +1142,22 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
+ u16 vid = vlan->vid;
+ struct mxl862xx_vf_vid *ve;
+ bool pvid_changed = false;
+ u16 vid = vlan->vid;
+ bool old_untagged;
+ u16 old_pvid;
+ int ret;
+
+ if (dsa_is_cpu_port(ds, port))
+ return 0;
+
+ ve = mxl862xx_vf_find_vid(&p->vf, vid);
+ if (!ve)
+ return 0;
+ old_untagged = ve->untagged;
+ old_pvid = p->pvid;
+
+ /* Clear PVID if we're deleting it */
+ if (p->pvid == vid) {
+ p->pvid = 0;
@@ -1299,28 +1170,45 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ */
+ ret = mxl862xx_vf_del_vid(priv, &p->vf, vid);
+ if (ret)
+ return ret;
+ goto err_pvid;
+
+ /* Reprogram egress tag-stripping rules (VID is now gone) */
+ ret = mxl862xx_evlan_program_egress(priv, port);
+ if (ret)
+ return ret;
+ goto err_rollback;
+
+ /* If PVID changed, reprogram ingress finals */
+ if (pvid_changed) {
+ ret = mxl862xx_evlan_program_ingress(priv, port);
+ if (ret)
+ return ret;
+ goto err_rollback;
+ }
+
+ return mxl862xx_set_bridge_port(ds, port);
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
+ goto err_rollback;
+
+ return 0;
+
+err_rollback:
+ /* Best-effort: re-add the VID and restore consistent hardware
+ * state. A retry of port_vlan_del will converge.
+ */
+ p->pvid = old_pvid;
+ mxl862xx_vf_add_vid(priv, &p->vf, vid, old_untagged);
+ mxl862xx_evlan_program_egress(priv, port);
+ mxl862xx_evlan_program_ingress(priv, port);
+ mxl862xx_set_bridge_port(ds, port);
+ return ret;
+err_pvid:
+ p->pvid = old_pvid;
+ return ret;
+}
+
static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port)
{
struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
struct dsa_port *dp;
- priv->ports[port].fid = MXL862XX_DEFAULT_BRIDGE;
- priv->ports[port].learning = true;
@@ -1332,41 +1220,21 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * assignment need to be configured.
+ */
/* include all assigned user ports in the CPU portmap */
- bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
+ bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
dsa_switch_for_each_user_port(dp, ds) {
/* it's safe to rely on cpu_dp being valid for user ports */
if (dp->cpu_dp->index != port)
continue;
- __set_bit(dp->index, priv->ports[port].portmap);
+ __set_bit(dp->index, p->portmap);
}
return mxl862xx_set_bridge_port(ds, port);
}
@@ -510,6 +1260,8 @@ static int mxl862xx_port_bridge_join(str
static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
const struct dsa_bridge bridge)
{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
int err;
+
static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
const struct dsa_bridge bridge,
bool *tx_fwd_offload,
@@ -553,6 +1417,22 @@ static void mxl862xx_port_bridge_leave(s
bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
__set_bit(dp->cpu_dp->index, p->portmap);
p->flood_block = 0;
+
+ /* Detach EVLAN and VF blocks from the bridge port BEFORE freeing
+ * them. The firmware tracks a usage count per block and rejects
+ * FREE while the count is non-zero.
+ *
+ * For EVLAN: setting in_use=false makes set_bridge_port send
+ * enable=false, which decrements the firmware refcount.
+ *
+ * For VF: set_bridge_port sees dp->bridge == NULL (DSA already
+ * cleared it) and sends vlan_filter_enable=0, which decrements
+ * the firmware VF refcount.
+ */
err = mxl862xx_sync_bridge_members(ds, &bridge);
@@ -521,6 +1273,10 @@ static void mxl862xx_port_bridge_leave(s
/* Revert leaving port, omitted by the sync above, to its
* single-port bridge
*/
+ p->pvid = 0;
+ p->ingress_evlan.in_use = false;
+ p->egress_evlan.in_use = false;
@@ -1374,28 +1242,22 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
err = mxl862xx_set_bridge_port(ds, port);
if (err)
dev_err(ds->dev,
@@ -602,6 +1482,28 @@ static int mxl862xx_port_setup(struct ds
@@ -585,6 +1341,22 @@ static int mxl862xx_port_setup(struct ds
if (ret)
return ret;
+ /* Initialize and pre-allocate per-port EVLAN and VF blocks for
+ * user ports. CPU ports do not use EVLAN or VF -- frames pass
+ * through without processing. Pre-allocation avoids firmware
+ * EVLAN table fragmentation and simplifies control flow.
+ */
+ mxl862xx_evlan_block_init(&priv->ports[port].ingress_evlan,
+ priv->evlan_ingress_size);
+ priv->ports[port].ingress_evlan.block_size = priv->evlan_ingress_size;
+ ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].ingress_evlan);
+ if (ret)
+ return ret;
+
+ mxl862xx_evlan_block_init(&priv->ports[port].egress_evlan,
+ priv->evlan_egress_size);
+ priv->ports[port].egress_evlan.block_size = priv->evlan_egress_size;
+ ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].egress_evlan);
+ if (ret)
+ return ret;
+
+ mxl862xx_vf_init(&priv->ports[port].vf, priv->vf_block_size);
+ priv->ports[port].vf.block_size = priv->vf_block_size;
+ INIT_LIST_HEAD(&priv->ports[port].vf.vids);
+ ret = mxl862xx_vf_alloc(priv, &priv->ports[port].vf);
+ if (ret)
+ return ret;
@@ -1403,7 +1265,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
priv->ports[port].setup_done = true;
return 0;
@@ -1012,6 +1914,9 @@ static const struct dsa_switch_ops mxl86
@@ -983,6 +1755,9 @@ static const struct dsa_switch_ops mxl86
.port_fdb_dump = mxl862xx_port_fdb_dump,
.port_mdb_add = mxl862xx_port_mdb_add,
.port_mdb_del = mxl862xx_port_mdb_del,
@@ -1424,7 +1286,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
/* Number of __le16 words in a firmware portmap (128-bit bitmap). */
#define MXL862XX_FW_PORTMAP_WORDS (MXL862XX_MAX_BRIDGE_PORTS / 16)
@@ -86,6 +88,66 @@ static inline bool mxl862xx_fw_portmap_i
@@ -55,6 +57,66 @@ static inline bool mxl862xx_fw_portmap_i
}
/**
@@ -1491,7 +1353,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* struct mxl862xx_port - per-port state tracked by the driver
* @priv: back-pointer to switch private data; needed by
* deferred work handlers to access ds and priv
@@ -101,6 +163,11 @@ static inline bool mxl862xx_fw_portmap_i
@@ -68,6 +130,11 @@ static inline bool mxl862xx_fw_portmap_i
* @setup_done: set at end of port_setup, cleared at start of
* port_teardown; guards deferred work against
* acting on torn-down state
@@ -1503,11 +1365,10 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* @host_flood_uc: desired host unicast flood state (true = flood);
* updated atomically by port_set_host_flood, consumed
* by the deferred host_flood_work
@@ -119,6 +186,12 @@ struct mxl862xx_port {
@@ -85,6 +152,11 @@ struct mxl862xx_port {
unsigned long flood_block;
bool learning;
bool setup_done;
+ /* VLAN state */
+ u16 pvid;
+ bool vlan_filtering;
+ struct mxl862xx_vf_block vf;
@@ -1516,7 +1377,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
bool host_flood_uc;
bool host_flood_mc;
struct work_struct host_flood_work;
@@ -126,17 +199,23 @@ struct mxl862xx_port {
@@ -92,17 +164,23 @@ struct mxl862xx_port {
/**
* struct mxl862xx_priv - driver private data for an MxL862xx switch
@@ -1551,7 +1412,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
*/
struct mxl862xx_priv {
struct dsa_switch *ds;
@@ -146,6 +225,9 @@ struct mxl862xx_priv {
@@ -112,6 +190,9 @@ struct mxl862xx_priv {
u16 drop_meter;
struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
u16 bridges[MXL862XX_MAX_BRIDGES + 1];
@@ -1,7 +1,7 @@
From 0067d79d10becfc5779fb50d5c0ac152cc5dc303 Mon Sep 17 00:00:00 2001
From e6295d124644b14a12b55edf5d3e89cf86a4a2ce Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Sun, 22 Mar 2026 00:57:33 +0000
Subject: [PATCH 06/26] net: dsa: mxl862xx: add ethtool statistics support
Date: Sun, 12 Apr 2026 01:01:57 +0100
Subject: [PATCH 1/2] net: dsa: mxl862xx: add ethtool statistics support
The MxL862xx firmware exposes per-port RMON counters through the
RMON_PORT_GET command, covering standard IEEE 802.3 MAC statistics
@@ -16,11 +16,13 @@ legacy ethtool -S support. Implement .get_eth_mac_stats,
IEEE 802.3 statistics interface.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/480be14d5ed51f3db7b1681b298044dbf8e87494.1775951347.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 142 ++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 142 +++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 3 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 168 ++++++++++++++++++++++++
3 files changed, 313 insertions(+)
drivers/net/dsa/mxl862xx/mxl862xx.c | 173 ++++++++++++++++++++++++
3 files changed, 318 insertions(+)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -120,7 +122,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * @tx_good_bytes: Transmitted good byte count (64-bit)
+ */
+struct mxl862xx_rmon_port_cnt {
+ enum mxl862xx_port_type port_type;
+ __le32 port_type; /* enum mxl862xx_port_type */
+ __le16 port_id;
+ __le16 sub_if_id_group;
+ u8 pce_bypass;
@@ -192,7 +194,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#define MXL862XX_MAC_TABLEENTRYQUERY (MXL862XX_SWMAC_MAGIC + 0x4)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -30,6 +30,64 @@
@@ -30,6 +30,38 @@
#define MXL862XX_API_READ_QUIET(dev, cmd, data) \
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
@@ -209,60 +211,34 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ .offset = offsetof(struct mxl862xx_rmon_port_cnt, _element) \
+}
+
+/* Hardware-specific counters not covered by any standardized stats callback. */
+static const struct mxl862xx_mib_desc mxl862xx_mib[] = {
+ MIB_DESC(1, "TxGoodPkts", tx_good_pkts),
+ MIB_DESC(1, "TxUnicastPkts", tx_unicast_pkts),
+ MIB_DESC(1, "TxBroadcastPkts", tx_broadcast_pkts),
+ MIB_DESC(1, "TxMulticastPkts", tx_multicast_pkts),
+ MIB_DESC(1, "Tx64BytePkts", tx64byte_pkts),
+ MIB_DESC(1, "Tx127BytePkts", tx127byte_pkts),
+ MIB_DESC(1, "Tx255BytePkts", tx255byte_pkts),
+ MIB_DESC(1, "Tx511BytePkts", tx511byte_pkts),
+ MIB_DESC(1, "Tx1023BytePkts", tx1023byte_pkts),
+ MIB_DESC(1, "TxMaxBytePkts", tx_max_byte_pkts),
+ MIB_DESC(1, "TxDroppedPkts", tx_dropped_pkts),
+ MIB_DESC(1, "TxAcmDroppedPkts", tx_acm_dropped_pkts),
+ MIB_DESC(2, "TxGoodBytes", tx_good_bytes),
+ MIB_DESC(1, "TxSingleCollCount", tx_single_coll_count),
+ MIB_DESC(1, "TxMultCollCount", tx_mult_coll_count),
+ MIB_DESC(1, "TxLateCollCount", tx_late_coll_count),
+ MIB_DESC(1, "TxExcessCollCount", tx_excess_coll_count),
+ MIB_DESC(1, "TxCollCount", tx_coll_count),
+ MIB_DESC(1, "TxPauseCount", tx_pause_count),
+ MIB_DESC(1, "RxGoodPkts", rx_good_pkts),
+ MIB_DESC(1, "RxUnicastPkts", rx_unicast_pkts),
+ MIB_DESC(1, "RxBroadcastPkts", rx_broadcast_pkts),
+ MIB_DESC(1, "RxMulticastPkts", rx_multicast_pkts),
+ MIB_DESC(1, "RxFCSErrorPkts", rx_fcserror_pkts),
+ MIB_DESC(1, "RxUnderSizeGoodPkts", rx_under_size_good_pkts),
+ MIB_DESC(1, "RxOversizeGoodPkts", rx_oversize_good_pkts),
+ MIB_DESC(1, "RxUnderSizeErrorPkts", rx_under_size_error_pkts),
+ MIB_DESC(1, "RxOversizeErrorPkts", rx_oversize_error_pkts),
+ MIB_DESC(1, "RxFilteredPkts", rx_filtered_pkts),
+ MIB_DESC(1, "Rx64BytePkts", rx64byte_pkts),
+ MIB_DESC(1, "Rx127BytePkts", rx127byte_pkts),
+ MIB_DESC(1, "Rx255BytePkts", rx255byte_pkts),
+ MIB_DESC(1, "Rx511BytePkts", rx511byte_pkts),
+ MIB_DESC(1, "Rx1023BytePkts", rx1023byte_pkts),
+ MIB_DESC(1, "RxMaxBytePkts", rx_max_byte_pkts),
+ MIB_DESC(1, "RxDroppedPkts", rx_dropped_pkts),
+ MIB_DESC(1, "RxExtendedVlanDiscardPkts", rx_extended_vlan_discard_pkts),
+ MIB_DESC(1, "MtuExceedDiscardPkts", mtu_exceed_discard_pkts),
+ MIB_DESC(2, "RxGoodBytes", rx_good_bytes),
+ MIB_DESC(2, "RxBadBytes", rx_bad_bytes),
+ MIB_DESC(1, "RxGoodPausePkts", rx_good_pause_pkts),
+ MIB_DESC(1, "RxAlignErrorPkts", rx_align_error_pkts),
+};
+
+static const struct ethtool_rmon_hist_range mxl862xx_rmon_ranges[] = {
+ { 0, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 10240 },
+ {}
+};
+
#define MXL862XX_SDMA_PCTRLP(p) (0xbc0 + ((p) * 0x6))
#define MXL862XX_SDMA_PCTRL_EN BIT(0)
@@ -1893,6 +1951,110 @@ static int mxl862xx_port_bridge_flags(st
@@ -1734,6 +1766,140 @@ static int mxl862xx_port_bridge_flags(st
return 0;
}
+static void mxl862xx_get_strings(struct dsa_switch *ds, int port,
+ u32 stringset, u8 *data)
+ u32 stringset, u8 *data)
+{
+ int i;
+
@@ -285,7 +261,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ struct mxl862xx_rmon_port_cnt *cnt)
+{
+ memset(cnt, 0, sizeof(*cnt));
+ cnt->port_type = MXL862XX_CTP_PORT;
+ cnt->port_type = cpu_to_le32(MXL862XX_CTP_PORT);
+ cnt->port_id = cpu_to_le16(port);
+
+ return MXL862XX_API_READ(ds->priv, MXL862XX_RMON_PORT_GET, *cnt);
@@ -365,10 +341,40 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ pause_stats->rx_pause_frames = le32_to_cpu(cnt.rx_good_pause_pkts);
+}
+
+static void mxl862xx_get_rmon_stats(struct dsa_switch *ds, int port,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct mxl862xx_rmon_port_cnt cnt;
+
+ if (mxl862xx_read_rmon(ds, port, &cnt))
+ return;
+
+ rmon_stats->undersize_pkts = le32_to_cpu(cnt.rx_under_size_good_pkts);
+ rmon_stats->oversize_pkts = le32_to_cpu(cnt.rx_oversize_good_pkts);
+ rmon_stats->fragments = le32_to_cpu(cnt.rx_under_size_error_pkts);
+ rmon_stats->jabbers = le32_to_cpu(cnt.rx_oversize_error_pkts);
+
+ rmon_stats->hist[0] = le32_to_cpu(cnt.rx64byte_pkts);
+ rmon_stats->hist[1] = le32_to_cpu(cnt.rx127byte_pkts);
+ rmon_stats->hist[2] = le32_to_cpu(cnt.rx255byte_pkts);
+ rmon_stats->hist[3] = le32_to_cpu(cnt.rx511byte_pkts);
+ rmon_stats->hist[4] = le32_to_cpu(cnt.rx1023byte_pkts);
+ rmon_stats->hist[5] = le32_to_cpu(cnt.rx_max_byte_pkts);
+
+ rmon_stats->hist_tx[0] = le32_to_cpu(cnt.tx64byte_pkts);
+ rmon_stats->hist_tx[1] = le32_to_cpu(cnt.tx127byte_pkts);
+ rmon_stats->hist_tx[2] = le32_to_cpu(cnt.tx255byte_pkts);
+ rmon_stats->hist_tx[3] = le32_to_cpu(cnt.tx511byte_pkts);
+ rmon_stats->hist_tx[4] = le32_to_cpu(cnt.tx1023byte_pkts);
+ rmon_stats->hist_tx[5] = le32_to_cpu(cnt.tx_max_byte_pkts);
+
+ *ranges = mxl862xx_rmon_ranges;
+}
static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_tag_protocol = mxl862xx_get_tag_protocol,
.setup = mxl862xx_setup,
@@ -1917,6 +2079,12 @@ static const struct dsa_switch_ops mxl86
@@ -1758,6 +1924,13 @@ static const struct dsa_switch_ops mxl86
.port_vlan_filtering = mxl862xx_port_vlan_filtering,
.port_vlan_add = mxl862xx_port_vlan_add,
.port_vlan_del = mxl862xx_port_vlan_del,
@@ -378,6 +384,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ .get_eth_mac_stats = mxl862xx_get_eth_mac_stats,
+ .get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
+ .get_pause_stats = mxl862xx_get_pause_stats,
+ .get_rmon_stats = mxl862xx_get_rmon_stats,
};
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
@@ -1,20 +1,22 @@
From bab5a69e3872a693069e430a1fa0d2825ea83b4f Mon Sep 17 00:00:00 2001
From a21d33a5265f0b31d935a8b9b2b6faefb5185911 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 04:14:38 +0000
Subject: [PATCH 07/26] net: dsa: mxl862xx: implement .get_stats64
Date: Sun, 12 Apr 2026 01:02:05 +0100
Subject: [PATCH 2/2] net: dsa: mxl862xx: implement .get_stats64
Poll free-running firmware RMON counters every 2 seconds and accumulate
deltas into 64-bit per-port statistics. 32-bit packet counters wrap
in ~880s at 2.5 Gbps line rate; the 2s polling interval provides a
comfortable margin. The .get_stats64 callback forces a fresh poll so
that counters are always up to date when queried.
in ~220s at 10 Gbps line rate with minimum-size frames; the 2s polling
interval provides a comfortable margin. The .get_stats64 callback
forces a fresh poll so that counters are always up to date when queried.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/fa38548ba05866879e8912721edc91947ce4ff12.1775951347.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 8 +-
drivers/net/dsa/mxl862xx/mxl862xx.c | 174 +++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx.h | 63 +++++++-
3 files changed, 238 insertions(+), 7 deletions(-)
drivers/net/dsa/mxl862xx/mxl862xx.c | 175 +++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx.h | 94 +++++++++++-
3 files changed, 270 insertions(+), 7 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -69,7 +71,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
struct mxl862xx_mib_desc {
unsigned int size;
unsigned int offset;
@@ -739,6 +745,9 @@ static int mxl862xx_setup(struct dsa_swi
@@ -677,6 +683,9 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
@@ -79,10 +81,11 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
return mxl862xx_setup_mdio(ds);
}
@@ -2055,6 +2064,158 @@ static void mxl862xx_get_pause_stats(str
pause_stats->rx_pause_frames = le32_to_cpu(cnt.rx_good_pause_pkts);
}
@@ -1900,6 +1909,159 @@ static void mxl862xx_get_rmon_stats(stru
*ranges = mxl862xx_rmon_ranges;
}
+
+/* Compute the delta between two 32-bit free-running counter snapshots,
+ * handling a single wrap-around correctly via unsigned subtraction.
+ */
@@ -238,15 +241,15 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_tag_protocol = mxl862xx_get_tag_protocol,
.setup = mxl862xx_setup,
@@ -2085,6 +2246,7 @@ static const struct dsa_switch_ops mxl86
.get_eth_mac_stats = mxl862xx_get_eth_mac_stats,
@@ -1931,6 +2093,7 @@ static const struct dsa_switch_ops mxl86
.get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
.get_pause_stats = mxl862xx_get_pause_stats,
.get_rmon_stats = mxl862xx_get_rmon_stats,
+ .get_stats64 = mxl862xx_get_stats64,
};
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
@@ -2146,16 +2308,22 @@ static int mxl862xx_probe(struct mdio_de
@@ -1992,16 +2155,22 @@ static int mxl862xx_probe(struct mdio_de
priv->ports[i].priv = priv;
INIT_WORK(&priv->ports[i].host_flood_work,
mxl862xx_host_flood_work_fn);
@@ -269,7 +272,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
return err;
}
@@ -2170,6 +2338,9 @@ static void mxl862xx_remove(struct mdio_
@@ -2016,6 +2185,9 @@ static void mxl862xx_remove(struct mdio_
priv = ds->priv;
@@ -279,7 +282,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
dsa_unregister_switch(ds);
mxl862xx_host_shutdown(priv);
@@ -2196,6 +2367,9 @@ static void mxl862xx_shutdown(struct mdi
@@ -2042,6 +2214,9 @@ static void mxl862xx_shutdown(struct mdi
dsa_switch_shutdown(ds);
@@ -291,11 +294,45 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
for (i = 0; i < MXL862XX_MAX_PORTS; i++)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -148,6 +148,47 @@ struct mxl862xx_evlan_block {
@@ -117,6 +117,79 @@ struct mxl862xx_evlan_block {
};
/**
+ * struct mxl862xx_port_stats - 64-bit accumulated hardware port statistics
+ * @rx_packets: total received packets
+ * @tx_packets: total transmitted packets
+ * @rx_bytes: total received bytes
+ * @tx_bytes: total transmitted bytes
+ * @rx_errors: total receive errors
+ * @tx_errors: total transmit errors
+ * @rx_dropped: total received packets dropped
+ * @tx_dropped: total transmitted packets dropped
+ * @multicast: total received multicast packets
+ * @collisions: total transmit collisions
+ * @rx_length_errors: received length errors (undersize + oversize)
+ * @rx_crc_errors: received FCS errors
+ * @rx_frame_errors: received alignment errors
+ * @prev_rx_good_pkts: previous snapshot of rx good packet counter
+ * @prev_tx_good_pkts: previous snapshot of tx good packet counter
+ * @prev_rx_good_bytes: previous snapshot of rx good byte counter
+ * @prev_tx_good_bytes: previous snapshot of tx good byte counter
+ * @prev_rx_fcserror_pkts: previous snapshot of rx FCS error counter
+ * @prev_rx_under_size_error_pkts: previous snapshot of rx undersize
+ * error counter
+ * @prev_rx_oversize_error_pkts: previous snapshot of rx oversize
+ * error counter
+ * @prev_rx_align_error_pkts: previous snapshot of rx alignment
+ * error counter
+ * @prev_tx_dropped_pkts: previous snapshot of tx dropped counter
+ * @prev_rx_dropped_pkts: previous snapshot of rx dropped counter
+ * @prev_rx_evlan_discard_pkts: previous snapshot of extended VLAN
+ * discard counter
+ * @prev_mtu_exceed_discard_pkts: previous snapshot of MTU exceed
+ * discard counter
+ * @prev_tx_acm_dropped_pkts: previous snapshot of tx ACM dropped
+ * counter
+ * @prev_rx_multicast_pkts: previous snapshot of rx multicast counter
+ * @prev_tx_coll_count: previous snapshot of tx collision counter
+ *
+ * The firmware RMON counters are 32-bit free-running (64-bit for byte
+ * counters). This structure holds 64-bit accumulators alongside the
@@ -303,7 +340,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * handling 32-bit wrap correctly via unsigned subtraction.
+ */
+struct mxl862xx_port_stats {
+ /* 64-bit accumulators */
+ u64 rx_packets;
+ u64 tx_packets;
+ u64 rx_bytes;
@@ -317,7 +353,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ u64 rx_length_errors;
+ u64 rx_crc_errors;
+ u64 rx_frame_errors;
+ /* Previous raw RMON values for delta computation */
+ u32 prev_rx_good_pkts;
+ u32 prev_tx_good_pkts;
+ u64 prev_rx_good_bytes;
@@ -339,7 +374,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* struct mxl862xx_port - per-port state tracked by the driver
* @priv: back-pointer to switch private data; needed by
* deferred work handlers to access ds and priv
@@ -178,6 +219,10 @@ struct mxl862xx_evlan_block {
@@ -145,6 +218,10 @@ struct mxl862xx_evlan_block {
* The worker acquires rtnl_lock() to serialize with
* DSA callbacks and checks @setup_done to avoid
* acting on torn-down ports.
@@ -350,13 +385,12 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
*/
struct mxl862xx_port {
struct mxl862xx_priv *priv;
@@ -195,16 +240,25 @@ struct mxl862xx_port {
@@ -160,16 +237,24 @@ struct mxl862xx_port {
bool host_flood_uc;
bool host_flood_mc;
struct work_struct host_flood_work;
+ /* Hardware stats accumulation */
+ struct mxl862xx_port_stats stats;
+ spinlock_t stats_lock;
+ spinlock_t stats_lock; /* protects stats accumulators */
};
+/* Bit indices for struct mxl862xx_priv::flags */
@@ -378,7 +412,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* @drop_meter: index of the single shared zero-rate firmware meter
* used to unconditionally drop traffic (used to block
* flooding)
@@ -216,18 +270,21 @@ struct mxl862xx_port {
@@ -181,18 +266,21 @@ struct mxl862xx_port {
* @evlan_ingress_size: per-port ingress Extended VLAN block size
* @evlan_egress_size: per-port egress Extended VLAN block size
* @vf_block_size: per-port VLAN Filter block size
@@ -0,0 +1,235 @@
From b7b4dcd96e3dfbb955d152c9ce4b490498b0f4b4 Mon Sep 17 00:00:00 2001
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: Sun, 11 Jan 2026 11:39:30 +0200
Subject: [PATCH 1/5] dt-bindings: phy: rename transmit-amplitude.yaml to
phy-common-props.yaml
I would like to add more properties similar to tx-p2p-microvolt, and I
don't think it makes sense to create one schema for each such property
(transmit-amplitude.yaml, lane-polarity.yaml, transmit-equalization.yaml
etc).
Instead, let's rename to phy-common-props.yaml, which makes it a more
adequate host schema for all the above properties.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Link: https://patch.msgid.link/20260111093940.975359-2-vladimir.oltean@nxp.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
---
.../{transmit-amplitude.yaml => phy-common-props.yaml} | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
rename Documentation/devicetree/bindings/phy/{transmit-amplitude.yaml => phy-common-props.yaml} (90%)
--- a/Documentation/devicetree/bindings/phy/transmit-amplitude.yaml
+++ /dev/null
@@ -1,103 +0,0 @@
-# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
-%YAML 1.2
----
-$id: http://devicetree.org/schemas/phy/transmit-amplitude.yaml#
-$schema: http://devicetree.org/meta-schemas/core.yaml#
-
-title: Common PHY and network PCS transmit amplitude property
-
-description:
- Binding describing the peak-to-peak transmit amplitude for common PHYs
- and network PCSes.
-
-maintainers:
- - Marek Behún <kabel@kernel.org>
-
-properties:
- tx-p2p-microvolt:
- description:
- Transmit amplitude voltages in microvolts, peak-to-peak. If this property
- contains multiple values for various PHY modes, the
- 'tx-p2p-microvolt-names' property must be provided and contain
- corresponding mode names.
-
- tx-p2p-microvolt-names:
- description: |
- Names of the modes corresponding to voltages in the 'tx-p2p-microvolt'
- property. Required only if multiple voltages are provided.
-
- If a value of 'default' is provided, the system should use it for any PHY
- mode that is otherwise not defined here. If 'default' is not provided, the
- system should use manufacturer default value.
- minItems: 1
- maxItems: 16
- items:
- enum:
- - default
-
- # ethernet modes
- - sgmii
- - qsgmii
- - xgmii
- - 1000base-x
- - 2500base-x
- - 5gbase-r
- - rxaui
- - xaui
- - 10gbase-kr
- - usxgmii
- - 10gbase-r
- - 25gbase-r
-
- # PCIe modes
- - pcie
- - pcie1
- - pcie2
- - pcie3
- - pcie4
- - pcie5
- - pcie6
-
- # USB modes
- - usb
- - usb-ls
- - usb-fs
- - usb-hs
- - usb-ss
- - usb-ss+
- - usb-4
-
- # storage modes
- - sata
- - ufs-hs
- - ufs-hs-a
- - ufs-hs-b
-
- # display modes
- - lvds
- - dp
- - dp-rbr
- - dp-hbr
- - dp-hbr2
- - dp-hbr3
- - dp-uhbr-10
- - dp-uhbr-13.5
- - dp-uhbr-20
-
- # camera modes
- - mipi-dphy
- - mipi-dphy-univ
- - mipi-dphy-v2.5-univ
-
-dependencies:
- tx-p2p-microvolt-names: [ tx-p2p-microvolt ]
-
-additionalProperties: true
-
-examples:
- - |
- phy: phy {
- #phy-cells = <1>;
- tx-p2p-microvolt = <915000>, <1100000>, <1200000>;
- tx-p2p-microvolt-names = "2500base-x", "usb-hs", "usb-ss";
- };
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/phy-common-props.yaml
@@ -0,0 +1,103 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/phy-common-props.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Common PHY and network PCS properties
+
+description:
+ Common PHY and network PCS properties, such as peak-to-peak transmit
+ amplitude.
+
+maintainers:
+ - Marek Behún <kabel@kernel.org>
+
+properties:
+ tx-p2p-microvolt:
+ description:
+ Transmit amplitude voltages in microvolts, peak-to-peak. If this property
+ contains multiple values for various PHY modes, the
+ 'tx-p2p-microvolt-names' property must be provided and contain
+ corresponding mode names.
+
+ tx-p2p-microvolt-names:
+ description: |
+ Names of the modes corresponding to voltages in the 'tx-p2p-microvolt'
+ property. Required only if multiple voltages are provided.
+
+ If a value of 'default' is provided, the system should use it for any PHY
+ mode that is otherwise not defined here. If 'default' is not provided, the
+ system should use manufacturer default value.
+ minItems: 1
+ maxItems: 16
+ items:
+ enum:
+ - default
+
+ # ethernet modes
+ - sgmii
+ - qsgmii
+ - xgmii
+ - 1000base-x
+ - 2500base-x
+ - 5gbase-r
+ - rxaui
+ - xaui
+ - 10gbase-kr
+ - usxgmii
+ - 10gbase-r
+ - 25gbase-r
+
+ # PCIe modes
+ - pcie
+ - pcie1
+ - pcie2
+ - pcie3
+ - pcie4
+ - pcie5
+ - pcie6
+
+ # USB modes
+ - usb
+ - usb-ls
+ - usb-fs
+ - usb-hs
+ - usb-ss
+ - usb-ss+
+ - usb-4
+
+ # storage modes
+ - sata
+ - ufs-hs
+ - ufs-hs-a
+ - ufs-hs-b
+
+ # display modes
+ - lvds
+ - dp
+ - dp-rbr
+ - dp-hbr
+ - dp-hbr2
+ - dp-hbr3
+ - dp-uhbr-10
+ - dp-uhbr-13.5
+ - dp-uhbr-20
+
+ # camera modes
+ - mipi-dphy
+ - mipi-dphy-univ
+ - mipi-dphy-v2.5-univ
+
+dependencies:
+ tx-p2p-microvolt-names: [ tx-p2p-microvolt ]
+
+additionalProperties: true
+
+examples:
+ - |
+ phy: phy {
+ #phy-cells = <1>;
+ tx-p2p-microvolt = <915000>, <1100000>, <1200000>;
+ tx-p2p-microvolt-names = "2500base-x", "usb-hs", "usb-ss";
+ };
@@ -0,0 +1,69 @@
From 33c79865c7d3cc84705ed133c101794902e60269 Mon Sep 17 00:00:00 2001
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: Sun, 11 Jan 2026 11:39:31 +0200
Subject: [PATCH 2/5] dt-bindings: phy-common-props: create a reusable
"protocol-names" definition
Other properties also need to be defined per protocol than just
tx-p2p-microvolt-names. Create a common definition to avoid copying a 55
line property.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Link: https://patch.msgid.link/20260111093940.975359-3-vladimir.oltean@nxp.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
---
.../bindings/phy/phy-common-props.yaml | 34 +++++++++++--------
1 file changed, 19 insertions(+), 15 deletions(-)
--- a/Documentation/devicetree/bindings/phy/phy-common-props.yaml
+++ b/Documentation/devicetree/bindings/phy/phy-common-props.yaml
@@ -13,22 +13,12 @@ description:
maintainers:
- Marek Behún <kabel@kernel.org>
-properties:
- tx-p2p-microvolt:
+$defs:
+ protocol-names:
description:
- Transmit amplitude voltages in microvolts, peak-to-peak. If this property
- contains multiple values for various PHY modes, the
- 'tx-p2p-microvolt-names' property must be provided and contain
- corresponding mode names.
-
- tx-p2p-microvolt-names:
- description: |
- Names of the modes corresponding to voltages in the 'tx-p2p-microvolt'
- property. Required only if multiple voltages are provided.
-
- If a value of 'default' is provided, the system should use it for any PHY
- mode that is otherwise not defined here. If 'default' is not provided, the
- system should use manufacturer default value.
+ Names of the PHY modes. If a value of 'default' is provided, the system
+ should use it for any PHY mode that is otherwise not defined here. If
+ 'default' is not provided, the system should use manufacturer default value.
minItems: 1
maxItems: 16
items:
@@ -89,6 +79,20 @@ properties:
- mipi-dphy-univ
- mipi-dphy-v2.5-univ
+properties:
+ tx-p2p-microvolt:
+ description:
+ Transmit amplitude voltages in microvolts, peak-to-peak. If this property
+ contains multiple values for various PHY modes, the
+ 'tx-p2p-microvolt-names' property must be provided and contain
+ corresponding mode names.
+
+ tx-p2p-microvolt-names:
+ description:
+ Names of the modes corresponding to voltages in the 'tx-p2p-microvolt'
+ property. Required only if multiple voltages are provided.
+ $ref: "#/$defs/protocol-names"
+
dependencies:
tx-p2p-microvolt-names: [ tx-p2p-microvolt ]
@@ -0,0 +1,42 @@
From 01fc2215940c20bbb22fa196a331ec9d50e45452 Mon Sep 17 00:00:00 2001
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: Sun, 11 Jan 2026 11:39:32 +0200
Subject: [PATCH 3/5] dt-bindings: phy-common-props: ensure protocol-names are
unique
Rob Herring points out that "The default for .*-names is the entries
don't have to be unique.":
https://lore.kernel.org/linux-phy/20251204155219.GA1533839-robh@kernel.org/
Let's use uniqueItems: true to make sure the schema enforces this. It
doesn't make sense in this case to have duplicate properties for the
same SerDes protocol.
Note that this can only be done with the $defs + $ref pattern as
established by the previous commit. When the tx-p2p-microvolt-names
constraints were expressed directly under "properties", it would have
been validated by the string-array meta-schema, which does not support
the 'uniqueItems' keyword as can be seen below.
properties:tx-p2p-microvolt-names: Additional properties are not allowed ('uniqueItems' was unexpected)
from schema $id: http://devicetree.org/meta-schemas/string-array.yaml
Suggested-by: Rob Herring <robh@kernel.org>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Link: https://patch.msgid.link/20260111093940.975359-4-vladimir.oltean@nxp.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
---
Documentation/devicetree/bindings/phy/phy-common-props.yaml | 1 +
1 file changed, 1 insertion(+)
--- a/Documentation/devicetree/bindings/phy/phy-common-props.yaml
+++ b/Documentation/devicetree/bindings/phy/phy-common-props.yaml
@@ -21,6 +21,7 @@ $defs:
'default' is not provided, the system should use manufacturer default value.
minItems: 1
maxItems: 16
+ uniqueItems: true
items:
enum:
- default
@@ -0,0 +1,177 @@
From fceb17ac05e772ffc82f1f008e876bf7752f0576 Mon Sep 17 00:00:00 2001
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: Sun, 11 Jan 2026 11:39:33 +0200
Subject: [PATCH 4/5] dt-bindings: phy-common-props: RX and TX lane polarity
inversion
Differential signaling is a technique for high-speed protocols to be
more resilient to noise. At the transmit side we have a positive and a
negative signal which are mirror images of each other. At the receiver,
if we subtract the negative signal (say of amplitude -A) from the
positive signal (say +A), we recover the original single-ended signal at
twice its original amplitude. But any noise, like one coming from EMI
from outside sources, is supposed to have an almost equal impact upon
the positive (A + E, E being for "error") and negative signal (-A + E).
So (A + E) - (-A + E) eliminates this noise, and this is what makes
differential signaling useful.
Except that in order to work, there must be strict requirements observed
during PCB design and layout, like the signal traces needing to have the
same length and be physically close to each other, and many others.
Sometimes it is not easy to fulfill all these requirements, a simple
case to understand is when on chip A's pins, the positive pin is on the
left and the negative is on the right, but on the chip B's pins (with
which A tries to communicate), positive is on the right and negative on
the left. The signals would need to cross, using vias and other ugly
stuff that affects signal integrity (introduces impedance
discontinuities which cause reflections, etc).
So sometimes, board designers intentionally connect differential lanes
the wrong way, and expect somebody else to invert that signal to recover
useful data. This is where RX and TX polarity inversion comes in as a
generic concept that applies to any high-speed serial protocol as long
as it uses differential signaling.
I've stopped two attempts to introduce more vendor-specific descriptions
of this only in the past month:
https://lore.kernel.org/linux-phy/20251110110536.2596490-1-horatiu.vultur@microchip.com/
https://lore.kernel.org/netdev/20251028000959.3kiac5kwo5pcl4ft@skbuf/
and in the kernel we already have merged:
- "st,px_rx_pol_inv"
- "st,pcie-tx-pol-inv"
- "st,sata-tx-pol-inv"
- "mediatek,pnswap"
- "airoha,pnswap-rx"
- "airoha,pnswap-tx"
and maybe more. So it is pretty general.
One additional element of complexity is introduced by the fact that for
some protocols, receivers can automatically detect and correct for an
inverted lane polarity (example: the PCIe LTSSM does this in the
Polling.Configuration state; the USB 3.1 Link Layer Test Specification
says that the detection and correction of the lane polarity inversion in
SuperSpeed operation shall be enabled in Polling.RxEQ.). Whereas for
other protocols (SGMII, SATA, 10GBase-R, etc etc), the polarity is all
manual and there is no detection mechanism mandated by their respective
standards.
So why would one even describe rx-polarity and tx-polarity for protocols
like PCIe, if it had to always be PHY_POL_AUTO?
Related question: why would we define the polarity as an array per
protocol? Isn't the physical PCB layout protocol-agnostic, and aren't we
describing the same physical reality from the lens of different protocols?
The answer to both questions is because multi-protocol PHYs exist
(supporting e.g. USB2 and USB3, or SATA and PCIe, or PCIe and Ethernet
over the same lane), one would need to manually set the polarity for
SATA/Ethernet, while leaving it at auto for PCIe/USB 3.0+.
I also investigated from another angle: what if polarity inversion in
the PHY is one layer, and then the PCIe/USB3 LTSSM polarity detection is
another layer on top? Then rx-polarity = <PHY_POL_AUTO> doesn't make
sense, it can still be rx-polarity = <PHY_POL_NORMAL> or <PHY_POL_INVERT>,
and the link training state machine figures things out on top of that.
This would radically simplify the design, as the elimination of
PHY_POL_AUTO inherently means that the need for a property array per
protocol also goes away.
I don't know how things are in the general case, but at least in the 10G
and 28G Lynx SerDes blocks from NXP Layerscape devices, this isn't the
case, and there's only a single level of RX polarity inversion: in the
SerDes lane. In the case of PCIe, the controller is in charge of driving
the RDAT_INV bit autonomously, and it is read-only to software.
So the existence of this kind of SerDes lane proves the need for
PHY_POL_AUTO to be a third state.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Link: https://patch.msgid.link/20260111093940.975359-5-vladimir.oltean@nxp.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
---
.../bindings/phy/phy-common-props.yaml | 49 +++++++++++++++++++
include/dt-bindings/phy/phy.h | 4 ++
2 files changed, 53 insertions(+)
--- a/Documentation/devicetree/bindings/phy/phy-common-props.yaml
+++ b/Documentation/devicetree/bindings/phy/phy-common-props.yaml
@@ -94,15 +94,64 @@ properties:
property. Required only if multiple voltages are provided.
$ref: "#/$defs/protocol-names"
+ rx-polarity:
+ description:
+ An array of values indicating whether the differential receiver's
+ polarity is inverted. Each value can be one of
+ PHY_POL_NORMAL (0) which means the negative signal is decoded from the
+ RXN input, and the positive signal from the RXP input;
+ PHY_POL_INVERT (1) which means the negative signal is decoded from the
+ RXP input, and the positive signal from the RXN input;
+ PHY_POL_AUTO (2) which means the receiver performs automatic polarity
+ detection and correction, which is a mandatory part of link training for
+ some protocols (PCIe, USB SS).
+
+ The values are defined in <dt-bindings/phy/phy.h>. If the property is
+ absent, the default value is undefined.
+
+ Note that the RXP and RXN inputs refer to the block that this property is
+ under, and do not necessarily directly translate to external pins.
+
+ If this property contains multiple values for various protocols, the
+ 'rx-polarity-names' property must be provided.
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 1
+ maxItems: 16
+ items:
+ enum: [0, 1, 2]
+
+ rx-polarity-names:
+ $ref: '#/$defs/protocol-names'
+
+ tx-polarity:
+ description:
+ Like 'rx-polarity', except it applies to differential transmitters,
+ and only the values of PHY_POL_NORMAL and PHY_POL_INVERT are possible.
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 1
+ maxItems: 16
+ items:
+ enum: [0, 1]
+
+ tx-polarity-names:
+ $ref: '#/$defs/protocol-names'
+
dependencies:
tx-p2p-microvolt-names: [ tx-p2p-microvolt ]
+ rx-polarity-names: [ rx-polarity ]
+ tx-polarity-names: [ tx-polarity ]
additionalProperties: true
examples:
- |
+ #include <dt-bindings/phy/phy.h>
+
phy: phy {
#phy-cells = <1>;
tx-p2p-microvolt = <915000>, <1100000>, <1200000>;
tx-p2p-microvolt-names = "2500base-x", "usb-hs", "usb-ss";
+ rx-polarity = <PHY_POL_AUTO>, <PHY_POL_NORMAL>;
+ rx-polarity-names = "usb-ss", "default";
+ tx-polarity = <PHY_POL_INVERT>;
};
--- a/include/dt-bindings/phy/phy.h
+++ b/include/dt-bindings/phy/phy.h
@@ -24,4 +24,8 @@
#define PHY_TYPE_CPHY 11
#define PHY_TYPE_USXGMII 12
+#define PHY_POL_NORMAL 0
+#define PHY_POL_INVERT 1
+#define PHY_POL_AUTO 2
+
#endif /* _DT_BINDINGS_PHY */
@@ -0,0 +1,758 @@
From e7556b59ba65179612bce3fa56bb53d1b4fb20db Mon Sep 17 00:00:00 2001
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: Sun, 11 Jan 2026 11:39:34 +0200
Subject: [PATCH 5/5] phy: add phy_get_rx_polarity() and phy_get_tx_polarity()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add helpers in the generic PHY folder which can be used using 'select
PHY_COMMON_PROPS' from Kconfig, without otherwise needing to
enable GENERIC_PHY.
These helpers need to deal with the slight messiness of the fact that
the polarity properties are arrays per protocol, and with the fact that
there is no default value mandated by the standard properties, all
default values depend on driver and protocol (PHY_POL_NORMAL may be a
good default for SGMII, whereas PHY_POL_AUTO may be a good default for
PCIe).
Push the supported mask of polarities to these helpers, to simplify
drivers such that they don't need to validate what's in the device tree
(or other firmware description).
Add a KUnit test suite to make sure that the API produces the expected
results. The fact that we use fwnode structures means we can validate
with software nodes, and as opposed to the device_property API, we can
bypass the need to have a device structure.
Co-developed-by: Bjørn Mork <bjorn@mork.no>
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Link: https://patch.msgid.link/20260111093940.975359-6-vladimir.oltean@nxp.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
---
drivers/phy/Kconfig | 22 ++
drivers/phy/Makefile | 2 +
drivers/phy/phy-common-props-test.c | 422 +++++++++++++++++++++++++++
drivers/phy/phy-common-props.c | 209 +++++++++++++
include/linux/phy/phy-common-props.h | 32 ++
6 files changed, 697 insertions(+)
create mode 100644 drivers/phy/phy-common-props-test.c
create mode 100644 drivers/phy/phy-common-props.c
create mode 100644 include/linux/phy/phy-common-props.h
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -5,6 +5,28 @@
menu "PHY Subsystem"
+config PHY_COMMON_PROPS
+ bool
+ help
+ This parses properties common between generic PHYs and Ethernet PHYs.
+
+ Select this from consumer drivers to gain access to helpers for
+ parsing properties from the
+ Documentation/devicetree/bindings/phy/phy-common-props.yaml schema.
+
+config PHY_COMMON_PROPS_TEST
+ tristate "KUnit tests for PHY common props" if !KUNIT_ALL_TESTS
+ select PHY_COMMON_PROPS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds KUnit tests for the PHY common property API.
+
+ For more information on KUnit and unit tests in general,
+ please refer to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+ When in doubt, say N.
+
config GENERIC_PHY
bool "PHY Core"
help
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -3,6 +3,8 @@
# Makefile for the phy drivers.
#
+obj-$(CONFIG_PHY_COMMON_PROPS) += phy-common-props.o
+obj-$(CONFIG_PHY_COMMON_PROPS_TEST) += phy-common-props-test.o
obj-$(CONFIG_GENERIC_PHY) += phy-core.o
obj-$(CONFIG_GENERIC_PHY_MIPI_DPHY) += phy-core-mipi-dphy.o
obj-$(CONFIG_PHY_CAN_TRANSCEIVER) += phy-can-transceiver.o
--- /dev/null
+++ b/drivers/phy/phy-common-props-test.c
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * phy-common-props-test.c -- Unit tests for PHY common properties API
+ *
+ * Copyright 2025-2026 NXP
+ */
+#include <kunit/test.h>
+#include <linux/property.h>
+#include <linux/phy/phy-common-props.h>
+#include <dt-bindings/phy/phy.h>
+
+/* Test: rx-polarity property is missing */
+static void phy_test_rx_polarity_is_missing(struct kunit *test)
+{
+ static const struct property_entry entries[] = {
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_rx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, val, PHY_POL_NORMAL);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: rx-polarity has more values than rx-polarity-names */
+static void phy_test_rx_polarity_more_values_than_names(struct kunit *test)
+{
+ static const u32 rx_pol[] = { PHY_POL_NORMAL, PHY_POL_INVERT, PHY_POL_NORMAL };
+ static const char * const rx_pol_names[] = { "sgmii", "2500base-x" };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("rx-polarity", rx_pol),
+ PROPERTY_ENTRY_STRING_ARRAY("rx-polarity-names", rx_pol_names),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_rx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: rx-polarity has 1 value and rx-polarity-names does not exist */
+static void phy_test_rx_polarity_single_value_no_names(struct kunit *test)
+{
+ static const u32 rx_pol[] = { PHY_POL_INVERT };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("rx-polarity", rx_pol),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_rx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, val, PHY_POL_INVERT);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: rx-polarity-names has more values than rx-polarity */
+static void phy_test_rx_polarity_more_names_than_values(struct kunit *test)
+{
+ static const u32 rx_pol[] = { PHY_POL_NORMAL, PHY_POL_INVERT };
+ static const char * const rx_pol_names[] = { "sgmii", "2500base-x", "1000base-x" };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("rx-polarity", rx_pol),
+ PROPERTY_ENTRY_STRING_ARRAY("rx-polarity-names", rx_pol_names),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_rx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: rx-polarity and rx-polarity-names have same length, find the name */
+static void phy_test_rx_polarity_find_by_name(struct kunit *test)
+{
+ static const u32 rx_pol[] = { PHY_POL_NORMAL, PHY_POL_INVERT, PHY_POL_AUTO };
+ static const char * const rx_pol_names[] = { "sgmii", "2500base-x", "usb-ss" };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("rx-polarity", rx_pol),
+ PROPERTY_ENTRY_STRING_ARRAY("rx-polarity-names", rx_pol_names),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_rx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, val, PHY_POL_NORMAL);
+
+ ret = phy_get_manual_rx_polarity(node, "2500base-x", &val);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, val, PHY_POL_INVERT);
+
+ ret = phy_get_rx_polarity(node, "usb-ss", BIT(PHY_POL_AUTO),
+ PHY_POL_AUTO, &val);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, val, PHY_POL_AUTO);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: same length, name not found, no "default" - error */
+static void phy_test_rx_polarity_name_not_found_no_default(struct kunit *test)
+{
+ static const u32 rx_pol[] = { PHY_POL_NORMAL, PHY_POL_INVERT };
+ static const char * const rx_pol_names[] = { "2500base-x", "1000base-x" };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("rx-polarity", rx_pol),
+ PROPERTY_ENTRY_STRING_ARRAY("rx-polarity-names", rx_pol_names),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_rx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: same length, name not found, but "default" exists */
+static void phy_test_rx_polarity_name_not_found_with_default(struct kunit *test)
+{
+ static const u32 rx_pol[] = { PHY_POL_NORMAL, PHY_POL_INVERT };
+ static const char * const rx_pol_names[] = { "2500base-x", "default" };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("rx-polarity", rx_pol),
+ PROPERTY_ENTRY_STRING_ARRAY("rx-polarity-names", rx_pol_names),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_rx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, val, PHY_POL_INVERT);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: polarity found but value is unsupported */
+static void phy_test_rx_polarity_unsupported_value(struct kunit *test)
+{
+ static const u32 rx_pol[] = { PHY_POL_AUTO };
+ static const char * const rx_pol_names[] = { "sgmii" };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("rx-polarity", rx_pol),
+ PROPERTY_ENTRY_STRING_ARRAY("rx-polarity-names", rx_pol_names),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_rx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, -EOPNOTSUPP);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: tx-polarity property is missing */
+static void phy_test_tx_polarity_is_missing(struct kunit *test)
+{
+ static const struct property_entry entries[] = {
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_tx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, val, PHY_POL_NORMAL);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: tx-polarity has more values than tx-polarity-names */
+static void phy_test_tx_polarity_more_values_than_names(struct kunit *test)
+{
+ static const u32 tx_pol[] = { PHY_POL_NORMAL, PHY_POL_INVERT, PHY_POL_NORMAL };
+ static const char * const tx_pol_names[] = { "sgmii", "2500base-x" };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("tx-polarity", tx_pol),
+ PROPERTY_ENTRY_STRING_ARRAY("tx-polarity-names", tx_pol_names),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_tx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: tx-polarity has 1 value and tx-polarity-names does not exist */
+static void phy_test_tx_polarity_single_value_no_names(struct kunit *test)
+{
+ static const u32 tx_pol[] = { PHY_POL_INVERT };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("tx-polarity", tx_pol),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_tx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, val, PHY_POL_INVERT);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: tx-polarity-names has more values than tx-polarity */
+static void phy_test_tx_polarity_more_names_than_values(struct kunit *test)
+{
+ static const u32 tx_pol[] = { PHY_POL_NORMAL, PHY_POL_INVERT };
+ static const char * const tx_pol_names[] = { "sgmii", "2500base-x", "1000base-x" };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("tx-polarity", tx_pol),
+ PROPERTY_ENTRY_STRING_ARRAY("tx-polarity-names", tx_pol_names),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_tx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: tx-polarity and tx-polarity-names have same length, find the name */
+static void phy_test_tx_polarity_find_by_name(struct kunit *test)
+{
+ static const u32 tx_pol[] = { PHY_POL_NORMAL, PHY_POL_INVERT, PHY_POL_NORMAL };
+ static const char * const tx_pol_names[] = { "sgmii", "2500base-x", "1000base-x" };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("tx-polarity", tx_pol),
+ PROPERTY_ENTRY_STRING_ARRAY("tx-polarity-names", tx_pol_names),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_tx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, val, PHY_POL_NORMAL);
+
+ ret = phy_get_manual_tx_polarity(node, "2500base-x", &val);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, val, PHY_POL_INVERT);
+
+ ret = phy_get_manual_tx_polarity(node, "1000base-x", &val);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, val, PHY_POL_NORMAL);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: same length, name not found, no "default" - error */
+static void phy_test_tx_polarity_name_not_found_no_default(struct kunit *test)
+{
+ static const u32 tx_pol[] = { PHY_POL_NORMAL, PHY_POL_INVERT };
+ static const char * const tx_pol_names[] = { "2500base-x", "1000base-x" };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("tx-polarity", tx_pol),
+ PROPERTY_ENTRY_STRING_ARRAY("tx-polarity-names", tx_pol_names),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_tx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: same length, name not found, but "default" exists */
+static void phy_test_tx_polarity_name_not_found_with_default(struct kunit *test)
+{
+ static const u32 tx_pol[] = { PHY_POL_NORMAL, PHY_POL_INVERT };
+ static const char * const tx_pol_names[] = { "2500base-x", "default" };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("tx-polarity", tx_pol),
+ PROPERTY_ENTRY_STRING_ARRAY("tx-polarity-names", tx_pol_names),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_tx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, 0);
+ KUNIT_EXPECT_EQ(test, val, PHY_POL_INVERT);
+
+ fwnode_remove_software_node(node);
+}
+
+/* Test: polarity found but value is unsupported (AUTO for TX) */
+static void phy_test_tx_polarity_unsupported_value(struct kunit *test)
+{
+ static const u32 tx_pol[] = { PHY_POL_AUTO };
+ static const char * const tx_pol_names[] = { "sgmii" };
+ static const struct property_entry entries[] = {
+ PROPERTY_ENTRY_U32_ARRAY("tx-polarity", tx_pol),
+ PROPERTY_ENTRY_STRING_ARRAY("tx-polarity-names", tx_pol_names),
+ {}
+ };
+ struct fwnode_handle *node;
+ unsigned int val;
+ int ret;
+
+ node = fwnode_create_software_node(entries, NULL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, node);
+
+ ret = phy_get_manual_tx_polarity(node, "sgmii", &val);
+ KUNIT_EXPECT_EQ(test, ret, -EOPNOTSUPP);
+
+ fwnode_remove_software_node(node);
+}
+
+static struct kunit_case phy_common_props_test_cases[] = {
+ KUNIT_CASE(phy_test_rx_polarity_is_missing),
+ KUNIT_CASE(phy_test_rx_polarity_more_values_than_names),
+ KUNIT_CASE(phy_test_rx_polarity_single_value_no_names),
+ KUNIT_CASE(phy_test_rx_polarity_more_names_than_values),
+ KUNIT_CASE(phy_test_rx_polarity_find_by_name),
+ KUNIT_CASE(phy_test_rx_polarity_name_not_found_no_default),
+ KUNIT_CASE(phy_test_rx_polarity_name_not_found_with_default),
+ KUNIT_CASE(phy_test_rx_polarity_unsupported_value),
+ KUNIT_CASE(phy_test_tx_polarity_is_missing),
+ KUNIT_CASE(phy_test_tx_polarity_more_values_than_names),
+ KUNIT_CASE(phy_test_tx_polarity_single_value_no_names),
+ KUNIT_CASE(phy_test_tx_polarity_more_names_than_values),
+ KUNIT_CASE(phy_test_tx_polarity_find_by_name),
+ KUNIT_CASE(phy_test_tx_polarity_name_not_found_no_default),
+ KUNIT_CASE(phy_test_tx_polarity_name_not_found_with_default),
+ KUNIT_CASE(phy_test_tx_polarity_unsupported_value),
+ {}
+};
+
+static struct kunit_suite phy_common_props_test_suite = {
+ .name = "phy-common-props",
+ .test_cases = phy_common_props_test_cases,
+};
+
+kunit_test_suite(phy_common_props_test_suite);
+
+MODULE_DESCRIPTION("Test module for PHY common properties API");
+MODULE_AUTHOR("Vladimir Oltean <vladimir.oltean@nxp.com>");
+MODULE_LICENSE("GPL");
--- /dev/null
+++ b/drivers/phy/phy-common-props.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * phy-common-props.c -- Common PHY properties
+ *
+ * Copyright 2025-2026 NXP
+ */
+#include <linux/export.h>
+#include <linux/fwnode.h>
+#include <linux/phy/phy-common-props.h>
+#include <linux/printk.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+/**
+ * fwnode_get_u32_prop_for_name - Find u32 property by name, or default value
+ * @fwnode: Pointer to firmware node, or NULL to use @default_val
+ * @name: Property name used as lookup key in @names_title (must not be NULL)
+ * @props_title: Name of u32 array property holding values
+ * @names_title: Name of string array property holding lookup keys
+ * @default_val: Default value if @fwnode is NULL or @props_title is empty
+ * @val: Pointer to store the returned value
+ *
+ * This function retrieves a u32 value from @props_title based on a name lookup
+ * in @names_title. The value stored in @val is determined as follows:
+ *
+ * - If @fwnode is NULL or @props_title is empty: @default_val is used
+ * - If @props_title has exactly one element and @names_title is empty:
+ * that element is used
+ * - Otherwise: @val is set to the element at the same index where @name is
+ * found in @names_title.
+ * - If @name is not found, the function looks for a "default" entry in
+ * @names_title and uses the corresponding value from @props_title
+ *
+ * When both @props_title and @names_title are present, they must have the
+ * same number of elements (except when @props_title has exactly one element).
+ *
+ * Return: zero on success, negative error on failure.
+ */
+static int fwnode_get_u32_prop_for_name(struct fwnode_handle *fwnode,
+ const char *name,
+ const char *props_title,
+ const char *names_title,
+ unsigned int default_val,
+ unsigned int *val)
+{
+ int err, n_props, n_names, idx;
+ u32 *props;
+
+ if (!name) {
+ pr_err("Lookup key inside \"%s\" is mandatory\n", names_title);
+ return -EINVAL;
+ }
+
+ n_props = fwnode_property_count_u32(fwnode, props_title);
+ if (n_props <= 0) {
+ /* fwnode is NULL, or is missing requested property */
+ *val = default_val;
+ return 0;
+ }
+
+ n_names = fwnode_property_string_array_count(fwnode, names_title);
+ if (n_names >= 0 && n_props != n_names) {
+ pr_err("%pfw mismatch between \"%s\" and \"%s\" property count (%d vs %d)\n",
+ fwnode, props_title, names_title, n_props, n_names);
+ return -EINVAL;
+ }
+
+ idx = fwnode_property_match_string(fwnode, names_title, name);
+ if (idx < 0)
+ idx = fwnode_property_match_string(fwnode, names_title, "default");
+ /*
+ * If the mode name is missing, it can only mean the specified property
+ * is the default one for all modes, so reject any other property count
+ * than 1.
+ */
+ if (idx < 0 && n_props != 1) {
+ pr_err("%pfw \"%s \" property has %d elements, but cannot find \"%s\" in \"%s\" and there is no default value\n",
+ fwnode, props_title, n_props, name, names_title);
+ return -EINVAL;
+ }
+
+ if (n_props == 1) {
+ err = fwnode_property_read_u32(fwnode, props_title, val);
+ if (err)
+ return err;
+
+ return 0;
+ }
+
+ /* We implicitly know idx >= 0 here */
+ props = kcalloc(n_props, sizeof(*props), GFP_KERNEL);
+ if (!props)
+ return -ENOMEM;
+
+ err = fwnode_property_read_u32_array(fwnode, props_title, props, n_props);
+ if (err >= 0)
+ *val = props[idx];
+
+ kfree(props);
+
+ return err;
+}
+
+static int phy_get_polarity_for_mode(struct fwnode_handle *fwnode,
+ const char *mode_name,
+ unsigned int supported,
+ unsigned int default_val,
+ const char *polarity_prop,
+ const char *names_prop,
+ unsigned int *val)
+{
+ int err;
+
+ err = fwnode_get_u32_prop_for_name(fwnode, mode_name, polarity_prop,
+ names_prop, default_val, val);
+ if (err)
+ return err;
+
+ if (!(supported & BIT(*val))) {
+ pr_err("%d is not a supported value for %pfw '%s' element '%s'\n",
+ *val, fwnode, polarity_prop, mode_name);
+ err = -EOPNOTSUPP;
+ }
+
+ return err;
+}
+
+/**
+ * phy_get_rx_polarity - Get RX polarity for PHY differential lane
+ * @fwnode: Pointer to the PHY's firmware node.
+ * @mode_name: The name of the PHY mode to look up.
+ * @supported: Bit mask of PHY_POL_NORMAL, PHY_POL_INVERT and PHY_POL_AUTO
+ * @default_val: Default polarity value if property is missing
+ * @val: Pointer to returned polarity.
+ *
+ * Return: zero on success, negative error on failure.
+ */
+int __must_check phy_get_rx_polarity(struct fwnode_handle *fwnode,
+ const char *mode_name,
+ unsigned int supported,
+ unsigned int default_val,
+ unsigned int *val)
+{
+ return phy_get_polarity_for_mode(fwnode, mode_name, supported,
+ default_val, "rx-polarity",
+ "rx-polarity-names", val);
+}
+EXPORT_SYMBOL_GPL(phy_get_rx_polarity);
+
+/**
+ * phy_get_tx_polarity - Get TX polarity for PHY differential lane
+ * @fwnode: Pointer to the PHY's firmware node.
+ * @mode_name: The name of the PHY mode to look up.
+ * @supported: Bit mask of PHY_POL_NORMAL and PHY_POL_INVERT
+ * @default_val: Default polarity value if property is missing
+ * @val: Pointer to returned polarity.
+ *
+ * Return: zero on success, negative error on failure.
+ */
+int __must_check phy_get_tx_polarity(struct fwnode_handle *fwnode,
+ const char *mode_name, unsigned int supported,
+ unsigned int default_val, unsigned int *val)
+{
+ return phy_get_polarity_for_mode(fwnode, mode_name, supported,
+ default_val, "tx-polarity",
+ "tx-polarity-names", val);
+}
+EXPORT_SYMBOL_GPL(phy_get_tx_polarity);
+
+/**
+ * phy_get_manual_rx_polarity - Get manual RX polarity for PHY differential lane
+ * @fwnode: Pointer to the PHY's firmware node.
+ * @mode_name: The name of the PHY mode to look up.
+ * @val: Pointer to returned polarity.
+ *
+ * Helper for PHYs which do not support protocols with automatic RX polarity
+ * detection and correction.
+ *
+ * Return: zero on success, negative error on failure.
+ */
+int __must_check phy_get_manual_rx_polarity(struct fwnode_handle *fwnode,
+ const char *mode_name,
+ unsigned int *val)
+{
+ return phy_get_rx_polarity(fwnode, mode_name,
+ BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
+ PHY_POL_NORMAL, val);
+}
+EXPORT_SYMBOL_GPL(phy_get_manual_rx_polarity);
+
+/**
+ * phy_get_manual_tx_polarity - Get manual TX polarity for PHY differential lane
+ * @fwnode: Pointer to the PHY's firmware node.
+ * @mode_name: The name of the PHY mode to look up.
+ * @val: Pointer to returned polarity.
+ *
+ * Helper for PHYs without any custom default value for the TX polarity.
+ *
+ * Return: zero on success, negative error on failure.
+ */
+int __must_check phy_get_manual_tx_polarity(struct fwnode_handle *fwnode,
+ const char *mode_name,
+ unsigned int *val)
+{
+ return phy_get_tx_polarity(fwnode, mode_name,
+ BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
+ PHY_POL_NORMAL, val);
+}
+EXPORT_SYMBOL_GPL(phy_get_manual_tx_polarity);
--- /dev/null
+++ b/include/linux/phy/phy-common-props.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * phy-common-props.h -- Common properties for generic PHYs
+ *
+ * Copyright 2025 NXP
+ */
+
+#ifndef __PHY_COMMON_PROPS_H
+#define __PHY_COMMON_PROPS_H
+
+#include <dt-bindings/phy/phy.h>
+
+struct fwnode_handle;
+
+int __must_check phy_get_rx_polarity(struct fwnode_handle *fwnode,
+ const char *mode_name,
+ unsigned int supported,
+ unsigned int default_val,
+ unsigned int *val);
+int __must_check phy_get_tx_polarity(struct fwnode_handle *fwnode,
+ const char *mode_name,
+ unsigned int supported,
+ unsigned int default_val,
+ unsigned int *val);
+int __must_check phy_get_manual_rx_polarity(struct fwnode_handle *fwnode,
+ const char *mode_name,
+ unsigned int *val);
+int __must_check phy_get_manual_tx_polarity(struct fwnode_handle *fwnode,
+ const char *mode_name,
+ unsigned int *val);
+
+#endif /* __PHY_COMMON_PROPS_H */
@@ -0,0 +1,62 @@
From 44f62aa1b1209232cedcfb39097fc1bfbe75bbc7 Mon Sep 17 00:00:00 2001
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: Mon, 19 Jan 2026 11:12:16 +0200
Subject: [PATCH 1/2] dt-bindings: net: airoha,en8811h: deprecate
"airoha,pnswap-rx" and "airoha,pnswap-tx"
Reference the common PHY properties, and update the example to use them.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Link: https://patch.msgid.link/20260119091220.1493761-2-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
.../devicetree/bindings/net/airoha,en8811h.yaml | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
--- a/Documentation/devicetree/bindings/net/airoha,en8811h.yaml
+++ b/Documentation/devicetree/bindings/net/airoha,en8811h.yaml
@@ -16,6 +16,7 @@ description:
allOf:
- $ref: ethernet-phy.yaml#
+ - $ref: /schemas/phy/phy-common-props.yaml#
properties:
compatible:
@@ -30,12 +31,18 @@ properties:
description:
Reverse rx polarity of the SERDES. This is the receiving
side of the lines from the MAC towards the EN881H.
+ This property is deprecated, for details please refer to
+ Documentation/devicetree/bindings/phy/phy-common-props.yaml
+ deprecated: true
airoha,pnswap-tx:
type: boolean
description:
Reverse tx polarity of SERDES. This is the transmitting
side of the lines from EN8811H towards the MAC.
+ This property is deprecated, for details please refer to
+ Documentation/devicetree/bindings/phy/phy-common-props.yaml
+ deprecated: true
required:
- reg
@@ -44,6 +51,8 @@ unevaluatedProperties: false
examples:
- |
+ #include <dt-bindings/phy/phy.h>
+
mdio {
#address-cells = <1>;
#size-cells = <0>;
@@ -51,6 +60,6 @@ examples:
ethernet-phy@1 {
compatible = "ethernet-phy-id03a2.a411";
reg = <1>;
- airoha,pnswap-rx;
+ rx-polarity = <PHY_POL_INVERT>;
};
};
@@ -0,0 +1,108 @@
From 66d8a334b57e64e43810623b3d88f0ce9745270b Mon Sep 17 00:00:00 2001
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: Mon, 19 Jan 2026 11:12:17 +0200
Subject: [PATCH 2/2] net: phy: air_en8811h: deprecate "airoha,pnswap-rx" and
"airoha,pnswap-tx"
Prefer the new "rx-polarity" and "tx-polarity" properties, and use the
vendor specific ones as fallback if the standard description doesn't
exist.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Link: https://patch.msgid.link/20260119091220.1493761-3-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/phy/Kconfig | 1 +
drivers/net/phy/air_en8811h.c | 53 +++++++++++++++++++++++++----------
2 files changed, 39 insertions(+), 15 deletions(-)
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -98,6 +98,7 @@ config AS21XXX_PHY
config AIR_EN8811H_PHY
tristate "Airoha EN8811H 2.5 Gigabit PHY"
+ select PHY_COMMON_PROPS
help
Currently supports the Airoha EN8811H PHY.
--- a/drivers/net/phy/air_en8811h.c
+++ b/drivers/net/phy/air_en8811h.c
@@ -14,6 +14,7 @@
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/phy.h>
+#include <linux/phy/phy-common-props.h>
#include <linux/firmware.h>
#include <linux/property.h>
#include <linux/wordpart.h>
@@ -966,11 +967,45 @@ static int en8811h_probe(struct phy_devi
return 0;
}
+static int en8811h_config_serdes_polarity(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ unsigned int pol, default_pol;
+ u32 pbus_value = 0;
+ int ret;
+
+ default_pol = PHY_POL_NORMAL;
+ if (device_property_read_bool(dev, "airoha,pnswap-rx"))
+ default_pol = PHY_POL_INVERT;
+
+ ret = phy_get_rx_polarity(dev_fwnode(dev), phy_modes(phydev->interface),
+ BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
+ default_pol, &pol);
+ if (ret)
+ return ret;
+ if (pol == PHY_POL_INVERT)
+ pbus_value |= EN8811H_POLARITY_RX_REVERSE;
+
+ default_pol = PHY_POL_NORMAL;
+ if (device_property_read_bool(dev, "airoha,pnswap-tx"))
+ default_pol = PHY_POL_INVERT;
+
+ ret = phy_get_tx_polarity(dev_fwnode(dev), phy_modes(phydev->interface),
+ BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
+ default_pol, &pol);
+ if (ret)
+ return ret;
+ if (pol == PHY_POL_NORMAL)
+ pbus_value |= EN8811H_POLARITY_TX_NORMAL;
+
+ return air_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
+ EN8811H_POLARITY_RX_REVERSE |
+ EN8811H_POLARITY_TX_NORMAL, pbus_value);
+}
+
static int en8811h_config_init(struct phy_device *phydev)
{
struct en8811h_priv *priv = phydev->priv;
- struct device *dev = &phydev->mdio.dev;
- u32 pbus_value;
int ret;
/* If restart happened in .probe(), no need to restart now */
@@ -1003,19 +1038,7 @@ static int en8811h_config_init(struct ph
if (ret < 0)
return ret;
- /* Serdes polarity */
- pbus_value = 0;
- if (device_property_read_bool(dev, "airoha,pnswap-rx"))
- pbus_value |= EN8811H_POLARITY_RX_REVERSE;
- else
- pbus_value &= ~EN8811H_POLARITY_RX_REVERSE;
- if (device_property_read_bool(dev, "airoha,pnswap-tx"))
- pbus_value &= ~EN8811H_POLARITY_TX_NORMAL;
- else
- pbus_value |= EN8811H_POLARITY_TX_NORMAL;
- ret = air_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
- EN8811H_POLARITY_RX_REVERSE |
- EN8811H_POLARITY_TX_NORMAL, pbus_value);
+ ret = en8811h_config_serdes_polarity(phydev);
if (ret < 0)
return ret;
@@ -0,0 +1,40 @@
From 9f841922ebd0a34c78fc1984dc9abcb346704c58 Mon Sep 17 00:00:00 2001
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: Mon, 19 Jan 2026 11:12:18 +0200
Subject: [PATCH 1/3] dt-bindings: net: pcs: mediatek,sgmiisys: deprecate
"mediatek,pnswap"
Reference the common PHY properties, and update the example to use them.
Note that a PCS subnode exists, and it seems a better container of the
polarity description than the SGMIISYS node that hosts "mediatek,pnswap".
So use that.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Link: https://patch.msgid.link/20260119091220.1493761-4-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
.../devicetree/bindings/net/pcs/mediatek,sgmiisys.yaml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
--- a/Documentation/devicetree/bindings/net/pcs/mediatek,sgmiisys.yaml
+++ b/Documentation/devicetree/bindings/net/pcs/mediatek,sgmiisys.yaml
@@ -39,12 +39,17 @@ properties:
const: 1
mediatek,pnswap:
- description: Invert polarity of the SGMII data lanes
+ description:
+ Invert polarity of the SGMII data lanes.
+ This property is deprecated, for details please refer to
+ Documentation/devicetree/bindings/phy/phy-common-props.yaml.
type: boolean
+ deprecated: true
pcs:
type: object
description: MediaTek LynxI HSGMII PCS
+ $ref: /schemas/phy/phy-common-props.yaml#
properties:
compatible:
const: mediatek,mt7988-sgmii
@@ -0,0 +1,160 @@
From bde1ae2d52ab3599e1c7ca68a90af8407d20f91d Mon Sep 17 00:00:00 2001
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: Mon, 19 Jan 2026 11:12:19 +0200
Subject: [PATCH 2/3] net: pcs: pcs-mtk-lynxi: pass SGMIISYS OF node to PCS
The Mediatek LynxI PCS is used from the MT7530 DSA driver (where it does
not have an OF presence) and from mtk_eth_soc, where it does
(Documentation/devicetree/bindings/net/pcs/mediatek,sgmiisys.yaml
informs of a combined clock provider + SGMII PCS "SGMIISYS" syscon
block).
Currently, mtk_eth_soc parses the SGMIISYS OF node for the
"mediatek,pnswap" property and sets a bit in the "flags" argument of
mtk_pcs_lynxi_create() if set.
I'd like to deprecate "mediatek,pnswap" in favour of a property which
takes the current phy-mode into consideration. But this is only known at
mtk_pcs_lynxi_config() time, and not known at mtk_pcs_lynxi_create(),
when the SGMIISYS OF node is parsed.
To achieve that, we must pass the OF node of the PCS, if it exists, to
mtk_pcs_lynxi_create(), and let the PCS take a reference on it and
handle property parsing whenever it wants.
Use the fwnode API which is more general than OF (in case we ever need
to describe the PCS using some other format). This API should be NULL
tolerant, so add no particular tests for the mt7530 case.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Link: https://patch.msgid.link/20260119091220.1493761-5-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mt7530-mdio.c | 4 ++--
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 19 ++++++++-----------
drivers/net/pcs/pcs-mtk-lynxi.c | 15 ++++++++++-----
include/linux/pcs/pcs-mtk-lynxi.h | 5 ++---
4 files changed, 22 insertions(+), 21 deletions(-)
--- a/drivers/net/dsa/mt7530-mdio.c
+++ b/drivers/net/dsa/mt7530-mdio.c
@@ -113,8 +113,8 @@ mt7531_create_sgmii(struct mt7530_priv *
ret = PTR_ERR(regmap);
break;
}
- pcs = mtk_pcs_lynxi_create(priv->dev, regmap,
- MT7531_PHYA_CTRL_SIGNAL3, 0);
+ pcs = mtk_pcs_lynxi_create(priv->dev, NULL, regmap,
+ MT7531_PHYA_CTRL_SIGNAL3);
if (!pcs) {
ret = -ENXIO;
break;
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -5000,7 +5000,6 @@ static int mtk_sgmii_init(struct mtk_eth
{
struct device_node *np;
struct regmap *regmap;
- u32 flags;
int i;
for (i = 0; i < MTK_MAX_DEVS; i++) {
@@ -5009,18 +5008,16 @@ static int mtk_sgmii_init(struct mtk_eth
break;
regmap = syscon_node_to_regmap(np);
- flags = 0;
- if (of_property_read_bool(np, "mediatek,pnswap"))
- flags |= MTK_SGMII_FLAG_PN_SWAP;
-
- of_node_put(np);
-
- if (IS_ERR(regmap))
+ if (IS_ERR(regmap)) {
+ of_node_put(np);
return PTR_ERR(regmap);
+ }
- eth->sgmii_pcs[i] = mtk_pcs_lynxi_create(eth->dev, regmap,
- eth->soc->ana_rgc3,
- flags);
+ eth->sgmii_pcs[i] = mtk_pcs_lynxi_create(eth->dev,
+ of_fwnode_handle(np),
+ regmap,
+ eth->soc->ana_rgc3);
+ of_node_put(np);
}
return 0;
--- a/drivers/net/pcs/pcs-mtk-lynxi.c
+++ b/drivers/net/pcs/pcs-mtk-lynxi.c
@@ -81,6 +81,7 @@ struct mtk_pcs_lynxi {
phy_interface_t interface;
struct phylink_pcs pcs;
u32 flags;
+ struct fwnode_handle *fwnode;
};
static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs)
@@ -166,7 +167,7 @@ static int mtk_pcs_lynxi_config(struct p
regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
SGMII_SW_RESET);
- if (mpcs->flags & MTK_SGMII_FLAG_PN_SWAP)
+ if (fwnode_property_read_bool(mpcs->fwnode, "mediatek,pnswap"))
regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
SGMII_PN_SWAP_MASK,
SGMII_PN_SWAP_TX_RX);
@@ -266,8 +267,8 @@ static const struct phylink_pcs_ops mtk_
};
struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
- struct regmap *regmap, u32 ana_rgc3,
- u32 flags)
+ struct fwnode_handle *fwnode,
+ struct regmap *regmap, u32 ana_rgc3)
{
struct mtk_pcs_lynxi *mpcs;
u32 id, ver;
@@ -301,10 +302,10 @@ struct phylink_pcs *mtk_pcs_lynxi_create
mpcs->ana_rgc3 = ana_rgc3;
mpcs->regmap = regmap;
- mpcs->flags = flags;
mpcs->pcs.ops = &mtk_pcs_lynxi_ops;
mpcs->pcs.poll = true;
mpcs->interface = PHY_INTERFACE_MODE_NA;
+ mpcs->fwnode = fwnode_handle_get(fwnode);
__set_bit(PHY_INTERFACE_MODE_SGMII, mpcs->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_1000BASEX, mpcs->pcs.supported_interfaces);
@@ -316,10 +317,14 @@ EXPORT_SYMBOL(mtk_pcs_lynxi_create);
void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs)
{
+ struct mtk_pcs_lynxi *mpcs;
+
if (!pcs)
return;
- kfree(pcs_to_mtk_pcs_lynxi(pcs));
+ mpcs = pcs_to_mtk_pcs_lynxi(pcs);
+ fwnode_handle_put(mpcs->fwnode);
+ kfree(mpcs);
}
EXPORT_SYMBOL(mtk_pcs_lynxi_destroy);
--- a/include/linux/pcs/pcs-mtk-lynxi.h
+++ b/include/linux/pcs/pcs-mtk-lynxi.h
@@ -5,9 +5,8 @@
#include <linux/phylink.h>
#include <linux/regmap.h>
-#define MTK_SGMII_FLAG_PN_SWAP BIT(0)
struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
- struct regmap *regmap,
- u32 ana_rgc3, u32 flags);
+ struct fwnode_handle *fwnode,
+ struct regmap *regmap, u32 ana_rgc3);
void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs);
#endif
@@ -0,0 +1,123 @@
From 8871389da15165198c3407584d40e7295bceaca5 Mon Sep 17 00:00:00 2001
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: Mon, 19 Jan 2026 11:12:20 +0200
Subject: [PATCH 3/3] net: pcs: pcs-mtk-lynxi: deprecate "mediatek,pnswap"
Prefer the new "rx-polarity" and "tx-polarity" properties, which in this
case have the advantage that polarity inversion can be specified per
direction (and per protocol, although this isn't useful here).
We use the vendor specific ones as fallback if the standard description
doesn't exist.
Daniel, referring to the Mediatek SDK, clarifies that the combined
SGMII_PN_SWAP_TX_RX register field should be split like this: bit 0 is
TX and bit 1 is RX:
https://lore.kernel.org/linux-phy/aSW--slbJWpXK0nv@makrotopia.org/
Suggested-by: Daniel Golle <daniel@makrotopia.org>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Link: https://patch.msgid.link/20260119091220.1493761-6-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/pcs/Kconfig | 1 +
drivers/net/pcs/pcs-mtk-lynxi.c | 50 +++++++++++++++++++++++++++++----
2 files changed, 45 insertions(+), 6 deletions(-)
--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig
@@ -20,6 +20,7 @@ config PCS_LYNX
config PCS_MTK_LYNXI
tristate
+ select PHY_COMMON_PROPS
select REGMAP
help
This module provides helpers to phylink for managing the LynxI PCS
--- a/drivers/net/pcs/pcs-mtk-lynxi.c
+++ b/drivers/net/pcs/pcs-mtk-lynxi.c
@@ -11,6 +11,7 @@
#include <linux/mdio.h>
#include <linux/of.h>
#include <linux/pcs/pcs-mtk-lynxi.h>
+#include <linux/phy/phy-common-props.h>
#include <linux/phylink.h>
#include <linux/regmap.h>
@@ -62,8 +63,9 @@
/* Register to QPHY wrapper control */
#define SGMSYS_QPHY_WRAP_CTRL 0xec
-#define SGMII_PN_SWAP_MASK GENMASK(1, 0)
-#define SGMII_PN_SWAP_TX_RX (BIT(0) | BIT(1))
+#define SGMII_PN_SWAP_RX BIT(1)
+#define SGMII_PN_SWAP_TX BIT(0)
+
/* struct mtk_pcs_lynxi - This structure holds each sgmii regmap andassociated
* data
@@ -119,6 +121,42 @@ static void mtk_pcs_lynxi_get_state(stru
FIELD_GET(SGMII_LPA, adv));
}
+static int mtk_pcs_config_polarity(struct mtk_pcs_lynxi *mpcs,
+ phy_interface_t interface)
+{
+ struct fwnode_handle *fwnode = mpcs->fwnode, *pcs_fwnode;
+ unsigned int pol, default_pol = PHY_POL_NORMAL;
+ unsigned int val = 0;
+ int ret;
+
+ if (fwnode_property_read_bool(fwnode, "mediatek,pnswap"))
+ default_pol = PHY_POL_INVERT;
+
+ pcs_fwnode = fwnode_get_named_child_node(fwnode, "pcs");
+
+ ret = phy_get_rx_polarity(pcs_fwnode, phy_modes(interface),
+ BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
+ default_pol, &pol);
+ if (ret) {
+ fwnode_handle_put(pcs_fwnode);
+ return ret;
+ }
+ if (pol == PHY_POL_INVERT)
+ val |= SGMII_PN_SWAP_RX;
+
+ ret = phy_get_tx_polarity(pcs_fwnode, phy_modes(interface),
+ BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
+ default_pol, &pol);
+ fwnode_handle_put(pcs_fwnode);
+ if (ret)
+ return ret;
+ if (pol == PHY_POL_INVERT)
+ val |= SGMII_PN_SWAP_TX;
+
+ return regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
+ SGMII_PN_SWAP_RX | SGMII_PN_SWAP_TX, val);
+}
+
static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
@@ -128,6 +166,7 @@ static int mtk_pcs_lynxi_config(struct p
bool mode_changed = false, changed;
unsigned int rgc3, sgm_mode, bmcr;
int advertise, link_timer;
+ int ret;
advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
advertising);
@@ -167,10 +206,9 @@ static int mtk_pcs_lynxi_config(struct p
regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
SGMII_SW_RESET);
- if (fwnode_property_read_bool(mpcs->fwnode, "mediatek,pnswap"))
- regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
- SGMII_PN_SWAP_MASK,
- SGMII_PN_SWAP_TX_RX);
+ ret = mtk_pcs_config_polarity(mpcs, interface);
+ if (ret)
+ return ret;
if (interface == PHY_INTERFACE_MODE_2500BASEX)
rgc3 = SGMII_PHY_SPEED_3_125G;
@@ -0,0 +1,52 @@
From 3ddcd24b4d8454b2b9b2d013a0d61986ae8bbbe7 Mon Sep 17 00:00:00 2001
From: Vladimir Oltean <vladimir.oltean@nxp.com>
Date: Fri, 23 Jan 2026 13:06:00 +0200
Subject: [PATCH] phy: enter drivers/phy/Makefile even without
CONFIG_GENERIC_PHY
Kconfig option CONFIG_PHY_COMMON_PROPS, which builds
drivers/phy/phy-common-props.c, was intended to be selectable
independently of CONFIG_GENERIC_PHY. Yet it lives in drivers/phy/, which
is entered by the Makefile only if CONFIG_GENERIC_PHY is set.
Allow the Makefile to enter one level deeper, but stop at drivers/phy/
if CONFIG_GENERIC_PHY is unselected (i.e. do not enter vendor folders).
The other stuff from drivers/phy/Makefile except for CONFIG_PHY_COMMON_PROPS,
like CONFIG_PHY_NXP_PTN3222, all depends on CONFIG_GENERIC_PHY.
Fixes: e7556b59ba65 ("phy: add phy_get_rx_polarity() and phy_get_tx_polarity()")
Closes: https://lore.kernel.org/lkml/43ea0202-891d-4582-980b-5cb557b41114@linux.ibm.com/
Reported-by: Venkat Rao Bagalkote <venkat88@linux.ibm.com>
Debugged-by: Christophe Leroy (CS GROUP) <chleroy@kernel.org>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Christophe Leroy (CS GROUP) <chleroy@kernel.org>
Tested-by: Venkat Rao Bagalkote <venkat88@linux.ibm.com>
Link: https://patch.msgid.link/20260123110600.3118561-1-vladimir.oltean@nxp.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
---
drivers/Makefile | 2 +-
drivers/phy/Makefile | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -10,7 +10,7 @@ obj-y += cache/
obj-y += irqchip/
obj-y += bus/
-obj-$(CONFIG_GENERIC_PHY) += phy/
+obj-y += phy/
# GPIO must come after pinctrl as gpios may need to mux pins etc
obj-$(CONFIG_PINCTRL) += pinctrl/
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -15,7 +15,7 @@ obj-$(CONFIG_PHY_SNPS_EUSB2) += phy-snp
obj-$(CONFIG_USB_LGM_PHY) += phy-lgm-usb.o
obj-$(CONFIG_PHY_AIROHA_PCIE) += phy-airoha-pcie.o
obj-$(CONFIG_PHY_NXP_PTN3222) += phy-nxp-ptn3222.o
-obj-y += allwinner/ \
+obj-$(CONFIG_GENERIC_PHY) += allwinner/ \
amlogic/ \
broadcom/ \
cadence/ \
+1
View File
@@ -1863,6 +1863,7 @@ CONFIG_DVB_MAX_ADAPTERS=16
# CONFIG_DWMAC_LOONGSON is not set
# CONFIG_DWMAC_LPC18XX is not set
# CONFIG_DWMAC_MESON is not set
# CONFIG_DWMAC_MOTORCOMM is not set
# CONFIG_DWMAC_ROCKCHIP is not set
# CONFIG_DWMAC_SOCFPGA is not set
# CONFIG_DWMAC_STI is not set
+1
View File
@@ -1949,6 +1949,7 @@ CONFIG_DVB_MAX_ADAPTERS=16
# CONFIG_DWMAC_LOONGSON is not set
# CONFIG_DWMAC_LPC18XX is not set
# CONFIG_DWMAC_MESON is not set
# CONFIG_DWMAC_MOTORCOMM is not set
# CONFIG_DWMAC_ROCKCHIP is not set
# CONFIG_DWMAC_SOCFPGA is not set
# CONFIG_DWMAC_STI is not set
+4
View File
@@ -17,6 +17,10 @@ CONFIG_PAHOLE_HAS_LANG_EXCLUDE=.*
CONFIG_PAHOLE_HAS_SPLIT_BTF=.*
CONFIG_PAHOLE_VERSION=.*
CONFIG_PLUGIN_HOSTCC=".*"
CONFIG_RUSTC_HAS_COERCE_POINTEE=.*
CONFIG_RUSTC_HAS_FILE_AS_C_STR=.*
CONFIG_RUSTC_HAS_FILE_WITH_NUL=.*
CONFIG_RUSTC_HAS_SPAN_FILE=.*
CONFIG_RUSTC_HAS_UNNECESSARY_TRANSMUTES=.*
CONFIG_RUSTC_LLVM_VERSION=.*
CONFIG_RUSTC_SUPPORTS_.*=.*
@@ -37,7 +37,7 @@ Signed-off-by: Bo-Cun Chen <bc-bocun.chen@mediatek.com>
.glo_cfg = 0x4604,
.rst_idx = 0x4608,
.delay_irq = 0x460c,
@@ -4176,6 +4179,56 @@ static void mtk_set_mcr_max_rx(struct mt
@@ -4125,6 +4128,56 @@ static void mtk_set_mcr_max_rx(struct mt
mtk_w32(mac->hw, mcr_new, MTK_MAC_MCR(mac->id));
}
@@ -94,7 +94,7 @@ Signed-off-by: Bo-Cun Chen <bc-bocun.chen@mediatek.com>
static void mtk_hw_reset(struct mtk_eth *eth)
{
u32 val;
@@ -4655,6 +4708,8 @@ static void mtk_pending_work(struct work
@@ -4604,6 +4657,8 @@ static void mtk_pending_work(struct work
rtnl_lock();
set_bit(MTK_RESETTING, &eth->state);
@@ -105,7 +105,7 @@ Signed-off-by: Bo-Cun Chen <bc-bocun.chen@mediatek.com>
/* Run again reset preliminary configuration in order to avoid any
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.h
@@ -1193,6 +1193,7 @@ struct mtk_reg_map {
@@ -1192,6 +1192,7 @@ struct mtk_reg_map {
u32 rx_ptr; /* rx base pointer */
u32 rx_cnt_cfg; /* rx max count configuration */
u32 qcrx_ptr; /* rx cpu pointer */
@@ -7,7 +7,7 @@ There is no upstream driver yet. Merge the RTL SDK driver for now.
Signed-off-by: John Crispin <john@phrozen.org>
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -458,6 +458,8 @@ config QSEMI_PHY
@@ -459,6 +459,8 @@ config QSEMI_PHY
source "drivers/net/phy/realtek/Kconfig"
@@ -1,65 +0,0 @@
From 880d1311335120f64447ca9d11933872d734e19a Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Mon, 27 Mar 2023 18:41:54 +0100
Subject: [PATCH] generic: pcs-mtk-lynxi: add hack to use 2500Base-X without AN
Using 2500Base-T SFP modules e.g. on the BananaPi R3 requires manually
disabling auto-negotiation, e.g. using ethtool. While a proper fix
using SFP quirks is being discussed upstream, bring a work-around to
restore user experience to what it was before the switch to the
dedicated SGMII PCS driver.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- a/drivers/net/pcs/pcs-mtk-lynxi.c
+++ b/drivers/net/pcs/pcs-mtk-lynxi.c
@@ -129,15 +129,23 @@ static void mtk_pcs_lynxi_get_state(stru
struct phylink_link_state *state)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
- unsigned int bm, adv;
+ unsigned int bm, bmsr, adv;
/* Read the BMSR and LPA */
regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm);
- regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv);
+ bmsr = FIELD_GET(SGMII_BMSR, bm);
+
+ if (state->interface == PHY_INTERFACE_MODE_2500BASEX) {
+ state->link = !!(bmsr & BMSR_LSTATUS);
+ state->an_complete = !!(bmsr & BMSR_ANEGCOMPLETE);
+ state->speed = SPEED_2500;
+ state->duplex = DUPLEX_FULL;
+
+ return;
+ }
- phylink_mii_c22_pcs_decode_state(state, neg_mode,
- FIELD_GET(SGMII_BMSR, bm),
- FIELD_GET(SGMII_LPA, adv));
+ regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv);
+ phylink_mii_c22_pcs_decode_state(state, neg_mode, bmsr, FIELD_GET(SGMII_LPA, adv));
}
static void mtk_sgmii_reset(struct mtk_pcs_lynxi *mpcs)
@@ -158,7 +166,7 @@ static int mtk_pcs_lynxi_config(struct p
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
bool mode_changed = false, changed;
- unsigned int rgc3, sgm_mode, bmcr;
+ unsigned int rgc3, sgm_mode, bmcr = 0;
int advertise, link_timer;
advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
@@ -181,9 +189,8 @@ static int mtk_pcs_lynxi_config(struct p
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
if (interface == PHY_INTERFACE_MODE_SGMII)
sgm_mode |= SGMII_SPEED_DUPLEX_AN;
- bmcr = BMCR_ANENABLE;
- } else {
- bmcr = 0;
+ if (interface != PHY_INTERFACE_MODE_2500BASEX)
+ bmcr = BMCR_ANENABLE;
}
if (mpcs->interface != interface) {
@@ -1,7 +1,7 @@
From da12469e73282da814163125153f381823e33f20 Mon Sep 17 00:00:00 2001
From 60a23e663e0c607ae4ed871aaa24d257051ad557 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 17:56:35 +0000
Subject: [PATCH 08/26] net: dsa: mxl862xx: store firmware version for feature
Subject: [PATCH 01/19] net: dsa: mxl862xx: store firmware version for feature
gating
Query the firmware version at init (already done in wait_ready),
@@ -20,7 +20,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -286,6 +286,9 @@ static int mxl862xx_wait_ready(struct ds
@@ -257,6 +257,9 @@ static int mxl862xx_wait_ready(struct ds
ver.iv_major, ver.iv_minor,
le16_to_cpu(ver.iv_revision),
le32_to_cpu(ver.iv_build_num));
@@ -40,8 +40,8 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#include <linux/mdio.h>
#include <linux/workqueue.h>
#include <net/dsa.h>
@@ -245,6 +246,38 @@ struct mxl862xx_port {
spinlock_t stats_lock;
@@ -241,6 +242,38 @@ struct mxl862xx_port {
spinlock_t stats_lock; /* protects stats accumulators */
};
+/**
@@ -79,7 +79,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
/* Bit indices for struct mxl862xx_priv::flags */
#define MXL862XX_FLAG_CRC_ERR 0
#define MXL862XX_FLAG_WORK_STOPPED 1
@@ -262,6 +295,8 @@ struct mxl862xx_port {
@@ -258,6 +291,8 @@ struct mxl862xx_port {
* @drop_meter: index of the single shared zero-rate firmware meter
* used to unconditionally drop traffic (used to block
* flooding)
@@ -88,7 +88,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* @ports: per-port state, indexed by switch port number
* @bridges: maps DSA bridge number to firmware bridge ID;
* zero means no firmware bridge allocated for that
@@ -279,6 +314,7 @@ struct mxl862xx_priv {
@@ -275,6 +310,7 @@ struct mxl862xx_priv {
struct work_struct crc_err_work;
unsigned long flags;
u16 drop_meter;
@@ -1,7 +1,7 @@
From f7606470d398e4091e1bc405bf2125dc5fc99919 Mon Sep 17 00:00:00 2001
From cefa0447dc95a4ddd5093f7b8cf35e654870283f Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 21:39:30 +0000
Subject: [PATCH 09/26] net: dsa: mxl862xx: move phylink stubs to
Subject: [PATCH 02/19] net: dsa: mxl862xx: move phylink stubs to
mxl862xx-phylink.c
Move the phylink MAC operations and get_caps callback from mxl862xx.c
@@ -110,7 +110,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#define MXL862XX_API_WRITE(dev, cmd, data) \
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false)
@@ -1597,16 +1598,6 @@ static void mxl862xx_port_teardown(struc
@@ -1424,16 +1425,6 @@ static void mxl862xx_port_teardown(struc
priv->ports[port].setup_done = false;
}
@@ -127,7 +127,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
static int mxl862xx_get_fid(struct dsa_switch *ds, struct dsa_db db)
{
struct mxl862xx_priv *priv = ds->priv;
@@ -2252,33 +2243,6 @@ static const struct dsa_switch_ops mxl86
@@ -2099,33 +2090,6 @@ static const struct dsa_switch_ops mxl86
.get_stats64 = mxl862xx_get_stats64,
};
@@ -1,7 +1,7 @@
From e583eeeb907f0abeef2082162293a5d63b9fd6fa Mon Sep 17 00:00:00 2001
From 3c1d77006daca1df20d612850535bc6050e266ee Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 26 Mar 2026 01:50:00 +0000
Subject: [PATCH 10/26] net: dsa: mxl862xx: move API macros to mxl862xx-host.h
Subject: [PATCH 03/19] net: dsa: mxl862xx: move API macros to mxl862xx-host.h
Move the MXL862XX_API_WRITE, MXL862XX_API_READ and
MXL862XX_API_READ_QUIET convenience macros from mxl862xx.c to
@@ -990,7 +990,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#endif /* __MXL862XX_PHYLINK_H */
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -684,7 +684,7 @@ static int mxl862xx_setup(struct dsa_swi
@@ -622,7 +622,7 @@ static int mxl862xx_setup(struct dsa_swi
int n_user_ports = 0, max_vlans;
int ingress_finals, vid_rules;
struct dsa_port *dp;
@@ -999,7 +999,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
ret = mxl862xx_reset(priv);
if (ret)
@@ -694,6 +694,9 @@ static int mxl862xx_setup(struct dsa_swi
@@ -632,6 +632,9 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
@@ -1011,7 +1011,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* Ingress: only final catchall rules (PVID insertion, 802.1Q
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -247,6 +247,22 @@ struct mxl862xx_port {
@@ -243,6 +243,22 @@ struct mxl862xx_port {
};
/**
@@ -1034,7 +1034,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* union mxl862xx_fw_version - firmware version for comparison and display
* @major: firmware major version
* @minor: firmware minor version
@@ -297,6 +313,8 @@ union mxl862xx_fw_version {
@@ -293,6 +309,8 @@ union mxl862xx_fw_version {
* flooding)
* @fw_version: cached firmware version, populated at probe and
* compared with MXL862XX_FW_VER_MIN()
@@ -1043,7 +1043,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* @ports: per-port state, indexed by switch port number
* @bridges: maps DSA bridge number to firmware bridge ID;
* zero means no firmware bridge allocated for that
@@ -315,6 +333,7 @@ struct mxl862xx_priv {
@@ -311,6 +329,7 @@ struct mxl862xx_priv {
unsigned long flags;
u16 drop_meter;
union mxl862xx_fw_version fw_version;
@@ -1,7 +1,7 @@
From 24d752291784e30d7329bed15744bbbc6a3e2485 Mon Sep 17 00:00:00 2001
From 3659914c43a587a1ca6418867834831aa518ac35 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:14:33 +0000
Subject: [PATCH 12/26] net: dsa: mxl862xx: add SerDes ethtool statistics
Subject: [PATCH 05/19] net: dsa: mxl862xx: add SerDes ethtool statistics
Expose SerDes equalization and signal detect parameters as ethtool
statistics on ports 9-16 (XPCS-backed ports). Uses the XPCS EQ_GET
@@ -239,7 +239,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#endif /* __MXL862XX_PHYLINK_H */
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -1960,6 +1960,8 @@ static void mxl862xx_get_strings(struct
@@ -1775,6 +1775,8 @@ static void mxl862xx_get_strings(struct
for (i = 0; i < ARRAY_SIZE(mxl862xx_mib); i++)
ethtool_puts(&data, mxl862xx_mib[i].name);
@@ -248,7 +248,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
}
static int mxl862xx_get_sset_count(struct dsa_switch *ds, int port, int sset)
@@ -1967,7 +1969,7 @@ static int mxl862xx_get_sset_count(struc
@@ -1782,7 +1784,7 @@ static int mxl862xx_get_sset_count(struc
if (sset != ETH_SS_STATS)
return 0;
@@ -257,7 +257,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
}
static int mxl862xx_read_rmon(struct dsa_switch *ds, int port,
@@ -2003,6 +2005,8 @@ static void mxl862xx_get_ethtool_stats(s
@@ -1818,6 +1820,8 @@ static void mxl862xx_get_ethtool_stats(s
else
*data++ = le64_to_cpu(*(__le64 *)field);
}
@@ -1,7 +1,7 @@
From ee227a5e4c74f599cc1b34578b32214d5873ad2f Mon Sep 17 00:00:00 2001
From ce66c0be462c8500dfc483395e68be4326ebf296 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:15:32 +0000
Subject: [PATCH 13/26] net: dsa: mxl862xx: add SerDes self-test via PRBS and
Subject: [PATCH 06/19] net: dsa: mxl862xx: add SerDes self-test via PRBS and
BERT
Implement the dsa_switch_ops.self_test callback for SerDes ports
@@ -198,9 +198,9 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#endif /* __MXL862XX_PHYLINK_H */
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -2241,6 +2241,7 @@ static const struct dsa_switch_ops mxl86
.get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
@@ -2088,6 +2088,7 @@ static const struct dsa_switch_ops mxl86
.get_pause_stats = mxl862xx_get_pause_stats,
.get_rmon_stats = mxl862xx_get_rmon_stats,
.get_stats64 = mxl862xx_get_stats64,
+ .self_test = mxl862xx_serdes_self_test,
};
@@ -1,14 +1,28 @@
From 43eb3eed250ea4e7e83371fcbf2bfb8d626eade6 Mon Sep 17 00:00:00 2001
From e6defbd42db6e64c2bb203f82b7d7f8c0691a052 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:51:13 +0000
Subject: [PATCH 14/26] net: dsa: mxl862xx: trap link-local frames to the CPU
port
Subject: [PATCH 07/19] net: dsa: mxl862xx: trap link-local and multicast
snooping frames to CPU
Install per-CTP PCE rules on each user port that trap IEEE 802.1D
link-local frames (01:80:c2:00:00:0x) to the CPU port via an
explicit forwarding portmap with cross-state enabled, ensuring the
frames reach the host even when the bridge port is in BLOCKING or
LEARNING state.
link-local frames (01:80:c2:00:00:0x) and IP multicast snooping
frames (IGMP, MLDv1, MLDv2) to the CPU port.
All trap rules share a common action helper,
mxl862xx_fill_cpu_trap_action(), which sets PORTMAP_ALTERNATIVE to
redirect frames to the CPU and enables cross-state forwarding so
that frames reach the host even when the bridge port is in BLOCKING
or LEARNING state.
A dedicated bridge FID (cpu_trap_fid) is allocated during setup with
all flood modes enabled. Each trap rule points the bridge engine at
this FID via bFidEnable so that IGMP and MLD frames are never
silently dropped by the ingress port's private flood policy.
Three multicast snooping rules are installed per port:
offset 2 -- IPv4 IGMP (IP protocol 2, all versions)
offset 3 -- ICMPv6 types 130-132 (MLDv1 query, report, done)
offset 4 -- ICMPv6 type 143 (MLDv2 Listener Report)
Add the PCE rule firmware API structures, command definitions, and
the rule block allocation interface.
@@ -17,8 +31,9 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 684 ++++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 5 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 69 +++
3 files changed, 758 insertions(+)
drivers/net/dsa/mxl862xx/mxl862xx.c | 186 +++++++
drivers/net/dsa/mxl862xx/mxl862xx.h | 8 +
4 files changed, 883 insertions(+)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -736,7 +751,15 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#define MXL862XX_BRIDGE_CONFIGGET (MXL862XX_BRDG_MAGIC + 0x3)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -280,9 +280,11 @@ static int mxl862xx_wait_ready(struct ds
@@ -10,6 +10,7 @@
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/etherdevice.h>
+#include <linux/icmpv6.h>
#include <linux/if_bridge.h>
#include <linux/module.h>
#include <linux/of_device.h>
@@ -251,9 +252,11 @@ static int mxl862xx_wait_ready(struct ds
ver.iv_major, ver.iv_minor,
le16_to_cpu(ver.iv_revision),
le32_to_cpu(ver.iv_build_num));
@@ -748,17 +771,54 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
return 0;
not_ready_yet:
@@ -410,6 +412,68 @@ static int mxl862xx_setup_drop_meter(str
@@ -381,6 +384,158 @@ static int mxl862xx_setup_drop_meter(str
return MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
}
+
+/* Per-CTP offset used for the link-local trap rule. Each port's CTP
+ * flow-table block is pre-allocated by the firmware during init (44
+ * entries per port on a 10-port SKU, of which offset 0 is reserved
+ * for flow-control marking). Offset 1 is the first unused slot.
+/* Per-CTP offsets for protocol trap rules. Each port's CTP flow-table
+ * block is pre-allocated by the firmware during init (44 entries per
+ * port on a 10-port SKU, of which offset 0 is reserved for flow-control
+ * marking). Offsets 1-4 are used for link-local and multicast snooping
+ * traps; all others remain free.
+ */
+#define MXL862XX_LINK_LOCAL_CTP_OFFSET 1
+#define MXL862XX_IGMP_CTP_OFFSET 2
+#define MXL862XX_MLDV1_CTP_OFFSET 3
+#define MXL862XX_MLDV2_CTP_OFFSET 4
+
+/* Fill the action fields of a PCE rule that traps ingress frames to
+ * the CPU port. Used by both the link-local trap and the multicast
+ * snooping traps. The caller must already have set the rule header
+ * (logicalportid, subifidgroup, region) and the pattern fields.
+ *
+ * PORTMAP_ALTERNATIVE redirects the frame to the CPU port but does
+ * not by itself bypass downstream flood gates. In SpTag mode the
+ * ingress port's private FID may have forward_unknown_multicast=false,
+ * which silently drops IGMP/MLD before they reach the CPU.
+ * Setting bFidEnable to cpu_trap_fid (a dedicated bridge with all
+ * flood modes enabled) overrides the FID used by the bridge engine,
+ * so the frame is never classified as blocked unknown MC regardless
+ * of the ingress port's standalone flood policy.
+ *
+ * Cross-state is enabled so trapped frames bypass STP port state.
+ */
+static void mxl862xx_fill_cpu_trap_action(struct dsa_switch *ds, int port,
+ struct mxl862xx_pce_rule *rule)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ int cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
+
+ rule->action.port_map_action =
+ cpu_to_le32(MXL862XX_PCE_ACTION_PORTMAP_ALTERNATIVE);
+ mxl862xx_fw_portmap_set_bit(rule->action.forward_port_map, cpu_port);
+
+ rule->action.cross_state_action =
+ cpu_to_le32(MXL862XX_PCE_ACTION_CROSS_STATE_CROSS);
+
+ rule->action.fid_enable = 1;
+ rule->action.fid = priv->cpu_trap_fid;
+}
+
+/* Install a PCE rule that traps IEEE 802.1D link-local frames
+ * (01:80:c2:00:00:0x) to the CPU port for a single user port,
@@ -778,54 +838,164 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ */
+static int mxl862xx_setup_link_local_trap(struct dsa_switch *ds, int port)
+{
+ DECLARE_BITMAP(portmap, MXL862XX_MAX_BRIDGE_PORTS);
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_pce_rule rule = {};
+ int cpu_port = dp->cpu_dp->index;
+ int i;
+
+ /* Address this port's CTP flow-table block */
+ rule.logicalportid = port;
+ rule.subifidgroup = 0;
+ rule.region = cpu_to_le32(MXL862XX_PCE_RULE_CTP);
+
+ /* Pattern: link-local MAC on this specific ingress port */
+ rule.pattern.index = cpu_to_le16(MXL862XX_LINK_LOCAL_CTP_OFFSET);
+ rule.pattern.enable = 1;
+ rule.pattern.mac_dst_enable = 1;
+ memcpy(rule.pattern.mac_dst, eth_reserved_addr_base, ETH_ALEN);
+ rule.pattern.mac_dst_mask = cpu_to_le16(0x0001);
+
+ /* Action: forward to the CPU port via explicit portmap */
+ rule.action.port_map_action =
+ cpu_to_le32(MXL862XX_PCE_ACTION_PORTMAP_ALTERNATIVE);
+ mxl862xx_fill_cpu_trap_action(ds, port, &rule);
+
+ bitmap_zero(portmap, MXL862XX_MAX_BRIDGE_PORTS);
+ __set_bit(cpu_port, portmap);
+ for (i = 0; i < ARRAY_SIZE(rule.action.forward_port_map); i++)
+ rule.action.forward_port_map[i] =
+ cpu_to_le16(bitmap_read(portmap, i * 16, 16));
+ return MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule);
+}
+
+ /* Bypass STP port state */
+ rule.action.cross_state_action =
+ cpu_to_le32(MXL862XX_PCE_ACTION_CROSS_STATE_CROSS);
+/* Install PCE rules that trap IGMP and MLD frames to the CPU port for
+ * a single user port. PORTMAP_ALTERNATIVE overrides the bridge
+ * forwarding portmap to the CPU port. bFidEnable points the bridge
+ * engine at cpu_trap_fid (all flood modes enabled) so the frames are
+ * never classified as blocked unknown MC regardless of the ingress
+ * port's standalone flood policy.
+ *
+ * Three rules are installed per port:
+ * offset 2 -- IPv4 IGMP (IP protocol 2, all versions)
+ * offset 3 -- ICMPv6 types 130-132 (MLDv1 query, report, done)
+ * offset 4 -- ICMPv6 type 143 (MLDv2 Listener Report)
+ *
+ * The MLDv1 rule uses range mode on the first two bytes after the IP
+ * header (ICMPv6 type + code): lower bound 0x8200 (type 130, code 0)
+ * to upper bound 0x84ff (type 132, code 255). The MLDv2 rule uses
+ * nibble mask 0x3 to match type 143 with any code byte.
+ */
+static int mxl862xx_setup_snooping_traps(struct dsa_switch *ds, int port)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_pce_rule rule = {};
+ int ret;
+
+ return MXL862XX_API_WRITE(ds->priv, MXL862XX_TFLOW_PCERULEWRITE,
+ rule);
+ rule.logicalportid = port;
+ rule.region = cpu_to_le32(MXL862XX_PCE_RULE_CTP);
+ mxl862xx_fill_cpu_trap_action(ds, port, &rule);
+
+ /* IGMP: IPv4 protocol 2, all versions */
+ rule.pattern.index = cpu_to_le16(MXL862XX_IGMP_CTP_OFFSET);
+ rule.pattern.enable = 1;
+ rule.pattern.protocol = IPPROTO_IGMP;
+ rule.pattern.protocol_enable = 1;
+
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule);
+ if (ret)
+ return ret;
+
+ /* MLDv1: ICMPv6 types 130 (query), 131 (report), 132 (done).
+ * Range mode covers all three types with any code value.
+ */
+ memset(&rule.pattern, 0, sizeof(rule.pattern));
+ rule.pattern.index = cpu_to_le16(MXL862XX_MLDV1_CTP_OFFSET);
+ rule.pattern.enable = 1;
+ rule.pattern.protocol = IPPROTO_ICMPV6;
+ rule.pattern.protocol_enable = 1;
+ rule.pattern.app_data_msb =
+ cpu_to_le16((u16)ICMPV6_MGM_QUERY << 8);
+ rule.pattern.app_mask_range_msb =
+ cpu_to_le16(((u16)ICMPV6_MGM_REDUCTION << 8) | 0xff);
+ rule.pattern.app_data_msb_enable = 1;
+ rule.pattern.app_mask_range_msb_select = 1; /* range mode */
+
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule);
+ if (ret)
+ return ret;
+
+ /* MLDv2: ICMPv6 type 143 (Listener Report v2), any code byte.
+ * Nibble mask 0x3 masks nibbles 0-1 (lower byte = code field).
+ */
+ memset(&rule.pattern, 0, sizeof(rule.pattern));
+ rule.pattern.index = cpu_to_le16(MXL862XX_MLDV2_CTP_OFFSET);
+ rule.pattern.enable = 1;
+ rule.pattern.protocol = IPPROTO_ICMPV6;
+ rule.pattern.protocol_enable = 1;
+ rule.pattern.app_data_msb = cpu_to_le16((u16)ICMPV6_MLD2_REPORT << 8);
+ rule.pattern.app_mask_range_msb = cpu_to_le16(0x0003);
+ rule.pattern.app_data_msb_enable = 1;
+ /* app_mask_range_msb_select = 0: nibble mask mode (default) */
+
+ return MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule);
+}
+
static int mxl862xx_set_bridge_port(struct dsa_switch *ds, int port)
{
struct mxl862xx_bridge_port_config br_port_cfg = {};
@@ -1549,6 +1613,11 @@ static int mxl862xx_port_setup(struct ds
@@ -683,6 +838,28 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
+ /* install link-local trap for this user port */
+
+ /* Allocate a dedicated PCE snooping FID with all flood modes enabled.
+ * Per-port PCE trap rules (link-local, IGMP, MLD) set bFidEnable to
+ * this FID so that the bridge engine uses it for its flood-permission
+ * check instead of the ingress port's private FID (which has
+ * mc_flood=false to restrict unknown MC from reaching the CPU in the
+ * normal path). The hardware PCE FID action field is 6 bits wide, so
+ * the allocated ID must be in range 0..63.
+ */
+ ret = mxl862xx_allocate_bridge(priv);
+ if (ret < 0)
+ return ret;
+
+ if (WARN_ON_ONCE(ret > 0x3F))
+ return -ERANGE;
+
+ priv->cpu_trap_fid = ret;
+
+ ret = mxl862xx_bridge_config_fwd(ds, priv->cpu_trap_fid,
+ true, true, true);
+ if (ret)
+ return ret;
schedule_delayed_work(&priv->stats_work,
MXL862XX_STATS_POLL_INTERVAL);
@@ -1382,6 +1559,15 @@ static int mxl862xx_port_setup(struct ds
if (ret)
return ret;
+ /* install link-local and multicast snooping traps */
+ ret = mxl862xx_setup_link_local_trap(ds, port);
+ if (ret)
+ return ret;
+
/* Initialize and pre-allocate per-port EVLAN and VF blocks for
* user ports. CPU ports do not use EVLAN or VF -- frames pass
* through without processing. Pre-allocation avoids firmware
+ ret = mxl862xx_setup_snooping_traps(ds, port);
+ if (ret)
+ return ret;
+
priv->ports[port].ingress_evlan.block_size = priv->evlan_ingress_size;
ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].ingress_evlan);
if (ret)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -319,6 +319,13 @@ union mxl862xx_fw_version {
* @evlan_ingress_size: per-port ingress Extended VLAN block size
* @evlan_egress_size: per-port egress Extended VLAN block size
* @vf_block_size: per-port VLAN Filter block size
+ * @cpu_trap_fid: firmware bridge FID allocated for PCE-trapped frames;
+ * configured with uc/mc/bc flood all enabled so that
+ * IGMP, MLD, and link-local frames always reach the CPU
+ * regardless of the ingress port's private FID flood
+ * policy. Set once in setup() and referenced by
+ * fill_cpu_trap_action() via bFidEnable. The PCE FID
+ * action field is 6 bits, so this value must be <= 63.
* @stats_work: periodic work item that polls RMON hardware counters
* and accumulates them into 64-bit per-port stats
*/
@@ -335,6 +342,7 @@ struct mxl862xx_priv {
u16 evlan_ingress_size;
u16 evlan_egress_size;
u16 vf_block_size;
+ u16 cpu_trap_fid;
struct delayed_work stats_work;
};
@@ -1,7 +1,7 @@
From e18f5b235d8df21209c73f4f0bbc00cc3a1973ba Mon Sep 17 00:00:00 2001
From 264838ee8ee3ad611a84df96d889429f1ded2148 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:51:21 +0000
Subject: [PATCH 15/26] net: dsa: mxl862xx: warn about old firmware default PCE
Subject: [PATCH 08/19] net: dsa: mxl862xx: warn about old firmware default PCE
rules
Firmware versions older than 1.0.80 install global PCE rules at
@@ -19,14 +19,14 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -809,6 +809,10 @@ static int mxl862xx_setup(struct dsa_swi
@@ -860,6 +860,10 @@ static int mxl862xx_setup(struct dsa_swi
true, true, true);
if (ret)
return ret;
+
+ if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 80))
+ dev_warn(ds->dev, "firmware < 1.0.80 installs global PCE rules "
+ "that interfere with DSA operation, please update\n");
+
schedule_delayed_work(&priv->stats_work,
MXL862XX_STATS_POLL_INTERVAL);
@@ -1,7 +1,7 @@
From 4e1d854199c166f617b93b7542e863e6a8ad2ccb Mon Sep 17 00:00:00 2001
From a32f6a9c09b90e767a03ddac34d061545a0cf15e Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 03:44:41 +0000
Subject: [PATCH 17/26] net: dsa: mxl862xx: add link aggregation support
Subject: [PATCH 10/20] net: dsa: mxl862xx: add link aggregation support
Implement LAG offloading via the firmware's trunking engine. A
dedicated firmware bridge port is allocated per LAG and remains
@@ -33,9 +33,9 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 22 +
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 4 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 587 +++++++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.h | 34 ++
4 files changed, 630 insertions(+), 17 deletions(-)
drivers/net/dsa/mxl862xx/mxl862xx.c | 578 +++++++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.h | 33 ++
4 files changed, 621 insertions(+), 16 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -90,8 +90,8 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x1)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -620,7 +620,42 @@ static int mxl862xx_setup_link_local_tra
rule);
@@ -680,7 +680,42 @@ static int mxl862xx_setup_snooping_traps
return MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule);
}
-static int mxl862xx_set_bridge_port(struct dsa_switch *ds, int port)
@@ -134,16 +134,31 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
{
struct mxl862xx_bridge_port_config br_port_cfg = {};
struct dsa_port *dp = dsa_to_port(ds, port);
@@ -632,7 +667,7 @@ static int mxl862xx_set_bridge_port(stru
bool enable;
int i, idx;
@@ -713,8 +748,12 @@ static int mxl862xx_set_bridge_port(stru
dp->bridge->dev) {
if (member_dp->index == port)
continue;
- mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
- member_dp->index);
+ if (!mxl862xx_is_lag_master(priv, member_dp->index))
+ continue;
+ mxl862xx_fw_portmap_set_bit(
+ br_port_cfg.bridge_port_map,
+ mxl862xx_lag_bridge_port(priv,
+ member_dp->index));
}
mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
mxl862xx_cpu_bridge_port_id(ds, port));
@@ -727,7 +766,7 @@ static int mxl862xx_set_bridge_port(stru
bridge_id = dp->bridge ? priv->bridges[dp->bridge->num] : p->fid;
- br_port_cfg.bridge_port_id = cpu_to_le16(port);
+ br_port_cfg.bridge_port_id = cpu_to_le16(bp_id);
br_port_cfg.bridge_id = cpu_to_le16(bridge_id);
br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
@@ -715,12 +750,38 @@ static int mxl862xx_set_bridge_port(stru
@@ -808,11 +847,38 @@ static int mxl862xx_set_bridge_port(stru
br_port_cfg);
}
@@ -174,41 +189,16 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
static int mxl862xx_sync_bridge_members(struct dsa_switch *ds,
const struct dsa_bridge *bridge)
{
struct mxl862xx_priv *priv = ds->priv;
struct dsa_port *dp, *member_dp;
- int port, err, ret = 0;
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p;
+ int port, member, err, ret = 0;
+ u16 lag_bp, bp;
struct dsa_port *dp;
- int ret = 0, err;
+ u16 lag_bp;
+ int err, ret = 0;
dsa_switch_for_each_bridge_member(dp, ds, bridge->dev) {
port = dp->index;
@@ -729,9 +790,21 @@ static int mxl862xx_sync_bridge_members(
MXL862XX_MAX_BRIDGE_PORTS);
dsa_switch_for_each_bridge_member(member_dp, ds, bridge->dev) {
- if (member_dp->index != port)
- __set_bit(member_dp->index,
- priv->ports[port].portmap);
+ member = member_dp->index;
+
+ /* For LAG members, only include the LAG's
+ * dedicated bridge port in the portmap.
+ * Non-master members are skipped to avoid
+ * duplicates (they share the same LAG bridge
+ * port).
+ */
+ if (!mxl862xx_is_lag_master(priv, member))
+ continue;
+ if (member != port) {
+ bp = mxl862xx_lag_bridge_port(priv,
+ member);
+ __set_bit(bp, priv->ports[port].portmap);
+ }
}
__set_bit(mxl862xx_cpu_bridge_port_id(ds, port),
priv->ports[port].portmap);
@@ -741,6 +814,25 @@ static int mxl862xx_sync_bridge_members(
err = mxl862xx_set_bridge_port(ds, dp->index);
@@ -820,6 +886,25 @@ static int mxl862xx_sync_bridge_members(
ret = err;
}
@@ -234,7 +224,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
return ret;
}
@@ -1881,6 +1973,408 @@ static int mxl862xx_setup_cpu_bridge(str
@@ -1859,6 +1944,408 @@ static int mxl862xx_setup_cpu_bridge(str
return mxl862xx_set_bridge_port(ds, port);
}
@@ -643,7 +633,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
const struct dsa_bridge bridge,
bool *tx_fwd_offload,
@@ -1907,7 +2401,18 @@ static int mxl862xx_port_bridge_join(str
@@ -1884,7 +2371,18 @@ static int mxl862xx_port_bridge_join(str
return 0;
}
@@ -663,7 +653,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
}
static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
@@ -1966,6 +2471,17 @@ static void mxl862xx_port_bridge_leave(s
@@ -1935,6 +2433,17 @@ static void mxl862xx_port_bridge_leave(s
"failed to update CPU VBP for port %d: %pe\n", port,
ERR_PTR(err));
@@ -681,7 +671,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
if (!dsa_bridge_ports(ds, bridge.dev))
mxl862xx_free_bridge(ds, &bridge);
}
@@ -2591,18 +3107,17 @@ static int mxl862xx_get_fid(struct dsa_s
@@ -2593,18 +3102,17 @@ static int mxl862xx_get_fid(struct dsa_s
}
/**
@@ -707,7 +697,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
*/
static int mxl862xx_fdb_bridge_port(struct dsa_switch *ds, int port,
const struct dsa_db db)
@@ -2618,7 +3133,7 @@ static int mxl862xx_fdb_bridge_port(stru
@@ -2620,7 +3128,7 @@ static int mxl862xx_fdb_bridge_port(stru
return bp_cpu;
}
@@ -716,7 +706,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
}
/**
@@ -2862,11 +3377,43 @@ static int mxl862xx_port_fdb_del(struct
@@ -2867,11 +3375,43 @@ static int mxl862xx_port_fdb_del(struct
return ret;
}
@@ -760,7 +750,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
u32 entry_port_id;
int ret;
@@ -2880,7 +3427,7 @@ static int mxl862xx_port_fdb_dump(struct
@@ -2885,7 +3425,7 @@ static int mxl862xx_port_fdb_dump(struct
entry_port_id = le32_to_cpu(param.port_id);
@@ -769,7 +759,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
ret = cb(param.mac, FIELD_GET(MXL862XX_TCI_VLAN_ID,
le16_to_cpu(param.tci)),
param.static_entry, data);
@@ -3521,6 +4068,11 @@ static const struct dsa_switch_ops mxl86
@@ -3556,6 +4096,11 @@ static const struct dsa_switch_ops mxl86
.port_fdb_dump = mxl862xx_port_fdb_dump,
.port_mdb_add = mxl862xx_port_mdb_add,
.port_mdb_del = mxl862xx_port_mdb_del,
@@ -781,7 +771,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
.port_vlan_filtering = mxl862xx_port_vlan_filtering,
.port_vlan_add = mxl862xx_port_vlan_add,
.port_vlan_del = mxl862xx_port_vlan_del,
@@ -3561,6 +4113,7 @@ static int mxl862xx_probe(struct mdio_de
@@ -3597,6 +4142,7 @@ static int mxl862xx_probe(struct mdio_de
ds->num_ports = MXL862XX_MAX_PORTS;
ds->fdb_isolation = true;
ds->max_num_bridges = MXL862XX_MAX_BRIDGES;
@@ -811,7 +801,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
/* Number of __le16 words in a firmware portmap (128-bit bitmap). */
#define MXL862XX_FW_PORTMAP_WORDS (MXL862XX_MAX_BRIDGE_PORTS / 16)
@@ -228,6 +241,12 @@ struct mxl862xx_port_stats {
@@ -227,6 +240,12 @@ struct mxl862xx_port_stats {
* @stats_lock: protects accumulator reads in .get_stats64 against
* concurrent updates from the polling work
* @tag_8021q_vid: currently assigned tag_8021q management VID
@@ -824,21 +814,20 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
*/
struct mxl862xx_port {
struct mxl862xx_priv *priv;
@@ -250,6 +269,10 @@ struct mxl862xx_port {
@@ -246,6 +265,9 @@ struct mxl862xx_port {
struct work_struct host_flood_work;
u16 tag_8021q_vid;
struct mxl862xx_evlan_block cpu_egress_evlan;
+ /* LAG state */
+ struct dsa_lag *lag;
+ bool lag_tx_enabled;
+ u8 lag_hash_bits;
/* Hardware stats accumulation */
struct mxl862xx_port_stats stats;
spinlock_t stats_lock;
@@ -334,6 +357,15 @@ union mxl862xx_fw_version {
* @evlan_egress_size: per-port egress Extended VLAN block size
* @cpu_evlan_ingress_size: CPU port ingress EVLAN block size (tag_8021q)
* @vf_block_size: per-port VLAN Filter block size
spinlock_t stats_lock; /* protects stats accumulators */
};
@@ -336,6 +358,15 @@ union mxl862xx_fw_version {
* policy. Set once in setup() and referenced by
* fill_cpu_trap_action() via bFidEnable. The PCE FID
* action field is 6 bits, so this value must be <= 63.
+ * @lag_bridge_ports: maps DSA LAG ID to firmware bridge port ID;
+ * zero means no bridge port allocated for that LAG.
+ * Indexed by lag->id (entry 0 is unused).
@@ -851,10 +840,10 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* @stats_work: periodic work item that polls RMON hardware counters
* and accumulates them into 64-bit per-port stats
*/
@@ -352,6 +384,8 @@ struct mxl862xx_priv {
u16 evlan_egress_size;
@@ -355,6 +386,8 @@ struct mxl862xx_priv {
u16 cpu_evlan_ingress_size;
u16 vf_block_size;
u16 cpu_trap_fid;
+ u16 lag_bridge_ports[MXL862XX_MAX_LAG_IDS + 1];
+ u8 trunk_hash;
struct delayed_work stats_work;
@@ -1,7 +1,7 @@
From 5528f38c3d709417625eb7f36628be31727a8221 Mon Sep 17 00:00:00 2001
From d919c2f8da9dbd4dda57ceebb5c8b103805b58d1 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 12:05:29 +0000
Subject: [PATCH 18/26] net: dsa: mxl862xx: add support for mirror port
Subject: [PATCH 11/19] net: dsa: mxl862xx: add support for mirror port
The MxL862xx hardware supports a single monitor port which can be
configured to mirror any other port's ingress and/or egress traffic.
@@ -14,8 +14,8 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 12 +++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 1 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 118 ++++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx.h | 8 ++
4 files changed, 139 insertions(+)
drivers/net/dsa/mxl862xx/mxl862xx.h | 7 ++
4 files changed, 138 insertions(+)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -50,7 +50,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#define MXL862XX_TFLOW_PCERULEWRITE (MXL862XX_TFLOW_MAGIC + 0x2)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -1084,6 +1084,8 @@ static int mxl862xx_setup(struct dsa_swi
@@ -1100,6 +1100,8 @@ static int mxl862xx_setup(struct dsa_swi
(n_user_ports + n_cpu_ports);
}
@@ -59,8 +59,8 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
ret = mxl862xx_setup_drop_meter(ds);
if (ret)
return ret;
@@ -1973,6 +1975,120 @@ static int mxl862xx_setup_cpu_bridge(str
return mxl862xx_set_bridge_port(ds, port);
@@ -3167,6 +3169,120 @@ static int mxl862xx_fdb_del_per_fid(stru
return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, param);
}
+static int mxl862xx_port_mirror_add(struct dsa_switch *ds, int port,
@@ -178,9 +178,9 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+}
+
/**
* mxl862xx_lag_master_port - Find the LAG master (lowest-numbered member)
* @ds: DSA switch
@@ -4068,6 +4184,8 @@ static const struct dsa_switch_ops mxl86
* mxl862xx_mac_portmap_add - Set port bits in a MAC table entry's portmap
* @priv: driver private data
@@ -4096,6 +4212,8 @@ static const struct dsa_switch_ops mxl86
.port_fdb_dump = mxl862xx_port_fdb_dump,
.port_mdb_add = mxl862xx_port_mdb_add,
.port_mdb_del = mxl862xx_port_mdb_del,
@@ -191,7 +191,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
.port_lag_change = mxl862xx_port_lag_change,
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -241,6 +241,8 @@ struct mxl862xx_port_stats {
@@ -240,6 +240,8 @@ struct mxl862xx_port_stats {
* @stats_lock: protects accumulator reads in .get_stats64 against
* concurrent updates from the polling work
* @tag_8021q_vid: currently assigned tag_8021q management VID
@@ -200,17 +200,16 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* @lag: non-NULL when port is member of a LAG group;
* points to the DSA LAG structure
* @lag_tx_enabled: true when this port is active for TX in its LAG
@@ -269,6 +271,9 @@ struct mxl862xx_port {
@@ -265,6 +267,8 @@ struct mxl862xx_port {
struct work_struct host_flood_work;
u16 tag_8021q_vid;
struct mxl862xx_evlan_block cpu_egress_evlan;
+ /* Mirror state */
+ bool ingress_mirror;
+ bool egress_mirror;
/* LAG state */
struct dsa_lag *lag;
bool lag_tx_enabled;
@@ -366,6 +371,8 @@ union mxl862xx_fw_version {
u8 lag_hash_bits;
@@ -367,6 +371,8 @@ union mxl862xx_fw_version {
* @trunk_hash: current global hash field bitmask (6 bits,
* MXL862XX_TRUNK_HASH_*); union of all active LAGs'
* hash requirements
@@ -219,8 +218,8 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* @stats_work: periodic work item that polls RMON hardware counters
* and accumulates them into 64-bit per-port stats
*/
@@ -386,6 +393,7 @@ struct mxl862xx_priv {
u16 vf_block_size;
@@ -388,6 +394,7 @@ struct mxl862xx_priv {
u16 cpu_trap_fid;
u16 lag_bridge_ports[MXL862XX_MAX_LAG_IDS + 1];
u8 trunk_hash;
+ int mirror_dest;
@@ -1,7 +1,7 @@
From 4059d35a5bbf1901b2e0eb7126369cd713cacfce Mon Sep 17 00:00:00 2001
From 64269d9d809962a0f7e68e9b618d81e561e3eb6f Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 16:30:08 +0000
Subject: [PATCH 19/26] net: dsa: wire flash_update devlink callback to drivers
Subject: [PATCH 12/19] net: dsa: wire flash_update devlink callback to drivers
Add a devlink_flash_update callback to dsa_switch_ops so that DSA
drivers can support devlink dev flash without open-coding the devlink
@@ -1,7 +1,7 @@
From 0145151dc68aa318d8addb6fe7f12c0967f951da Mon Sep 17 00:00:00 2001
From fed4225f75b6fe6898e48f472cbbee0aaa046760 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 16:30:17 +0000
Subject: [PATCH 20/26] net: dsa: mxl862xx: add SMDIO clause-22 register access
Subject: [PATCH 13/19] net: dsa: mxl862xx: add SMDIO clause-22 register access
Add mxl862xx_smdio_read() and mxl862xx_smdio_write() for clause-22
SMDIO register access. MCUboot rescue mode only exposes clause-22
@@ -1,7 +1,7 @@
From bdbca48510e3e96ed9210f20fa4244dd6df5d44a Mon Sep 17 00:00:00 2001
From fa186b09e346e0b7f2504232731bc9e4dee0c600 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 16:30:31 +0000
Subject: [PATCH 21/26] net: dsa: mxl862xx: add devlink flash_update and
Subject: [PATCH 14/19] net: dsa: mxl862xx: add devlink flash_update and
info_get
Implement runtime firmware upgrade via "devlink dev flash" and version
@@ -538,7 +538,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -22,6 +22,7 @@
@@ -23,6 +23,7 @@
#include "mxl862xx.h"
#include "mxl862xx-api.h"
#include "mxl862xx-cmd.h"
@@ -546,8 +546,8 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#include "mxl862xx-host.h"
#include "mxl862xx-phylink.h"
@@ -4204,6 +4205,9 @@ static const struct dsa_switch_ops mxl86
.get_pause_stats = mxl862xx_get_pause_stats,
@@ -4233,6 +4234,9 @@ static const struct dsa_switch_ops mxl86
.get_rmon_stats = mxl862xx_get_rmon_stats,
.get_stats64 = mxl862xx_get_stats64,
.self_test = mxl862xx_serdes_self_test,
+ .devlink_info_get = mxl862xx_devlink_info_get,
@@ -558,7 +558,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
static int mxl862xx_probe(struct mdio_device *mdiodev)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -394,6 +394,8 @@ struct mxl862xx_priv {
@@ -395,6 +395,8 @@ struct mxl862xx_priv {
u16 lag_bridge_ports[MXL862XX_MAX_LAG_IDS + 1];
u8 trunk_hash;
int mirror_dest;
@@ -1,7 +1,7 @@
From 8deb5be9638f7eb3009ed3eb619eedadee1df523 Mon Sep 17 00:00:00 2001
From 05bc981690d6ef03143a094f28714aa0171ab571 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 23:42:18 +0000
Subject: [PATCH 22/26] net: dsa: mxl862xx: implement port MTU configuration
Date: Wed, 1 Apr 2026 13:43:08 +0100
Subject: [PATCH 15/19] net: dsa: mxl862xx: implement port MTU configuration
The firmware exposes a global max_packet_len register via
MXL862XX_COMMON_CFGSET. Since this is switch-wide rather than
@@ -12,20 +12,20 @@ the effective maximum does not change.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 50 +++++++++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx.h | 4 +++
2 files changed, 54 insertions(+)
drivers/net/dsa/mxl862xx/mxl862xx.h | 3 ++
2 files changed, 53 insertions(+)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -11,6 +11,7 @@
#include <linux/delay.h>
@@ -12,6 +12,7 @@
#include <linux/etherdevice.h>
#include <linux/icmpv6.h>
#include <linux/if_bridge.h>
+#include <linux/if_vlan.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_mdio.h>
@@ -3723,6 +3724,53 @@ static int mxl862xx_set_ageing_time(stru
@@ -3727,6 +3728,53 @@ static int mxl862xx_set_ageing_time(stru
return ret;
}
@@ -79,7 +79,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port,
u8 state)
{
@@ -4174,6 +4222,8 @@ static const struct dsa_switch_ops mxl86
@@ -4202,6 +4250,8 @@ static const struct dsa_switch_ops mxl86
.port_disable = mxl862xx_port_disable,
.port_fast_age = mxl862xx_port_fast_age,
.set_ageing_time = mxl862xx_set_ageing_time,
@@ -90,7 +90,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
.port_pre_bridge_flags = mxl862xx_port_pre_bridge_flags,
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -249,6 +249,8 @@ struct mxl862xx_port_stats {
@@ -248,6 +248,8 @@ struct mxl862xx_port_stats {
* @lag_hash_bits: hash field bitmask (MXL862XX_TRUNK_HASH_*) requested
* when this port joined its LAG; used to recompute the
* global trunk_hash when a LAG is destroyed
@@ -99,12 +99,11 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
*/
struct mxl862xx_port {
struct mxl862xx_priv *priv;
@@ -278,6 +280,8 @@ struct mxl862xx_port {
@@ -272,6 +274,7 @@ struct mxl862xx_port {
struct dsa_lag *lag;
bool lag_tx_enabled;
u8 lag_hash_bits;
+ /* MTU */
+ int mtu;
/* Hardware stats accumulation */
struct mxl862xx_port_stats stats;
spinlock_t stats_lock;
spinlock_t stats_lock; /* protects stats accumulators */
};
@@ -1,7 +1,7 @@
From 13a4c918cd9ded7207f38033511ab13f7aff9bd2 Mon Sep 17 00:00:00 2001
From b723f7484006aadbaa51e16b870f3c98390605b1 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 01:47:19 +0000
Subject: [PATCH 23/26] net: dsa: mxl862xx: support BR_HAIRPIN_MODE bridge flag
Date: Wed, 1 Apr 2026 13:43:12 +0100
Subject: [PATCH 16/19] net: dsa: mxl862xx: support BR_HAIRPIN_MODE bridge flag
Implement hairpin mode by including the port's own bridge port ID in
its forwarding portmap. When hairpin is enabled, bridged frames whose
@@ -18,29 +18,24 @@ bridge member rebuild since only the calling port is affected.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 29 +++++++++++++++++++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 6 ++++++
2 files changed, 33 insertions(+), 2 deletions(-)
drivers/net/dsa/mxl862xx/mxl862xx.c | 12 ++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 5 +++++
2 files changed, 15 insertions(+), 2 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -811,6 +811,15 @@ static int mxl862xx_sync_bridge_members(
__set_bit(mxl862xx_cpu_bridge_port_id(ds, port),
priv->ports[port].portmap);
+ /* Hairpin: include the port's own bridge port so bridged
+ * frames can egress the ingress port.
+ * For LAG ports this adds the LAG bridge port, which
+ * propagates to the LAG BP in the second loop below.
+ */
+ if (priv->ports[port].hairpin)
+ __set_bit(mxl862xx_lag_bridge_port(priv, port),
+ priv->ports[port].portmap);
+
err = mxl862xx_set_bridge_port(ds, port);
if (err)
ret = err;
@@ -3898,7 +3907,7 @@ static int mxl862xx_port_pre_bridge_flag
@@ -759,6 +759,10 @@ static int __mxl862xx_set_bridge_port(st
}
mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
mxl862xx_cpu_bridge_port_id(ds, port));
+ if (p->hairpin)
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ mxl862xx_lag_bridge_port(priv,
+ port));
} else {
mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
mxl862xx_cpu_bridge_port_id(ds, port));
@@ -3895,7 +3899,7 @@ static int mxl862xx_port_pre_bridge_flag
struct netlink_ext_ack *extack)
{
if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD |
@@ -49,33 +44,14 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
return -EINVAL;
return 0;
@@ -3912,6 +3921,7 @@ static int mxl862xx_port_bridge_flags(st
unsigned long old_block = priv->ports[port].flood_block;
unsigned long block = old_block;
int ret;
+ u16 bp;
if (flags.mask & BR_FLOOD) {
if (flags.val & BR_FLOOD)
@@ -3940,7 +3950,22 @@ static int mxl862xx_port_bridge_flags(st
@@ -3937,7 +3941,11 @@ static int mxl862xx_port_bridge_flags(st
if (flags.mask & BR_LEARNING)
priv->ports[port].learning = !!(flags.val & BR_LEARNING);
- if ((block != old_block) || (flags.mask & BR_LEARNING)) {
+ if (flags.mask & BR_HAIRPIN_MODE) {
+ bp = mxl862xx_lag_bridge_port(priv, port);
- if (block != old_block || (flags.mask & BR_LEARNING)) {
+ if (flags.mask & BR_HAIRPIN_MODE)
+ priv->ports[port].hairpin = !!(flags.val & BR_HAIRPIN_MODE);
+
+ /* Hairpin adds/removes the port's own bridge port from its
+ * cached portmap. Only this port is affected -- push the
+ * updated portmap directly.
+ */
+ if (flags.val & BR_HAIRPIN_MODE)
+ __set_bit(bp, priv->ports[port].portmap);
+ else
+ __clear_bit(bp, priv->ports[port].portmap);
+ }
+
+ if ((block != old_block) ||
+ (flags.mask & (BR_LEARNING | BR_HAIRPIN_MODE))) {
priv->ports[port].flood_block = block;
@@ -83,7 +59,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
if (ret)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -241,6 +241,10 @@ struct mxl862xx_port_stats {
@@ -240,6 +240,10 @@ struct mxl862xx_port_stats {
* @stats_lock: protects accumulator reads in .get_stats64 against
* concurrent updates from the polling work
* @tag_8021q_vid: currently assigned tag_8021q management VID
@@ -94,12 +70,11 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* @ingress_mirror: true when ingress mirroring is active on this port
* @egress_mirror: true when egress mirroring is active on this port
* @lag: non-NULL when port is member of a LAG group;
@@ -273,6 +277,8 @@ struct mxl862xx_port {
@@ -269,6 +273,7 @@ struct mxl862xx_port {
struct work_struct host_flood_work;
u16 tag_8021q_vid;
struct mxl862xx_evlan_block cpu_egress_evlan;
+ /* Hairpin state */
+ bool hairpin;
/* Mirror state */
bool ingress_mirror;
bool egress_mirror;
struct dsa_lag *lag;
@@ -1,7 +1,7 @@
From d49d1f8bee29269def7593f980d0e08bfb5c3ef8 Mon Sep 17 00:00:00 2001
From d8f20ba50ce0f93f34a41a9b833be76a5d1d2714 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 01:51:33 +0000
Subject: [PATCH 24/26] net: dsa: mxl862xx: support BR_ISOLATED bridge flag
Subject: [PATCH 17/19] net: dsa: mxl862xx: support BR_ISOLATED bridge flag
Implement port isolation by excluding isolated ports from each other's
forwarding portmaps in sync_bridge_members. Non-isolated ports can
@@ -13,28 +13,22 @@ rebuilt via sync_bridge_members since multiple ports are affected.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 26 +++++++++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.c | 20 +++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.h | 4 ++++
2 files changed, 29 insertions(+), 1 deletion(-)
2 files changed, 23 insertions(+), 1 deletion(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -802,6 +802,14 @@ static int mxl862xx_sync_bridge_members(
*/
if (!mxl862xx_is_lag_master(priv, member))
@@ -752,6 +752,8 @@ static int __mxl862xx_set_bridge_port(st
continue;
+
+ /* Isolated ports cannot forward to each other.
+ * Non-isolated ports can reach everyone.
+ */
+ if (priv->ports[port].isolated &&
+ priv->ports[member].isolated)
if (!mxl862xx_is_lag_master(priv, member_dp->index))
continue;
+ if (p->isolated && priv->ports[member_dp->index].isolated)
+ continue;
+
if (member != port) {
bp = mxl862xx_lag_bridge_port(priv,
member);
@@ -3907,7 +3915,7 @@ static int mxl862xx_port_pre_bridge_flag
mxl862xx_fw_portmap_set_bit(
br_port_cfg.bridge_port_map,
mxl862xx_lag_bridge_port(priv,
@@ -3899,7 +3901,7 @@ static int mxl862xx_port_pre_bridge_flag
struct netlink_ext_ack *extack)
{
if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD |
@@ -43,15 +37,15 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
return -EINVAL;
return 0;
@@ -3920,6 +3928,7 @@ static int mxl862xx_port_bridge_flags(st
@@ -3912,6 +3914,7 @@ static int mxl862xx_port_bridge_flags(st
struct mxl862xx_priv *priv = ds->priv;
unsigned long old_block = priv->ports[port].flood_block;
unsigned long block = old_block;
+ struct dsa_port *dp;
int ret;
u16 bp;
@@ -3972,6 +3981,21 @@ static int mxl862xx_port_bridge_flags(st
if (flags.mask & BR_FLOOD) {
@@ -3952,6 +3955,21 @@ static int mxl862xx_port_bridge_flags(st
return ret;
}
@@ -75,7 +69,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -214,6 +214,9 @@ struct mxl862xx_port_stats {
@@ -213,6 +213,9 @@ struct mxl862xx_port_stats {
* @flood_block: bitmask of firmware meter indices that are currently
* rate-limiting flood traffic on this port (zero-rate
* meters used to block flooding)
@@ -85,11 +79,11 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
* @learning: true when address learning is enabled on this port
* @setup_done: set at end of port_setup, cleared at start of
* port_teardown; guards deferred work against
@@ -261,6 +264,7 @@ struct mxl862xx_port {
@@ -259,6 +262,7 @@ struct mxl862xx_port {
struct mxl862xx_priv *priv;
u16 fid;
DECLARE_BITMAP(portmap, MXL862XX_MAX_BRIDGE_PORTS);
unsigned long flood_block;
+ bool isolated;
bool learning;
bool setup_done;
/* VLAN state */
u16 pvid;
@@ -1,7 +1,7 @@
From c2fb7f0df63ac994850f766e7f2eb50c6c5ef2cf Mon Sep 17 00:00:00 2001
From 8856f7610167a3005ecef401c8528111d153b554 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:17:49 +0000
Subject: [PATCH 25/26] DO NOT SUBMIT: net: dsa: mxl862xx: re-introduce PCE
Subject: [PATCH 18/19] DO NOT SUBMIT: net: dsa: mxl862xx: re-introduce PCE
workaround for old firmware
Re-introduce the mxl862xx_disable_fw_global_rules() function that
@@ -20,7 +20,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -477,6 +477,43 @@ static int mxl862xx_setup_drop_meter(str
@@ -449,6 +449,43 @@ static int mxl862xx_setup_drop_meter(str
return MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
}
@@ -62,9 +62,9 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ return 0;
+}
/* Per-CTP offset used for the link-local trap rule. Each port's CTP
* flow-table block is pre-allocated by the firmware during init (44
@@ -1109,9 +1146,11 @@ static int mxl862xx_setup(struct dsa_swi
/* Per-CTP offsets for protocol trap rules. Each port's CTP flow-table
* block is pre-allocated by the firmware during init (44 entries per
@@ -1114,9 +1151,11 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
@@ -0,0 +1,25 @@
From df0c747216063041ba0d786b01f9b1e2aba5316a Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 22 Apr 2026 15:15:52 +0100
Subject: [PATCH] DO NOT SUBMIT: net: dsa: mxl862xx: increase CMD timeout
Lift the command timeout by 10x from 500ms to 5s.
This is done because older firmware can be extremely slow to respond
and cause -ETIMEOUT during setup, or crash the PHY state machine.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -207,7 +207,7 @@ static int mxl862xx_busy_wait(struct mxl
int val;
return readx_poll_timeout(mxl862xx_ctrl_read, priv, val,
- !(val & CTRL_BUSY_MASK), 15, 500000);
+ !(val & CTRL_BUSY_MASK), 50, 5000000);
}
/* Issue a firmware command with CRC-6 protection on the ctrl and len_ret
@@ -10,7 +10,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -5298,6 +5298,8 @@ static int mtk_probe(struct platform_dev
@@ -5295,6 +5295,8 @@ static int mtk_probe(struct platform_dev
dev_err(eth->dev, "failed to allocated dummy device\n");
goto err_unreg_netdev;
}
@@ -20,7 +20,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -2279,7 +2279,7 @@ int phylink_fwnode_phy_connect(struct ph
@@ -2278,7 +2278,7 @@ int phylink_fwnode_phy_connect(struct ph
{
struct fwnode_handle *phy_fwnode;
struct phy_device *phy_dev;
@@ -29,7 +29,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
/* Fixed links and 802.3z are handled without needing a PHY */
if (pl->cfg_link_an_mode == MLO_AN_FIXED ||
@@ -2309,6 +2309,25 @@ int phylink_fwnode_phy_connect(struct ph
@@ -2308,6 +2308,25 @@ int phylink_fwnode_phy_connect(struct ph
if (pl->config->mac_requires_rxc)
flags |= PHY_F_RXC_ALWAYS_ON;
@@ -0,0 +1,37 @@
From 7289d42b8fd17713531c0f7e8ae7653d1f2c6f8f Mon Sep 17 00:00:00 2001
From: Frank Wunderlich <frank-w@public-files.de>
Date: Thu, 9 Apr 2026 15:33:42 +0200
Subject: [PATCH] net: pcs: pcs-mtk-lynxi: fix bpi-r3 serdes configuration
Commit 8871389da151 introduces common pcs dts properties which writes
rx=normal,tx=normal polarity to register SGMSYS_QPHY_WRAP_CTRL of switch.
This is initialized with tx-bit set and so change inverts polarity
compared to before.
It looks like mt7531 has tx polarity inverted in hardware and set tx-bit
by default to restore the normal polarity.
Till this patch the register write was only called when mediatek,pnswap
property was set which cannot be done for switch because the fw-node param
was always NULL from switch driver in the mtk_pcs_lynxi_create call.
Do not configure switch side like it's done before.
Fixes: 8871389da151 ("net: pcs: pcs-mtk-lynxi: deprecate "mediatek,pnswap"")
Signed-off-by: Frank Wunderlich <frank-w@public-files.de>
---
drivers/net/pcs/pcs-mtk-lynxi.c | 3 +++
1 file changed, 3 insertions(+)
--- a/drivers/net/pcs/pcs-mtk-lynxi.c
+++ b/drivers/net/pcs/pcs-mtk-lynxi.c
@@ -129,6 +129,9 @@ static int mtk_pcs_config_polarity(struc
unsigned int val = 0;
int ret;
+ if (!fwnode)
+ return 0;
+
if (fwnode_property_read_bool(fwnode, "mediatek,pnswap"))
default_pol = PHY_POL_INVERT;
@@ -291,7 +291,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
eth->scratch_ring, eth->phy_scratch_ring,
true);
eth->scratch_ring = NULL;
@@ -5386,6 +5390,9 @@ static void mtk_remove(struct platform_d
@@ -5383,6 +5387,9 @@ static void mtk_remove(struct platform_d
mtk_mdio_cleanup(eth);
}
@@ -301,7 +301,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
static const struct mtk_soc_data mt2701_data = {
.reg_map = &mtk_reg_map,
.caps = MT7623_CAPS | MTK_HWLRO,
@@ -5394,14 +5401,14 @@ static const struct mtk_soc_data mt2701_
@@ -5391,14 +5398,14 @@ static const struct mtk_soc_data mt2701_
.required_pctl = true,
.version = 1,
.tx = {
@@ -318,7 +318,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
.irq_done_mask = MTK_RX_DONE_INT,
.dma_l4_valid = RX_DMA_L4_VALID,
.dma_size = MTK_DMA_SIZE(2K),
@@ -5422,14 +5429,14 @@ static const struct mtk_soc_data mt7621_
@@ -5419,14 +5426,14 @@ static const struct mtk_soc_data mt7621_
.hash_offset = 2,
.foe_entry_size = MTK_FOE_ENTRY_V1_SIZE,
.tx = {
@@ -335,7 +335,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
.irq_done_mask = MTK_RX_DONE_INT,
.dma_l4_valid = RX_DMA_L4_VALID,
.dma_size = MTK_DMA_SIZE(2K),
@@ -5452,14 +5459,14 @@ static const struct mtk_soc_data mt7622_
@@ -5449,14 +5456,14 @@ static const struct mtk_soc_data mt7622_
.has_accounting = true,
.foe_entry_size = MTK_FOE_ENTRY_V1_SIZE,
.tx = {
@@ -352,7 +352,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
.irq_done_mask = MTK_RX_DONE_INT,
.dma_l4_valid = RX_DMA_L4_VALID,
.dma_size = MTK_DMA_SIZE(2K),
@@ -5481,14 +5488,14 @@ static const struct mtk_soc_data mt7623_
@@ -5478,14 +5485,14 @@ static const struct mtk_soc_data mt7623_
.foe_entry_size = MTK_FOE_ENTRY_V1_SIZE,
.disable_pll_modes = true,
.tx = {
@@ -369,7 +369,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
.irq_done_mask = MTK_RX_DONE_INT,
.dma_l4_valid = RX_DMA_L4_VALID,
.dma_size = MTK_DMA_SIZE(2K),
@@ -5507,14 +5514,14 @@ static const struct mtk_soc_data mt7629_
@@ -5504,14 +5511,14 @@ static const struct mtk_soc_data mt7629_
.has_accounting = true,
.version = 1,
.tx = {
@@ -386,7 +386,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
.irq_done_mask = MTK_RX_DONE_INT,
.dma_l4_valid = RX_DMA_L4_VALID,
.dma_size = MTK_DMA_SIZE(2K),
@@ -5537,14 +5544,14 @@ static const struct mtk_soc_data mt7981_
@@ -5534,14 +5541,14 @@ static const struct mtk_soc_data mt7981_
.has_accounting = true,
.foe_entry_size = MTK_FOE_ENTRY_V2_SIZE,
.tx = {
@@ -403,7 +403,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
.irq_done_mask = MTK_RX_DONE_INT,
.dma_l4_valid = RX_DMA_L4_VALID_V2,
.dma_max_len = MTK_TX_DMA_BUF_LEN,
@@ -5567,14 +5574,14 @@ static const struct mtk_soc_data mt7986_
@@ -5564,14 +5571,14 @@ static const struct mtk_soc_data mt7986_
.has_accounting = true,
.foe_entry_size = MTK_FOE_ENTRY_V2_SIZE,
.tx = {
@@ -420,7 +420,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
.irq_done_mask = MTK_RX_DONE_INT,
.dma_l4_valid = RX_DMA_L4_VALID_V2,
.dma_max_len = MTK_TX_DMA_BUF_LEN,
@@ -5597,14 +5604,14 @@ static const struct mtk_soc_data mt7988_
@@ -5594,14 +5601,14 @@ static const struct mtk_soc_data mt7988_
.has_accounting = true,
.foe_entry_size = MTK_FOE_ENTRY_V3_SIZE,
.tx = {
@@ -437,7 +437,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
.irq_done_mask = MTK_RX_DONE_INT_V2,
.dma_l4_valid = RX_DMA_L4_VALID_V2,
.dma_max_len = MTK_TX_DMA_BUF_LEN_V2,
@@ -5621,13 +5628,13 @@ static const struct mtk_soc_data rt5350_
@@ -5618,13 +5625,13 @@ static const struct mtk_soc_data rt5350_
.required_pctl = false,
.version = 1,
.tx = {
@@ -0,0 +1,103 @@
From c6f6cb55c3c316f6169e07eacc5ccb214116719a Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Mon, 31 Mar 2025 15:40:12 +0200
Subject: [PATCH 1/7] net: phylink: keep and use MAC supported_interfaces in
phylink struct
Add in phylink struct a copy of supported_interfaces from phylink_config
and make use of that instead of relying on phylink_config value.
This in preparation for support of PCS handling internally to phylink
where a PCS can be removed or added after the phylink is created and we
need both a reference of the supported_interfaces value from
phylink_config and an internal value that can be updated with the new
PCS info.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/net/phy/phylink.c | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -59,6 +59,11 @@ struct phylink {
/* The link configuration settings */
struct phylink_link_state link_config;
+ /* What interface are supported by the current link.
+ * Can change on removal or addition of new PCS.
+ */
+ DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
+
/* The current settings */
phy_interface_t cur_interface;
@@ -623,7 +628,7 @@ static int phylink_validate_mask(struct
static int phylink_validate(struct phylink *pl, unsigned long *supported,
struct phylink_link_state *state)
{
- const unsigned long *interfaces = pl->config->supported_interfaces;
+ const unsigned long *interfaces = pl->supported_interfaces;
if (state->interface == PHY_INTERFACE_MODE_NA)
return phylink_validate_mask(pl, NULL, supported, state,
@@ -1853,6 +1858,9 @@ struct phylink *phylink_create(struct ph
mutex_init(&pl->state_mutex);
INIT_WORK(&pl->resolve, phylink_resolve);
+ phy_interface_copy(pl->supported_interfaces,
+ config->supported_interfaces);
+
pl->config = config;
if (config->type == PHYLINK_NETDEV) {
pl->netdev = to_net_dev(config->dev);
@@ -2011,7 +2019,7 @@ static int phylink_validate_phy(struct p
* those which the host supports.
*/
phy_interface_and(interfaces, phy->possible_interfaces,
- pl->config->supported_interfaces);
+ pl->supported_interfaces);
if (phy_interface_empty(interfaces)) {
phylink_err(pl, "PHY has no common interfaces\n");
@@ -2753,12 +2761,12 @@ static phy_interface_t phylink_sfp_selec
return interface;
}
- if (!test_bit(interface, pl->config->supported_interfaces)) {
+ if (!test_bit(interface, pl->supported_interfaces)) {
phylink_err(pl,
"selection of interface failed, SFP selected %s (%u) but MAC supports %*pbl\n",
phy_modes(interface), interface,
(int)PHY_INTERFACE_MODE_MAX,
- pl->config->supported_interfaces);
+ pl->supported_interfaces);
return PHY_INTERFACE_MODE_NA;
}
@@ -3686,14 +3694,14 @@ static int phylink_sfp_config_optical(st
phylink_dbg(pl, "optical SFP: interfaces=[mac=%*pbl, sfp=%*pbl]\n",
(int)PHY_INTERFACE_MODE_MAX,
- pl->config->supported_interfaces,
+ pl->supported_interfaces,
(int)PHY_INTERFACE_MODE_MAX,
pl->sfp_interfaces);
/* Find the union of the supported interfaces by the PCS/MAC and
* the SFP module.
*/
- phy_interface_and(pl->sfp_interfaces, pl->config->supported_interfaces,
+ phy_interface_and(pl->sfp_interfaces, pl->supported_interfaces,
pl->sfp_interfaces);
if (phy_interface_empty(pl->sfp_interfaces)) {
phylink_err(pl, "unsupported SFP module: no common interface modes\n");
@@ -3864,7 +3872,7 @@ static int phylink_sfp_connect_phy(void
/* Set the PHY's host supported interfaces */
phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces,
- pl->config->supported_interfaces);
+ pl->supported_interfaces);
/* Do the initial configuration */
return phylink_sfp_config_phy(pl, phy);
@@ -0,0 +1,384 @@
From d134e22b540226a7404cabb88c86a54857486b4f Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Mon, 31 Mar 2025 16:03:26 +0200
Subject: [PATCH 2/7] net: phylink: introduce internal phylink PCS handling
Introduce internal handling of PCS for phylink. This is an alternative
to .mac_select_pcs that moves the selection logic of the PCS entirely to
phylink with the usage of supported_interface value in the PCS struct.
MAC should now provide an array of available PCS in phylink_config in
.available_pcs and fill the .num_available_pcs with the number of
elements in the array. MAC should also define a new bitmap,
pcs_interfaces, in phylink_config to define for what interface mode a
dedicated PCS is required.
On phylink_create() this array is parsed and a linked list of PCS is
created based on the PCS passed in phylink_config.
Also the supported_interface value in phylink struct is updated with the
new supported_interface from the provided PCS.
On phylink_start() every PCS in phylink PCS list gets attached to the
phylink instance. This is done by setting the phylink value in
phylink_pcs struct to the phylink instance.
On phylink_stop(), every PCS in phylink PCS list is detached from the
phylink instance. This is done by setting the phylink value in
phylink_pcs struct to NULL.
On phylink_stop(), every PCS in phylink PCS list is removed from the
list.
phylink_validate_mac_and_pcs(), phylink_major_config() and
phylink_inband_caps() are updated to support this new implementation
with the PCS list stored in phylink.
They will make use of phylink_validate_pcs_interface() that will loop
for every PCS in the phylink PCS available list and find one that supports
the passed interface.
phylink_validate_pcs_interface() apply the same logic of .mac_select_pcs
where if a supported_interface value is not set for the PCS struct, then
it's assumed every interface is supported.
It's required for a MAC that implement either a .mac_select_pcs or make
use of the PCS list implementation. Implementing both will result in a fail
on MAC/PCS validation.
phylink value in phylink_pcs struct with this implementation is used to
track from PCS side when it's attached to a phylink instance. PCS driver
will make use of this information to correctly detach from a phylink
instance if needed.
The .mac_select_pcs implementation is not changed but it's expected that
every MAC driver migrates to the new implementation to later deprecate
and remove .mac_select_pcs.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/net/phy/phylink.c | 149 +++++++++++++++++++++++++++++++++-----
include/linux/phylink.h | 11 +++
2 files changed, 141 insertions(+), 19 deletions(-)
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -59,6 +59,9 @@ struct phylink {
/* The link configuration settings */
struct phylink_link_state link_config;
+ /* List of available PCS */
+ struct list_head pcs_list;
+
/* What interface are supported by the current link.
* Can change on removal or addition of new PCS.
*/
@@ -149,6 +152,8 @@ static const phy_interface_t phylink_sfp
static DECLARE_PHY_INTERFACE_MASK(phylink_sfp_interfaces);
+static void phylink_run_resolve(struct phylink *pl);
+
/**
* phylink_set_port_modes() - set the port type modes in the ethtool mask
* @mask: ethtool link mode mask
@@ -512,22 +517,59 @@ static void phylink_validate_mask_caps(u
linkmode_and(state->advertising, state->advertising, mask);
}
+static int phylink_validate_pcs_interface(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ /* If PCS define an empty supported_interfaces value, assume
+ * all interface are supported.
+ */
+ if (phy_interface_empty(pcs->supported_interfaces))
+ return 0;
+
+ /* Ensure that this PCS supports the interface mode */
+ if (!test_bit(interface, pcs->supported_interfaces))
+ return -EINVAL;
+
+ return 0;
+}
+
static int phylink_validate_mac_and_pcs(struct phylink *pl,
unsigned long *supported,
struct phylink_link_state *state)
{
- struct phylink_pcs *pcs = NULL;
unsigned long capabilities;
+ struct phylink_pcs *pcs;
+ bool pcs_found = false;
int ret;
/* Get the PCS for this interface mode */
if (pl->mac_ops->mac_select_pcs) {
+ /* Make sure either PCS internal validation or .mac_select_pcs
+ * is used. Return error if both are defined.
+ */
+ if (!list_empty(&pl->pcs_list)) {
+ phylink_err(pl, "either phylink_pcs_add() or .mac_select_pcs must be used\n");
+ return -EINVAL;
+ }
+
pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
if (IS_ERR(pcs))
return PTR_ERR(pcs);
+
+ pcs_found = !!pcs;
+ } else {
+ /* Check every assigned PCS and search for one that supports
+ * the interface.
+ */
+ list_for_each_entry(pcs, &pl->pcs_list, list) {
+ if (!phylink_validate_pcs_interface(pcs, state->interface)) {
+ pcs_found = true;
+ break;
+ }
+ }
}
- if (pcs) {
+ if (pcs_found) {
/* The PCS, if present, must be setup before phylink_create()
* has been called. If the ops is not initialised, print an
* error and backtrace rather than oopsing the kernel.
@@ -539,13 +581,10 @@ static int phylink_validate_mac_and_pcs(
return -EINVAL;
}
- /* Ensure that this PCS supports the interface which the MAC
- * returned it for. It is an error for the MAC to return a PCS
- * that does not support the interface mode.
- */
- if (!phy_interface_empty(pcs->supported_interfaces) &&
- !test_bit(state->interface, pcs->supported_interfaces)) {
- phylink_err(pl, "MAC returned PCS which does not support %s\n",
+ /* Recheck PCS to handle legacy way for .mac_select_pcs */
+ ret = phylink_validate_pcs_interface(pcs, state->interface);
+ if (ret) {
+ phylink_err(pl, "selected PCS does not support %s\n",
phy_modes(state->interface));
return -EINVAL;
}
@@ -959,12 +998,22 @@ static unsigned int phylink_inband_caps(
phy_interface_t interface)
{
struct phylink_pcs *pcs;
+ bool pcs_found = false;
- if (!pl->mac_ops->mac_select_pcs)
- return 0;
+ if (pl->mac_ops->mac_select_pcs) {
+ pcs = pl->mac_ops->mac_select_pcs(pl->config,
+ interface);
+ pcs_found = !!pcs;
+ } else {
+ list_for_each_entry(pcs, &pl->pcs_list, list) {
+ if (!phylink_validate_pcs_interface(pcs, interface)) {
+ pcs_found = true;
+ break;
+ }
+ }
+ }
- pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
- if (!pcs)
+ if (!pcs_found)
return 0;
return phylink_pcs_inband_caps(pcs, interface);
@@ -1259,10 +1308,36 @@ static void phylink_major_config(struct
pl->major_config_failed = true;
return;
}
+ /* Find a PCS in available PCS list for the requested interface.
+ * This doesn't overwrite the previous .mac_select_pcs as either
+ * .mac_select_pcs or PCS list implementation are permitted.
+ *
+ * Skip searching if the MAC doesn't require a dedicaed PCS for
+ * the requested interface.
+ */
+ } else if (test_bit(state->interface, pl->config->pcs_interfaces)) {
+ bool pcs_found = false;
+
+ list_for_each_entry(pcs, &pl->pcs_list, list) {
+ if (!phylink_validate_pcs_interface(pcs,
+ state->interface)) {
+ pcs_found = true;
+ break;
+ }
+ }
+
+ if (!pcs_found) {
+ phylink_err(pl,
+ "couldn't find a PCS for %s\n",
+ phy_modes(state->interface));
- pcs_changed = pl->pcs != pcs;
+ pl->major_config_failed = true;
+ return;
+ }
}
+ pcs_changed = pl->pcs != pcs;
+
phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
phylink_dbg(pl, "major config, active %s/%s/%s\n",
@@ -1289,11 +1364,13 @@ static void phylink_major_config(struct
if (pcs_changed) {
phylink_pcs_disable(pl->pcs);
- if (pl->pcs)
- pl->pcs->phylink = NULL;
+ if (pl->mac_ops->mac_select_pcs) {
+ if (pl->pcs)
+ pl->pcs->phylink = NULL;
- if (pcs)
- pcs->phylink = pl;
+ if (pcs)
+ pcs->phylink = pl;
+ }
pl->pcs = pcs;
}
@@ -1840,8 +1917,9 @@ struct phylink *phylink_create(struct ph
phy_interface_t iface,
const struct phylink_mac_ops *mac_ops)
{
+ struct phylink_pcs *pcs;
struct phylink *pl;
- int ret;
+ int i, ret;
/* Validate the supplied configuration */
if (phy_interface_empty(config->supported_interfaces)) {
@@ -1857,9 +1935,21 @@ struct phylink *phylink_create(struct ph
mutex_init(&pl->phydev_mutex);
mutex_init(&pl->state_mutex);
INIT_WORK(&pl->resolve, phylink_resolve);
+ INIT_LIST_HEAD(&pl->pcs_list);
+
+ /* Fill the PCS list with available PCS from phylink config */
+ for (i = 0; i < config->num_available_pcs; i++) {
+ pcs = config->available_pcs[i];
+
+ list_add(&pcs->list, &pl->pcs_list);
+ }
phy_interface_copy(pl->supported_interfaces,
config->supported_interfaces);
+ list_for_each_entry(pcs, &pl->pcs_list, list)
+ phy_interface_or(pl->supported_interfaces,
+ pl->supported_interfaces,
+ pcs->supported_interfaces);
pl->config = config;
if (config->type == PHYLINK_NETDEV) {
@@ -1938,10 +2028,16 @@ EXPORT_SYMBOL_GPL(phylink_create);
*/
void phylink_destroy(struct phylink *pl)
{
+ struct phylink_pcs *pcs, *tmp;
+
sfp_bus_del_upstream(pl->sfp_bus);
if (pl->link_gpio)
gpiod_put(pl->link_gpio);
+ /* Remove every PCS from phylink PCS list */
+ list_for_each_entry_safe(pcs, tmp, &pl->pcs_list, list)
+ list_del(&pcs->list);
+
cancel_work_sync(&pl->resolve);
kfree(pl);
}
@@ -2443,6 +2539,7 @@ static irqreturn_t phylink_link_handler(
*/
void phylink_start(struct phylink *pl)
{
+ struct phylink_pcs *pcs;
bool poll = false;
ASSERT_RTNL();
@@ -2469,6 +2566,10 @@ void phylink_start(struct phylink *pl)
pl->pcs_state = PCS_STATE_STARTED;
+ /* link available PCS to phylink struct */
+ list_for_each_entry(pcs, &pl->pcs_list, list)
+ pcs->phylink = pl;
+
phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED);
if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) {
@@ -2513,6 +2614,8 @@ EXPORT_SYMBOL_GPL(phylink_start);
*/
void phylink_stop(struct phylink *pl)
{
+ struct phylink_pcs *pcs;
+
ASSERT_RTNL();
if (pl->sfp_bus)
@@ -2530,6 +2633,14 @@ void phylink_stop(struct phylink *pl)
pl->pcs_state = PCS_STATE_DOWN;
phylink_pcs_disable(pl->pcs);
+
+ /* Drop link between phylink and PCS */
+ list_for_each_entry(pcs, &pl->pcs_list, list)
+ pcs->phylink = NULL;
+
+ /* Restore original supported interfaces */
+ phy_interface_copy(pl->supported_interfaces,
+ pl->config->supported_interfaces);
}
EXPORT_SYMBOL_GPL(phylink_stop);
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -150,12 +150,16 @@ enum phylink_op_type {
* if MAC link is at %MLO_AN_FIXED mode.
* @supported_interfaces: bitmap describing which PHY_INTERFACE_MODE_xxx
* are supported by the MAC/PCS.
+ * @pcs_interfaces: bitmap describing for which PHY_INTERFACE_MODE_xxx a
+ * dedicated PCS is required.
* @lpi_interfaces: bitmap describing which PHY interface modes can support
* LPI signalling.
* @mac_capabilities: MAC pause/speed/duplex capabilities.
* @lpi_capabilities: MAC speeds which can support LPI signalling
* @lpi_timer_default: Default EEE LPI timer setting.
* @eee_enabled_default: If set, EEE will be enabled by phylink at creation time
+ * @available_pcs: array of available phylink_pcs PCS
+ * @num_available_pcs: num of available phylink_pcs PCS
*/
struct phylink_config {
struct device *dev;
@@ -168,11 +172,15 @@ struct phylink_config {
void (*get_fixed_state)(struct phylink_config *config,
struct phylink_link_state *state);
DECLARE_PHY_INTERFACE_MASK(supported_interfaces);
+ DECLARE_PHY_INTERFACE_MASK(pcs_interfaces);
DECLARE_PHY_INTERFACE_MASK(lpi_interfaces);
unsigned long mac_capabilities;
unsigned long lpi_capabilities;
u32 lpi_timer_default;
bool eee_enabled_default;
+
+ struct phylink_pcs **available_pcs;
+ unsigned int num_available_pcs;
};
void phylink_limit_mac_speed(struct phylink_config *config, u32 max_speed);
@@ -468,6 +476,9 @@ struct phylink_pcs {
struct phylink *phylink;
bool poll;
bool rxc_always_on;
+
+ /* private: */
+ struct list_head list;
};
/**
@@ -0,0 +1,135 @@
From 1cb4e56c3ba32ac1bce89dc9c34ef2dbc9b89ad4 Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Mon, 31 Mar 2025 19:10:24 +0200
Subject: [PATCH 3/7] net: phylink: add phylink_release_pcs() to externally
release a PCS
Add phylink_release_pcs() to externally release a PCS from a phylink
instance. This can be used to handle case when a single PCS needs to be
removed and the phylink instance needs to be refreshed.
On calling phylink_release_pcs(), the PCS will be removed from the
phylink internal PCS list and the phylink supported_interfaces value is
reparsed with the remaining PCS interfaces.
Also a phylink resolve is triggered to handle the PCS removal.
It's also added to phylink a flag to make phylink resolve reconfigure
the interface mode (even if it didn't change). This is needed to handle
the special case when the current PCS used by phylink is removed and a
major_config is needed to propagae the configuration change. With this
option enabled we also force mac_config even if the PHY link is not up
for the in-band case.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/net/phy/phylink.c | 57 ++++++++++++++++++++++++++++++++++++++-
include/linux/phylink.h | 2 ++
2 files changed, 58 insertions(+), 1 deletion(-)
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -86,6 +86,7 @@ struct phylink {
bool link_failed;
bool suspend_link_up;
bool major_config_failed;
+ bool reconfig_interface;
bool mac_supports_eee_ops;
bool mac_supports_eee;
bool phy_enable_tx_lpi;
@@ -917,6 +918,55 @@ static void phylink_resolve_an_pause(str
}
}
+/**
+ * phylink_release_pcs - Removes a PCS from the phylink PCS available list
+ * @pcs: a pointer to the phylink_pcs struct to be released
+ *
+ * This function release a PCS from the phylink PCS available list if
+ * actually in use. It also refreshes the supported interfaces of the
+ * phylink instance by copying the supported interfaces from the phylink
+ * conf and merging the supported interfaces of the remaining available PCS
+ * in the list and trigger a resolve.
+ */
+void phylink_release_pcs(struct phylink_pcs *pcs)
+{
+ struct phylink *pl;
+
+ ASSERT_RTNL();
+
+ pl = pcs->phylink;
+ if (!pl)
+ return;
+
+ list_del(&pcs->list);
+ pcs->phylink = NULL;
+
+ /* Check if we are removing the PCS currently
+ * in use by phylink. If this is the case,
+ * force phylink resolve to reconfigure the interface
+ * mode and set the phylink PCS to NULL.
+ */
+ if (pl->pcs == pcs) {
+ mutex_lock(&pl->state_mutex);
+
+ pl->reconfig_interface = true;
+ pl->pcs = NULL;
+
+ mutex_unlock(&pl->state_mutex);
+ }
+
+ /* Refresh supported interfaces */
+ phy_interface_copy(pl->supported_interfaces,
+ pl->config->supported_interfaces);
+ list_for_each_entry(pcs, &pl->pcs_list, list)
+ phy_interface_or(pl->supported_interfaces,
+ pl->supported_interfaces,
+ pcs->supported_interfaces);
+
+ phylink_run_resolve(pl);
+}
+EXPORT_SYMBOL_GPL(phylink_release_pcs);
+
static unsigned int phylink_pcs_inband_caps(struct phylink_pcs *pcs,
phy_interface_t interface)
{
@@ -1730,6 +1780,10 @@ static void phylink_resolve(struct work_
if (phy)
link_state.link &= pl->phy_state.link;
+ /* Force mac_config if we need to reconfig the interface */
+ if (pl->reconfig_interface)
+ mac_config = true;
+
/* Only update if the PHY link is up */
if (phy && pl->phy_state.link) {
/* If the interface has changed, force a link down
@@ -1763,7 +1817,8 @@ static void phylink_resolve(struct work_
if (pl->act_link_an_mode != MLO_AN_FIXED)
phylink_apply_manual_flow(pl, &link_state);
- if (mac_config && link_state.interface != pl->link_config.interface) {
+ if ((mac_config && link_state.interface != pl->link_config.interface) ||
+ pl->reconfig_interface) {
/* The interface has changed, so force the link down and then
* reconfigure.
*/
@@ -1773,6 +1828,7 @@ static void phylink_resolve(struct work_
}
phylink_major_config(pl, false, &link_state);
pl->link_config.interface = link_state.interface;
+ pl->reconfig_interface = false;
}
/* If configuration of the interface failed, force the link down
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -717,6 +717,8 @@ void phylink_disconnect_phy(struct phyli
int phylink_set_fixed_link(struct phylink *,
const struct phylink_link_state *);
+void phylink_release_pcs(struct phylink_pcs *pcs);
+
void phylink_mac_change(struct phylink *, bool up);
void phylink_pcs_change(struct phylink_pcs *, bool up);
@@ -0,0 +1,384 @@
From a90c644c73bbffd400cd3839fc17ffdfc69ea1e8 Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Mon, 17 Mar 2025 01:46:53 +0100
Subject: [PATCH 4/7] net: pcs: implement Firmware node support for PCS driver
Implement the foundation of Firmware node support for PCS driver.
To support this, implement a simple Provider API where a PCS driver can
expose multiple PCS with an xlate .get function.
PCS driver will have to call fwnode_pcs_add_provider() and pass the
firmware node pointer and a xlate function to return the correct PCS for
the passed #pcs-cells.
This will register the PCS in a global list of providers so that
consumer can access it.
Consumer will then use fwnode_pcs_get() to get the actual PCS by passing
the firmware node pointer and the index for #pcs-cells.
For simple implementation where #pcs-cells is 0 and the PCS driver
expose a single PCS, the xlate function fwnode_pcs_simple_get() is
provided.
For advanced implementation a custom xlate function is required.
PCS driver on removal should first delete as a provider with
the usage of fwnode_pcs_del_provider() and then call
phylink_release_pcs() on every PCS the driver provides and
A generic function fwnode_phylink_pcs_parse() is provided for any MAC
driver that will declare PCS in DT (or ACPI).
This function will parse "pcs-handle" property and fill the passed array
with the parsed PCS in availabel_pcs up to the passed num_pcs value.
It's also possible to pass NULL as array to only parse the PCS and
update the num_pcs value with the count of scanned PCS.
Co-developed-by: Daniel Golle <daniel@makrotopia.org>
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/net/pcs/Kconfig | 7 ++
drivers/net/pcs/Makefile | 1 +
drivers/net/pcs/pcs.c | 201 +++++++++++++++++++++++++++++++
include/linux/pcs/pcs-provider.h | 41 +++++++
include/linux/pcs/pcs.h | 56 +++++++++
5 files changed, 306 insertions(+)
create mode 100644 drivers/net/pcs/pcs.c
create mode 100644 include/linux/pcs/pcs-provider.h
create mode 100644 include/linux/pcs/pcs.h
--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig
@@ -5,6 +5,13 @@
menu "PCS device drivers"
+config FWNODE_PCS
+ tristate
+ depends on (ACPI || OF)
+ depends on PHYLINK
+ help
+ Firmware node PCS accessors
+
config PCS_XPCS
tristate "Synopsys DesignWare Ethernet XPCS"
select PHYLINK
--- a/drivers/net/pcs/Makefile
+++ b/drivers/net/pcs/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for Linux PCS drivers
+obj-$(CONFIG_FWNODE_PCS) += pcs.o
pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-plat.o \
pcs-xpcs-nxp.o pcs-xpcs-wx.o
--- /dev/null
+++ b/drivers/net/pcs/pcs.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/phylink.h>
+#include <linux/pcs/pcs.h>
+#include <linux/pcs/pcs-provider.h>
+
+MODULE_DESCRIPTION("PCS library");
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_LICENSE("GPL");
+
+struct fwnode_pcs_provider {
+ struct list_head link;
+
+ struct fwnode_handle *fwnode;
+ struct phylink_pcs *(*get)(struct fwnode_reference_args *pcsspec,
+ void *data);
+
+ void *data;
+};
+
+static LIST_HEAD(fwnode_pcs_providers);
+static DEFINE_MUTEX(fwnode_pcs_mutex);
+
+struct phylink_pcs *fwnode_pcs_simple_get(struct fwnode_reference_args *pcsspec,
+ void *data)
+{
+ return data;
+}
+EXPORT_SYMBOL_GPL(fwnode_pcs_simple_get);
+
+int fwnode_pcs_add_provider(struct fwnode_handle *fwnode,
+ struct phylink_pcs *(*get)(struct fwnode_reference_args *pcsspec,
+ void *data),
+ void *data)
+{
+ struct fwnode_pcs_provider *pp;
+
+ if (!fwnode)
+ return 0;
+
+ pp = kzalloc(sizeof(*pp), GFP_KERNEL);
+ if (!pp)
+ return -ENOMEM;
+
+ pp->fwnode = fwnode_handle_get(fwnode);
+ pp->data = data;
+ pp->get = get;
+
+ mutex_lock(&fwnode_pcs_mutex);
+ list_add(&pp->link, &fwnode_pcs_providers);
+ mutex_unlock(&fwnode_pcs_mutex);
+ pr_debug("Added pcs provider from %pfwf\n", fwnode);
+
+ fwnode_dev_initialized(fwnode, true);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fwnode_pcs_add_provider);
+
+void fwnode_pcs_del_provider(struct fwnode_handle *fwnode)
+{
+ struct fwnode_pcs_provider *pp;
+
+ if (!fwnode)
+ return;
+
+ mutex_lock(&fwnode_pcs_mutex);
+ list_for_each_entry(pp, &fwnode_pcs_providers, link) {
+ if (pp->fwnode == fwnode) {
+ list_del(&pp->link);
+ fwnode_dev_initialized(pp->fwnode, false);
+ fwnode_handle_put(pp->fwnode);
+ kfree(pp);
+ break;
+ }
+ }
+ mutex_unlock(&fwnode_pcs_mutex);
+}
+EXPORT_SYMBOL_GPL(fwnode_pcs_del_provider);
+
+static int fwnode_parse_pcsspec(const struct fwnode_handle *fwnode, int index,
+ const char *name,
+ struct fwnode_reference_args *out_args)
+{
+ int ret = -ENOENT;
+
+ if (!fwnode)
+ return -ENOENT;
+
+ if (name)
+ index = fwnode_property_match_string(fwnode, "pcs-names",
+ name);
+
+ ret = fwnode_property_get_reference_args(fwnode, "pcs-handle",
+ "#pcs-cells",
+ -1, index, out_args);
+ if (ret || (name && index < 0))
+ return ret;
+
+ return 0;
+}
+
+static struct phylink_pcs *
+fwnode_pcs_get_from_pcsspec(struct fwnode_reference_args *pcsspec)
+{
+ struct fwnode_pcs_provider *provider;
+ struct phylink_pcs *pcs = ERR_PTR(-EPROBE_DEFER);
+
+ if (!pcsspec)
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&fwnode_pcs_mutex);
+ list_for_each_entry(provider, &fwnode_pcs_providers, link) {
+ if (provider->fwnode == pcsspec->fwnode) {
+ pcs = provider->get(pcsspec, provider->data);
+ if (!IS_ERR(pcs))
+ break;
+ }
+ }
+ mutex_unlock(&fwnode_pcs_mutex);
+
+ return pcs;
+}
+
+static struct phylink_pcs *__fwnode_pcs_get(struct fwnode_handle *fwnode,
+ int index, const char *con_id)
+{
+ struct fwnode_reference_args pcsspec;
+ struct phylink_pcs *pcs;
+ int ret;
+
+ ret = fwnode_parse_pcsspec(fwnode, index, con_id, &pcsspec);
+ if (ret)
+ return ERR_PTR(ret);
+
+ pcs = fwnode_pcs_get_from_pcsspec(&pcsspec);
+ fwnode_handle_put(pcsspec.fwnode);
+
+ return pcs;
+}
+
+struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode, int index)
+{
+ return __fwnode_pcs_get(fwnode, index, NULL);
+}
+EXPORT_SYMBOL_GPL(fwnode_pcs_get);
+
+static int fwnode_phylink_pcs_count(struct fwnode_handle *fwnode,
+ unsigned int *num_pcs)
+{
+ struct fwnode_reference_args out_args;
+ int index = 0;
+ int ret;
+
+ while (true) {
+ ret = fwnode_property_get_reference_args(fwnode, "pcs-handle",
+ "#pcs-cells",
+ -1, index, &out_args);
+ /* We expect to reach an -ENOENT error while counting */
+ if (ret)
+ break;
+
+ fwnode_handle_put(out_args.fwnode);
+ index++;
+ }
+
+ /* Update num_pcs with parsed PCS */
+ *num_pcs = index;
+
+ /* Return error if we didn't found any PCS */
+ return index > 0 ? 0 : -ENOENT;
+}
+
+int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
+ struct phylink_pcs **available_pcs,
+ unsigned int *num_pcs)
+{
+ int i;
+
+ if (!fwnode_property_present(fwnode, "pcs-handle"))
+ return -ENODEV;
+
+ /* With available_pcs NULL, only count the PCS */
+ if (!available_pcs)
+ return fwnode_phylink_pcs_count(fwnode, num_pcs);
+
+ for (i = 0; i < *num_pcs; i++) {
+ struct phylink_pcs *pcs;
+
+ pcs = fwnode_pcs_get(fwnode, i);
+ if (IS_ERR(pcs))
+ return PTR_ERR(pcs);
+
+ available_pcs[i] = pcs;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fwnode_phylink_pcs_parse);
--- /dev/null
+++ b/include/linux/pcs/pcs-provider.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __LINUX_PCS_PROVIDER_H
+#define __LINUX_PCS_PROVIDER_H
+
+/**
+ * fwnode_pcs_simple_get - Simple xlate function to retrieve PCS
+ * @pcsspec: reference arguments
+ * @data: Context data (assumed assigned to the single PCS)
+ *
+ * Returns the PCS. (pointed by data)
+ */
+struct phylink_pcs *fwnode_pcs_simple_get(struct fwnode_reference_args *pcsspec,
+ void *data);
+
+/**
+ * fwnode_pcs_add_provider - Registers a new PCS provider
+ * @np: Firmware node
+ * @get: xlate function to retrieve the PCS
+ * @data: Context data
+ *
+ * Register and add a new PCS to the global providers list
+ * for the firmware node. A function to get the PCS from
+ * firmware node with the use fwnode reference arguments.
+ * To the get function is also passed the interface type
+ * requested for the PHY. PCS driver will use the passed
+ * interface to understand if the PCS can support it or not.
+ *
+ * Returns 0 on success or -ENOMEM on allocation failure.
+ */
+int fwnode_pcs_add_provider(struct fwnode_handle *fwnode,
+ struct phylink_pcs *(*get)(struct fwnode_reference_args *pcsspec,
+ void *data),
+ void *data);
+
+/**
+ * fwnode_pcs_del_provider - Removes a PCS provider
+ * @fwnode: Firmware node
+ */
+void fwnode_pcs_del_provider(struct fwnode_handle *fwnode);
+
+#endif /* __LINUX_PCS_PROVIDER_H */
--- /dev/null
+++ b/include/linux/pcs/pcs.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __LINUX_PCS_H
+#define __LINUX_PCS_H
+
+#include <linux/phylink.h>
+
+#if IS_ENABLED(CONFIG_FWNODE_PCS)
+/**
+ * fwnode_pcs_get - Retrieves a PCS from a firmware node
+ * @fwnode: firmware node
+ * @index: index fwnode PCS handle in firmware node
+ *
+ * Get a PCS from the firmware node at index.
+ *
+ * Returns a pointer to the phylink_pcs or a negative
+ * error pointer. Can return -EPROBE_DEFER if the PCS is not
+ * present in global providers list (either due to driver
+ * still needs to be probed or it failed to probe/removed)
+ */
+struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode,
+ int index);
+
+/**
+ * fwnode_phylink_pcs_parse - generic PCS parse for fwnode PCS provider
+ * @fwnode: firmware node
+ * @available_pcs: pointer to preallocated array of PCS
+ * @num_pcs: where to store count of parsed PCS
+ *
+ * Generic helper function to fill available_pcs array with PCS parsed
+ * from a "pcs-handle" fwnode property defined in firmware node up to
+ * passed num_pcs.
+ *
+ * If available_pcs is NULL, num_pcs is updated with the count of the
+ * parsed PCS.
+ *
+ * Returns 0 or a negative error.
+ */
+int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
+ struct phylink_pcs **available_pcs,
+ unsigned int *num_pcs);
+#else
+static inline struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode,
+ int index)
+{
+ return ERR_PTR(-ENOENT);
+}
+
+static inline int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
+ struct phylink_pcs **available_pcs,
+ unsigned int *num_pcs)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
+#endif /* __LINUX_PCS_H */
@@ -0,0 +1,254 @@
From 684e49a015f2c5ae95ba968bb21ffc8fc36a2c7f Mon Sep 17 00:00:00 2001
From: Christian Marangi <ansuelsmth@gmail.com>
Date: Sun, 6 Apr 2025 03:28:22 +0200
Subject: [PATCH 5/7] net: phylink: support late PCS provider attach
Add support in phylink for late PCS provider attach to a phylink
instance. This works by creating a global notifier for the PCS provider
and making each phylink instance that makes use of fwnode subscribe to
this notifier.
The PCS notifier will emit the event FWNODE_PCS_PROVIDER_ADD every time
a new PCS provider is added.
phylink will then react to this event and will call the new function
fwnode_phylink_pcs_get_from_fwnode() that will check if the PCS fwnode
provided by the event is present in the phy-handle property of the
phylink instance.
If a related PCS is found, then such PCS is added to the phylink
instance PCS list.
Then we link the PCS to the phylink instance if it's not disable and we
refresh the supported interfaces of the phylink instance.
Finally we check if we are in a major_config_failed scenario and trigger
an interface reconfiguration in the next phylink resolve.
If link was previously torn down due to removal of PCS, the link will be
established again as the PCS came back and is not available to phylink.
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
drivers/net/pcs/pcs.c | 34 +++++++++++++++++++++++++
drivers/net/phy/phylink.c | 52 +++++++++++++++++++++++++++++++++++++++
include/linux/pcs/pcs.h | 48 ++++++++++++++++++++++++++++++++++++
3 files changed, 134 insertions(+)
--- a/drivers/net/pcs/pcs.c
+++ b/drivers/net/pcs/pcs.c
@@ -22,6 +22,13 @@ struct fwnode_pcs_provider {
static LIST_HEAD(fwnode_pcs_providers);
static DEFINE_MUTEX(fwnode_pcs_mutex);
+static BLOCKING_NOTIFIER_HEAD(fwnode_pcs_notify_list);
+
+int register_fwnode_pcs_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&fwnode_pcs_notify_list, nb);
+}
+EXPORT_SYMBOL_GPL(register_fwnode_pcs_notifier);
struct phylink_pcs *fwnode_pcs_simple_get(struct fwnode_reference_args *pcsspec,
void *data)
@@ -55,6 +62,10 @@ int fwnode_pcs_add_provider(struct fwnod
fwnode_dev_initialized(fwnode, true);
+ blocking_notifier_call_chain(&fwnode_pcs_notify_list,
+ FWNODE_PCS_PROVIDER_ADD,
+ fwnode);
+
return 0;
}
EXPORT_SYMBOL_GPL(fwnode_pcs_add_provider);
@@ -147,6 +158,29 @@ struct phylink_pcs *fwnode_pcs_get(struc
}
EXPORT_SYMBOL_GPL(fwnode_pcs_get);
+struct phylink_pcs *
+fwnode_phylink_pcs_get_from_fwnode(struct fwnode_handle *fwnode,
+ struct fwnode_handle *pcs_fwnode)
+{
+ struct fwnode_reference_args pcsspec;
+ int i = 0;
+ int ret;
+
+ while (true) {
+ ret = fwnode_parse_pcsspec(fwnode, i, NULL, &pcsspec);
+ if (ret)
+ break;
+
+ if (pcsspec.fwnode == pcs_fwnode)
+ break;
+
+ i++;
+ }
+
+ return fwnode_pcs_get(fwnode, i);
+}
+EXPORT_SYMBOL_GPL(fwnode_phylink_pcs_get_from_fwnode);
+
static int fwnode_phylink_pcs_count(struct fwnode_handle *fwnode,
unsigned int *num_pcs)
{
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -12,6 +12,7 @@
#include <linux/netdevice.h>
#include <linux/of.h>
#include <linux/of_mdio.h>
+#include <linux/pcs/pcs.h>
#include <linux/phy.h>
#include <linux/phy_fixed.h>
#include <linux/phylink.h>
@@ -61,6 +62,7 @@ struct phylink {
/* List of available PCS */
struct list_head pcs_list;
+ struct notifier_block fwnode_pcs_nb;
/* What interface are supported by the current link.
* Can change on removal or addition of new PCS.
@@ -1952,6 +1954,51 @@ int phylink_set_fixed_link(struct phylin
}
EXPORT_SYMBOL_GPL(phylink_set_fixed_link);
+static int pcs_provider_notify(struct notifier_block *self,
+ unsigned long val, void *data)
+{
+ struct phylink *pl = container_of(self, struct phylink, fwnode_pcs_nb);
+ struct fwnode_handle *pcs_fwnode = data;
+ struct phylink_pcs *pcs;
+
+ /* Check if the just added PCS provider is
+ * in the phylink instance phy-handle property
+ */
+ pcs = fwnode_phylink_pcs_get_from_fwnode(dev_fwnode(pl->config->dev),
+ pcs_fwnode);
+ if (IS_ERR(pcs))
+ return NOTIFY_DONE;
+
+ /* Add the PCS */
+ rtnl_lock();
+
+ list_add(&pcs->list, &pl->pcs_list);
+
+ /* Link phylink if we are started */
+ if (!pl->phylink_disable_state)
+ pcs->phylink = pl;
+
+ /* Refresh supported interfaces */
+ phy_interface_copy(pl->supported_interfaces,
+ pl->config->supported_interfaces);
+ list_for_each_entry(pcs, &pl->pcs_list, list)
+ phy_interface_or(pl->supported_interfaces,
+ pl->supported_interfaces,
+ pcs->supported_interfaces);
+
+ mutex_lock(&pl->state_mutex);
+ /* Force an interface reconfig if major config fail */
+ if (pl->major_config_failed)
+ pl->reconfig_interface = true;
+ mutex_unlock(&pl->state_mutex);
+
+ rtnl_unlock();
+
+ phylink_run_resolve(pl);
+
+ return NOTIFY_OK;
+}
+
/**
* phylink_create() - create a phylink instance
* @config: a pointer to the target &struct phylink_config
@@ -2007,6 +2054,11 @@ struct phylink *phylink_create(struct ph
pl->supported_interfaces,
pcs->supported_interfaces);
+ if (!phy_interface_empty(config->pcs_interfaces)) {
+ pl->fwnode_pcs_nb.notifier_call = pcs_provider_notify;
+ register_fwnode_pcs_notifier(&pl->fwnode_pcs_nb);
+ }
+
pl->config = config;
if (config->type == PHYLINK_NETDEV) {
pl->netdev = to_net_dev(config->dev);
--- a/include/linux/pcs/pcs.h
+++ b/include/linux/pcs/pcs.h
@@ -4,8 +4,25 @@
#include <linux/phylink.h>
+enum fwnode_pcs_notify_event {
+ FWNODE_PCS_PROVIDER_ADD,
+};
+
#if IS_ENABLED(CONFIG_FWNODE_PCS)
/**
+ * register_fwnode_pcs_notifier - Register a notifier block for fwnode
+ * PCS events
+ * @nb: pointer to the notifier block
+ *
+ * Registers a notifier block to the fwnode_pcs_notify_list blocking
+ * notifier chain. This allows phylink instance to subscribe for
+ * PCS provider events.
+ *
+ * Returns 0 or a negative error.
+ */
+int register_fwnode_pcs_notifier(struct notifier_block *nb);
+
+/**
* fwnode_pcs_get - Retrieves a PCS from a firmware node
* @fwnode: firmware node
* @index: index fwnode PCS handle in firmware node
@@ -21,6 +38,25 @@ struct phylink_pcs *fwnode_pcs_get(struc
int index);
/**
+ * fwnode_phylink_pcs_get_from_fwnode - Retrieves the PCS provided
+ * by the firmware node from a
+ * firmware node
+ * @fwnode: firmware node
+ * @pcs_fwnode: PCS firmware node
+ *
+ * Parse 'pcs-handle' in 'fwnode' and get the PCS that match
+ * 'pcs_fwnode' firmware node.
+ *
+ * Returns a pointer to the phylink_pcs or a negative
+ * error pointer. Can return -EPROBE_DEFER if the PCS is not
+ * present in global providers list (either due to driver
+ * still needs to be probed or it failed to probe/removed)
+ */
+struct phylink_pcs *
+fwnode_phylink_pcs_get_from_fwnode(struct fwnode_handle *fwnode,
+ struct fwnode_handle *pcs_fwnode);
+
+/**
* fwnode_phylink_pcs_parse - generic PCS parse for fwnode PCS provider
* @fwnode: firmware node
* @available_pcs: pointer to preallocated array of PCS
@@ -39,11 +75,23 @@ int fwnode_phylink_pcs_parse(struct fwno
struct phylink_pcs **available_pcs,
unsigned int *num_pcs);
#else
+static inline int register_fwnode_pcs_notifier(struct notifier_block *nb)
+{
+ return -EOPNOTSUPP;
+}
+
static inline struct phylink_pcs *fwnode_pcs_get(struct fwnode_handle *fwnode,
int index)
{
return ERR_PTR(-ENOENT);
}
+
+static inline struct phylink_pcs *
+fwnode_phylink_pcs_get_from_fwnode(struct fwnode_handle *fwnode,
+ struct fwnode_handle *pcs_fwnode)
+{
+ return ERR_PTR(-ENOENT);
+}
static inline int fwnode_phylink_pcs_parse(struct fwnode_handle *fwnode,
struct phylink_pcs **available_pcs,

Some files were not shown because too many files have changed in this diff Show More