Merge branch 'next' of https://source.denx.de/u-boot/custodians/u-boot-net into next

- Various DSA additions
- bootp: fix for VCI string
- tsec: support for promiscuous mode
- add Aspeed MDIO driver
diff --git a/arch/arm/dts/ls1021a-tsn.dts b/arch/arm/dts/ls1021a-tsn.dts
index 8e0f4ea..68f5543 100644
--- a/arch/arm/dts/ls1021a-tsn.dts
+++ b/arch/arm/dts/ls1021a-tsn.dts
@@ -14,9 +14,84 @@
 		enet1-sgmii-phy = &sgmii_phy1;
 		spi0 = &qspi;
 		spi1 = &dspi1;
+		ethernet0 = &enet0;
+		ethernet1 = &enet1;
+		ethernet2 = &enet2;
+		ethernet3 = &swp2;
+		ethernet4 = &swp3;
+		ethernet5 = &swp4;
+		ethernet6 = &swp5;
 	};
 };
 
+&dspi0 {
+	bus-num = <0>;
+	status = "okay";
+
+	sja1105: ethernet-switch@1 {
+		reg = <0x1>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		compatible = "nxp,sja1105t";
+		/* 12 MHz */
+		spi-max-frequency = <12000000>;
+		/* Sample data on trailing clock edge */
+		spi-cpha;
+		/* SPI controller settings for SJA1105 timing requirements */
+		fsl,spi-cs-sck-delay = <1000>;
+		fsl,spi-sck-cs-delay = <1000>;
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			swp5: port@0 {
+				/* ETH5 written on chassis */
+				label = "swp5";
+				phy-handle = <&rgmii_phy6>;
+				phy-mode = "rgmii-id";
+				reg = <0>;
+			};
+
+			swp2: port@1 {
+				/* ETH2 written on chassis */
+				label = "swp2";
+				phy-handle = <&rgmii_phy3>;
+				phy-mode = "rgmii-id";
+				reg = <1>;
+			};
+
+			swp3: port@2 {
+				/* ETH3 written on chassis */
+				label = "swp3";
+				phy-handle = <&rgmii_phy4>;
+				phy-mode = "rgmii-id";
+				reg = <2>;
+			};
+
+			swp4: port@3 {
+				/* ETH4 written on chassis */
+				label = "swp4";
+				phy-handle = <&rgmii_phy5>;
+				phy-mode = "rgmii-id";
+				reg = <3>;
+			};
+
+			port@4 {
+				/* Internal port connected to eth2 */
+				ethernet = <&enet2>;
+				phy-mode = "rgmii";
+				reg = <4>;
+
+				fixed-link {
+					speed = <1000>;
+					full-duplex;
+				};
+			};
+		};
+	};
+};
+
 &enet0 {
 	tbi-handle = <&tbi0>;
 	phy-handle = <&sgmii_phy2>;
@@ -31,6 +106,17 @@
 	status = "okay";
 };
 
+/* RGMII delays added via PCB traces */
+&enet2 {
+	phy-mode = "rgmii";
+	status = "okay";
+
+	fixed-link {
+		speed = <1000>;
+		full-duplex;
+	};
+};
+
 &i2c0 {
 	status = "okay";
 };
@@ -46,6 +132,23 @@
 		reg = <0x2>;
 	};
 
+	/* BCM5464 quad PHY */
+	rgmii_phy3: ethernet-phy@3 {
+		reg = <0x3>;
+	};
+
+	rgmii_phy4: ethernet-phy@4 {
+		reg = <0x4>;
+	};
+
+	rgmii_phy5: ethernet-phy@5 {
+		reg = <0x5>;
+	};
+
+	rgmii_phy6: ethernet-phy@6 {
+		reg = <0x6>;
+	};
+
 	/* SGMII PCS for enet0 */
 	tbi0: tbi-phy@1f {
 		reg = <0x1f>;
diff --git a/boot/pxe_utils.c b/boot/pxe_utils.c
index a7a84f2..a32acca 100644
--- a/boot/pxe_utils.c
+++ b/boot/pxe_utils.c
@@ -550,7 +550,10 @@
 	 * Scenario 2: If there is an fdt_addr specified, pass it along to
 	 * bootm, and adjust argc appropriately.
 	 *
-	 * Scenario 3: fdt blob is not available.
+	 * Scenario 3: If there is an fdtcontroladdr specified, pass it along to
+	 * bootm, and adjust argc appropriately.
+	 *
+	 * Scenario 4: fdt blob is not available.
 	 */
 	bootm_argv[3] = env_get("fdt_addr_r");
 
@@ -652,6 +655,9 @@
 	if (!bootm_argv[3])
 		bootm_argv[3] = env_get("fdt_addr");
 
+	if (!bootm_argv[3])
+		bootm_argv[3] = env_get("fdtcontroladdr");
+
 	if (bootm_argv[3]) {
 		if (!bootm_argv[2])
 			bootm_argv[2] = "-";
diff --git a/configs/ls1021atsn_qspi_defconfig b/configs/ls1021atsn_qspi_defconfig
index d92fdf4..4385df6 100644
--- a/configs/ls1021atsn_qspi_defconfig
+++ b/configs/ls1021atsn_qspi_defconfig
@@ -51,8 +51,11 @@
 CONFIG_PHY_ATHEROS=y
 CONFIG_PHY_BROADCOM=y
 CONFIG_PHY_FIXED=y
+CONFIG_NET_RANDOM_ETHADDR=y
 CONFIG_DM_ETH=y
 CONFIG_DM_MDIO=y
+CONFIG_DM_DSA=y
+CONFIG_SJA1105=y
 CONFIG_PHY_GIGE=y
 CONFIG_MII=y
 CONFIG_TSEC_ENET=y
diff --git a/configs/ls1021atsn_sdcard_defconfig b/configs/ls1021atsn_sdcard_defconfig
index a745857..506a337 100644
--- a/configs/ls1021atsn_sdcard_defconfig
+++ b/configs/ls1021atsn_sdcard_defconfig
@@ -66,8 +66,11 @@
 CONFIG_PHY_ATHEROS=y
 CONFIG_PHY_BROADCOM=y
 CONFIG_PHY_FIXED=y
+CONFIG_NET_RANDOM_ETHADDR=y
 CONFIG_DM_ETH=y
 CONFIG_DM_MDIO=y
+CONFIG_DM_DSA=y
+CONFIG_SJA1105=y
 CONFIG_PHY_GIGE=y
 CONFIG_MII=y
 CONFIG_TSEC_ENET=y
diff --git a/configs/pine64_plus_defconfig b/configs/pine64_plus_defconfig
index d1c2c3c..f42f4e5 100644
--- a/configs/pine64_plus_defconfig
+++ b/configs/pine64_plus_defconfig
@@ -8,7 +8,6 @@
 # CONFIG_SYS_MALLOC_CLEAR_ON_INIT is not set
 CONFIG_OF_LIST="sun50i-a64-pine64 sun50i-a64-pine64-plus"
 CONFIG_PHY_REALTEK=y
-CONFIG_RTL8211E_PINE64_GIGABIT_FIX=y
 CONFIG_SUN8I_EMAC=y
 CONFIG_USB_EHCI_HCD=y
 CONFIG_USB_OHCI_HCD=y
diff --git a/drivers/core/of_extra.c b/drivers/core/of_extra.c
index 632a1c2..59ce917 100644
--- a/drivers/core/of_extra.c
+++ b/drivers/core/of_extra.c
@@ -155,3 +155,15 @@
 
 	return true;
 }
+
+bool ofnode_eth_uses_inband_aneg(ofnode eth_node)
+{
+	bool inband_aneg = false;
+	const char *managed;
+
+	managed = ofnode_read_string(eth_node, "managed");
+	if (managed && !strcmp(managed, "in-band-status"))
+		inband_aneg = true;
+
+	return inband_aneg;
+}
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index c1a4917..e054bec 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -554,6 +554,22 @@
 	  This driver supports Realtek 8169 series gigabit ethernet family of
 	  PCI/PCIe chipsets/adapters.
 
+config SJA1105
+	bool "NXP SJA1105 Ethernet switch family driver"
+	depends on DM_DSA && DM_SPI
+	select BITREVERSE
+	help
+	  This is the driver for the NXP SJA1105 automotive Ethernet switch
+	  family. These are 5-port devices and are managed over an SPI
+	  interface. Probing is handled based on OF bindings. The driver
+	  supports the following revisions:
+	    - SJA1105E (Gen. 1, No TT-Ethernet)
+	    - SJA1105T (Gen. 1, TT-Ethernet)
+	    - SJA1105P (Gen. 2, No SGMII, No TT-Ethernet)
+	    - SJA1105Q (Gen. 2, No SGMII, TT-Ethernet)
+	    - SJA1105R (Gen. 2, SGMII, No TT-Ethernet)
+	    - SJA1105S (Gen. 2, SGMII, TT-Ethernet)
+
 config SMC911X
 	bool "SMSC LAN911x and LAN921x controller driver"
 
@@ -835,6 +851,13 @@
 	  This driver supports the MDIO bus found on the Fman 10G Ethernet MACs and
 	  on the mEMAC (which supports both Clauses 22 and 45).
 
+config ASPEED_MDIO
+	bool "Aspeed MDIO interface support"
+	depends on DM_MDIO
+	help
+	  This driver supports the MDIO bus of Aspeed AST2600 SOC.  The driver
+	  currently supports Clause 22.
+
 config MDIO_MUX_MMIOREG
 	bool "MDIO MUX accessed as a MMIO register access"
 	depends on DM_MDIO_MUX
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index e4078d1..cf6294c 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -29,6 +29,7 @@
 obj-$(CONFIG_E1000) += e1000.o
 obj-$(CONFIG_E1000_SPI) += e1000_spi.o
 obj-$(CONFIG_EEPRO100) += eepro100.o
+obj-$(CONFIG_SJA1105) += sja1105.o
 obj-$(CONFIG_SUN4I_EMAC) += sunxi_emac.o
 obj-$(CONFIG_SUN8I_EMAC) += sun8i_emac.o
 obj-$(CONFIG_EP93XX) += ep93xx_eth.o
@@ -101,3 +102,4 @@
 obj-$(CONFIG_MDIO_SANDBOX) += mdio_sandbox.o
 obj-$(CONFIG_FSL_ENETC) += fsl_enetc.o fsl_enetc_mdio.o
 obj-$(CONFIG_FSL_LS_MDIO) += fsl_ls_mdio.o
+obj-$(CONFIG_ASPEED_MDIO) += aspeed_mdio.o
diff --git a/drivers/net/aspeed_mdio.c b/drivers/net/aspeed_mdio.c
new file mode 100644
index 0000000..a99715a
--- /dev/null
+++ b/drivers/net/aspeed_mdio.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Aspeed MDIO driver
+ *
+ * (C) Copyright 2021 Aspeed Technology Inc.
+ *
+ * This file is inspired from the Linux kernel driver drivers/net/phy/mdio-aspeed.c
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <log.h>
+#include <miiphy.h>
+#include <net.h>
+#include <reset.h>
+#include <linux/bitops.h>
+#include <linux/bitfield.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+
+#define ASPEED_MDIO_CTRL		0x0
+#define   ASPEED_MDIO_CTRL_FIRE		BIT(31)
+#define   ASPEED_MDIO_CTRL_ST		BIT(28)
+#define     ASPEED_MDIO_CTRL_ST_C45	0
+#define     ASPEED_MDIO_CTRL_ST_C22	1
+#define   ASPEED_MDIO_CTRL_OP		GENMASK(27, 26)
+#define     MDIO_C22_OP_WRITE		0b01
+#define     MDIO_C22_OP_READ		0b10
+#define   ASPEED_MDIO_CTRL_PHYAD	GENMASK(25, 21)
+#define   ASPEED_MDIO_CTRL_REGAD	GENMASK(20, 16)
+#define   ASPEED_MDIO_CTRL_MIIWDATA	GENMASK(15, 0)
+
+#define ASPEED_MDIO_DATA		0x4
+#define   ASPEED_MDIO_DATA_MDC_THRES	GENMASK(31, 24)
+#define   ASPEED_MDIO_DATA_MDIO_EDGE	BIT(23)
+#define   ASPEED_MDIO_DATA_MDIO_LATCH	GENMASK(22, 20)
+#define   ASPEED_MDIO_DATA_IDLE		BIT(16)
+#define   ASPEED_MDIO_DATA_MIIRDATA	GENMASK(15, 0)
+
+#define ASPEED_MDIO_TIMEOUT_US		1000
+
+struct aspeed_mdio_priv {
+	void *base;
+};
+
+static int aspeed_mdio_read(struct udevice *mdio_dev, int addr, int devad, int reg)
+{
+	struct aspeed_mdio_priv *priv = dev_get_priv(mdio_dev);
+	u32 ctrl;
+	u32 data;
+	int rc;
+
+	if (devad != MDIO_DEVAD_NONE)
+		return -EOPNOTSUPP;
+
+	ctrl = ASPEED_MDIO_CTRL_FIRE
+		| FIELD_PREP(ASPEED_MDIO_CTRL_ST, ASPEED_MDIO_CTRL_ST_C22)
+		| FIELD_PREP(ASPEED_MDIO_CTRL_OP, MDIO_C22_OP_READ)
+		| FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, addr)
+		| FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, reg);
+
+	writel(ctrl, priv->base + ASPEED_MDIO_CTRL);
+
+	rc = readl_poll_timeout(priv->base + ASPEED_MDIO_DATA, data,
+				data & ASPEED_MDIO_DATA_IDLE,
+				ASPEED_MDIO_TIMEOUT_US);
+
+	if (rc < 0)
+		return rc;
+
+	return FIELD_GET(ASPEED_MDIO_DATA_MIIRDATA, data);
+}
+
+static int aspeed_mdio_write(struct udevice *mdio_dev, int addr, int devad, int reg, u16 val)
+{
+	struct aspeed_mdio_priv *priv = dev_get_priv(mdio_dev);
+	u32 ctrl;
+
+	if (devad != MDIO_DEVAD_NONE)
+		return -EOPNOTSUPP;
+
+	ctrl = ASPEED_MDIO_CTRL_FIRE
+		| FIELD_PREP(ASPEED_MDIO_CTRL_ST, ASPEED_MDIO_CTRL_ST_C22)
+		| FIELD_PREP(ASPEED_MDIO_CTRL_OP, MDIO_C22_OP_WRITE)
+		| FIELD_PREP(ASPEED_MDIO_CTRL_PHYAD, addr)
+		| FIELD_PREP(ASPEED_MDIO_CTRL_REGAD, reg)
+		| FIELD_PREP(ASPEED_MDIO_CTRL_MIIWDATA, val);
+
+	writel(ctrl, priv->base + ASPEED_MDIO_CTRL);
+
+	return readl_poll_timeout(priv->base + ASPEED_MDIO_CTRL, ctrl,
+				  !(ctrl & ASPEED_MDIO_CTRL_FIRE),
+				  ASPEED_MDIO_TIMEOUT_US);
+}
+
+static const struct mdio_ops aspeed_mdio_ops = {
+	.read = aspeed_mdio_read,
+	.write = aspeed_mdio_write,
+};
+
+static int aspeed_mdio_probe(struct udevice *dev)
+{
+	struct aspeed_mdio_priv *priv = dev_get_priv(dev);
+	struct reset_ctl reset_ctl;
+	int ret = 0;
+
+	priv->base = dev_read_addr_ptr(dev);
+
+	ret = reset_get_by_index(dev, 0, &reset_ctl);
+	reset_deassert(&reset_ctl);
+
+	return 0;
+}
+
+static const struct udevice_id aspeed_mdio_ids[] = {
+	{ .compatible = "aspeed,ast2600-mdio" },
+	{ }
+};
+
+U_BOOT_DRIVER(aspeed_mdio) = {
+	.name = "aspeed_mdio",
+	.id = UCLASS_MDIO,
+	.of_match = aspeed_mdio_ids,
+	.probe = aspeed_mdio_probe,
+	.ops = &aspeed_mdio_ops,
+	.plat_auto = sizeof(struct mdio_perdev_priv),
+	.priv_auto = sizeof(struct aspeed_mdio_priv),
+};
diff --git a/drivers/net/fec_mxc.h b/drivers/net/fec_mxc.h
index 1c0d0e5..48faa33 100644
--- a/drivers/net/fec_mxc.h
+++ b/drivers/net/fec_mxc.h
@@ -272,7 +272,7 @@
 	struct clk clk_ref;
 	struct clk clk_ptp;
 	u32 clk_rate;
-	char promisc;
+	bool promisc;
 };
 
 /**
diff --git a/drivers/net/mscc_eswitch/felix_switch.c b/drivers/net/mscc_eswitch/felix_switch.c
index 551fc2c..2df8dde 100644
--- a/drivers/net/mscc_eswitch/felix_switch.c
+++ b/drivers/net/mscc_eswitch/felix_switch.c
@@ -16,6 +16,7 @@
  */
 
 #include <dm/device_compat.h>
+#include <dm/of_extra.h>
 #include <linux/delay.h>
 #include <net/dsa.h>
 #include <asm/io.h>
@@ -210,17 +211,14 @@
 static void felix_start_pcs(struct udevice *dev, int port,
 			    struct phy_device *phy, struct mii_dev *imdio)
 {
-	bool autoneg = true;
-
-	if (phy->phy_id == PHY_FIXED_ID ||
-	    phy->interface == PHY_INTERFACE_MODE_2500BASEX)
-		autoneg = false;
+	ofnode node = dsa_port_get_ofnode(dev, port);
+	bool inband_an = ofnode_eth_uses_inband_aneg(node);
 
 	switch (phy->interface) {
 	case PHY_INTERFACE_MODE_SGMII:
 	case PHY_INTERFACE_MODE_2500BASEX:
 	case PHY_INTERFACE_MODE_QSGMII:
-		felix_init_sgmii(imdio, port, autoneg);
+		felix_init_sgmii(imdio, port, inband_an);
 		break;
 	case PHY_INTERFACE_MODE_10GBASER:
 	case PHY_INTERFACE_MODE_USXGMII:
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 68ee7d7..e69cd8a 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -214,16 +214,6 @@
 config PHY_REALTEK
 	bool "Realtek Ethernet PHYs support"
 
-config RTL8211E_PINE64_GIGABIT_FIX
-	bool "Fix gigabit throughput on some Pine64+ models"
-	depends on PHY_REALTEK
-	help
-	  Configure the Realtek RTL8211E found on some Pine64+ models differently to
-	  fix throughput on Gigabit links, turning off all internal delays in the
-	  process. The settings that this touches are not documented in the CONFREG
-	  section of the RTL8211E datasheet, but come from Realtek by way of the
-	  Pine64 engineering team.
-
 config RTL8211X_PHY_FORCE_MASTER
 	bool "Ethernet PHY RTL8211x: force 1000BASE-T master mode"
 	depends on PHY_REALTEK
diff --git a/drivers/net/phy/mscc.c b/drivers/net/phy/mscc.c
index d1a643c..f9482b2 100644
--- a/drivers/net/phy/mscc.c
+++ b/drivers/net/phy/mscc.c
@@ -19,6 +19,7 @@
 /* Microsemi PHY ID's */
 #define PHY_ID_VSC8530                  0x00070560
 #define PHY_ID_VSC8531                  0x00070570
+#define PHY_ID_VSC8502			0x00070630
 #define PHY_ID_VSC8540                  0x00070760
 #define PHY_ID_VSC8541                  0x00070770
 #define PHY_ID_VSC8574			0x000704a0
@@ -1513,6 +1514,50 @@
 	return vsc8584_config_init(phydev);
 }
 
+static int vsc8502_config(struct phy_device *phydev)
+{
+	bool rgmii_rx_delay = false, rgmii_tx_delay = false;
+	u16 reg = 0;
+	int ret;
+
+	/* Assume nothing needs to be done for the default GMII/MII mode */
+	if (!phy_interface_is_rgmii(phydev))
+		return 0;
+
+	/* Set Extended PHY Control 1 register to RGMII */
+	phy_write(phydev, MDIO_DEVAD_NONE, MSCC_PHY_EXT_PHY_CNTL_1_REG,
+		  BIT(13) | BIT(12));
+
+	/* Soft reset required after changing PHY mode from the default
+	 * of GMII/MII
+	 */
+	ret = mscc_phy_soft_reset(phydev);
+	if (ret)
+		return ret;
+
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID ||
+	    phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
+		rgmii_rx_delay = true;
+	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID ||
+	    phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
+		rgmii_tx_delay = true;
+
+	phy_write(phydev, MDIO_DEVAD_NONE, MSCC_EXT_PAGE_ACCESS,
+		  MSCC_PHY_PAGE_EXT2);
+
+	if (rgmii_rx_delay)
+		reg |= VSC_PHY_RGMII_DELAY_2000_PS << RGMII_RX_CLK_DELAY_POS;
+	if (rgmii_tx_delay)
+		reg |= VSC_PHY_RGMII_DELAY_2000_PS << RGMII_TX_CLK_DELAY_POS;
+
+	phy_write(phydev, MDIO_DEVAD_NONE, MSCC_PHY_RGMII_CNTL_REG, reg);
+
+	phy_write(phydev, MDIO_DEVAD_NONE, MSCC_EXT_PAGE_ACCESS,
+		  MSCC_PHY_PAGE_STD);
+
+	return 0;
+}
+
 static struct phy_driver VSC8530_driver = {
 	.name = "Microsemi VSC8530",
 	.uid = PHY_ID_VSC8530,
@@ -1533,6 +1578,16 @@
 	.shutdown = &genphy_shutdown,
 };
 
+static struct phy_driver VSC8502_driver = {
+	.name = "Microsemi VSC8502",
+	.uid = PHY_ID_VSC8502,
+	.mask = 0x000ffff0,
+	.features = PHY_GBIT_FEATURES,
+	.config = &vsc8502_config,
+	.startup = &mscc_startup,
+	.shutdown = &genphy_shutdown,
+};
+
 static struct phy_driver VSC8540_driver = {
 	.name = "Microsemi VSC8540",
 	.uid = PHY_ID_VSC8540,
@@ -1577,6 +1632,7 @@
 {
 	phy_register(&VSC8530_driver);
 	phy_register(&VSC8531_driver);
+	phy_register(&VSC8502_driver);
 	phy_register(&VSC8540_driver);
 	phy_register(&VSC8541_driver);
 	phy_register(&VSC8574_driver);
diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c
index b1b1fa5..24c3ea5 100644
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -12,7 +12,6 @@
 #include <linux/delay.h>
 
 #define PHY_RTL8211x_FORCE_MASTER BIT(1)
-#define PHY_RTL8211E_PINE64_GIGABIT_FIX BIT(2)
 #define PHY_RTL8211F_FORCE_EEE_RXC_ON BIT(3)
 #define PHY_RTL8201F_S700_RMII_TIMINGS BIT(4)
 
@@ -49,10 +48,10 @@
 #define MIIM_RTL8211F_PHYSTAT_SPDDONE  0x0800
 #define MIIM_RTL8211F_PHYSTAT_LINK     0x0004
 
-#define MIIM_RTL8211E_CONFREG           0x1c
-#define MIIM_RTL8211E_CONFREG_TXD		0x0002
-#define MIIM_RTL8211E_CONFREG_RXD		0x0004
-#define MIIM_RTL8211E_CONFREG_MAGIC		0xb400	/* Undocumented */
+#define MIIM_RTL8211E_CONFREG		0x1c
+#define MIIM_RTL8211E_CTRL_DELAY	BIT(13)
+#define MIIM_RTL8211E_TX_DELAY		BIT(12)
+#define MIIM_RTL8211E_RX_DELAY		BIT(11)
 
 #define MIIM_RTL8211E_EXT_PAGE_SELECT  0x1e
 
@@ -108,10 +107,6 @@
 
 static int rtl8211e_probe(struct phy_device *phydev)
 {
-#ifdef CONFIG_RTL8211E_PINE64_GIGABIT_FIX
-	phydev->flags |= PHY_RTL8211E_PINE64_GIGABIT_FIX;
-#endif
-
 	return 0;
 }
 
@@ -154,22 +149,6 @@
 		reg |= MIIM_RTL8211x_CTRL1000T_MASTER;
 		phy_write(phydev, MDIO_DEVAD_NONE, MII_CTRL1000, reg);
 	}
-	if (phydev->flags & PHY_RTL8211E_PINE64_GIGABIT_FIX) {
-		unsigned int reg;
-
-		phy_write(phydev, MDIO_DEVAD_NONE, MIIM_RTL8211F_PAGE_SELECT,
-			  7);
-		phy_write(phydev, MDIO_DEVAD_NONE,
-			  MIIM_RTL8211E_EXT_PAGE_SELECT, 0xa4);
-		reg = phy_read(phydev, MDIO_DEVAD_NONE, MIIM_RTL8211E_CONFREG);
-		/* Ensure both internal delays are turned off */
-		reg &= ~(MIIM_RTL8211E_CONFREG_TXD | MIIM_RTL8211E_CONFREG_RXD);
-		/* Flip the magic undocumented bits */
-		reg |= MIIM_RTL8211E_CONFREG_MAGIC;
-		phy_write(phydev, MDIO_DEVAD_NONE, MIIM_RTL8211E_CONFREG, reg);
-		phy_write(phydev, MDIO_DEVAD_NONE, MIIM_RTL8211F_PAGE_SELECT,
-			  0);
-	}
 	/* read interrupt status just to clear it */
 	phy_read(phydev, MDIO_DEVAD_NONE, MIIM_RTL8211x_PHY_INER);
 
@@ -196,6 +175,44 @@
 			  0);
 	}
 
+	genphy_config_aneg(phydev);
+
+	return 0;
+}
+
+static int rtl8211e_config(struct phy_device *phydev)
+{
+	int reg, val;
+
+	/* enable TX/RX delay for rgmii-* modes, and disable them for rgmii. */
+	switch (phydev->interface) {
+	case PHY_INTERFACE_MODE_RGMII:
+		val = MIIM_RTL8211E_CTRL_DELAY;
+		break;
+	case PHY_INTERFACE_MODE_RGMII_ID:
+		val = MIIM_RTL8211E_CTRL_DELAY | MIIM_RTL8211E_TX_DELAY |
+		      MIIM_RTL8211E_RX_DELAY;
+		break;
+	case PHY_INTERFACE_MODE_RGMII_RXID:
+		val = MIIM_RTL8211E_CTRL_DELAY | MIIM_RTL8211E_RX_DELAY;
+		break;
+	case PHY_INTERFACE_MODE_RGMII_TXID:
+		val = MIIM_RTL8211E_CTRL_DELAY | MIIM_RTL8211E_TX_DELAY;
+		break;
+	default: /* the rest of the modes imply leaving delays as is. */
+		goto default_delay;
+	}
+
+	phy_write(phydev, MDIO_DEVAD_NONE, MIIM_RTL8211F_PAGE_SELECT, 7);
+	phy_write(phydev, MDIO_DEVAD_NONE, MIIM_RTL8211E_EXT_PAGE_SELECT, 0xa4);
+
+	reg = phy_read(phydev, MDIO_DEVAD_NONE, MIIM_RTL8211E_CONFREG);
+	reg &= ~(MIIM_RTL8211E_TX_DELAY | MIIM_RTL8211E_RX_DELAY);
+	phy_write(phydev, MDIO_DEVAD_NONE, MIIM_RTL8211E_CONFREG, reg | val);
+
+	phy_write(phydev, MDIO_DEVAD_NONE, MIIM_RTL8211F_PAGE_SELECT, 0);
+
+default_delay:
 	genphy_config_aneg(phydev);
 
 	return 0;
@@ -410,7 +427,7 @@
 	.mask = 0xffffff,
 	.features = PHY_GBIT_FEATURES,
 	.probe = &rtl8211e_probe,
-	.config = &rtl8211x_config,
+	.config = &rtl8211e_config,
 	.startup = &rtl8211e_startup,
 	.shutdown = &genphy_shutdown,
 };
diff --git a/drivers/net/sja1105.c b/drivers/net/sja1105.c
new file mode 100644
index 0000000..17bab33
--- /dev/null
+++ b/drivers/net/sja1105.c
@@ -0,0 +1,3376 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2016-2018 NXP
+ * Copyright 2018, Sensor-Technik Wiedemann GmbH
+ * Copyright 2018-2019, Vladimir Oltean <olteanv@gmail.com>
+ * Copyright 2020-2021 NXP
+ *
+ * Ported from Linux (drivers/net/dsa/sja1105/).
+ */
+
+#include <common.h>
+#include <dm/device_compat.h>
+#include <linux/bitops.h>
+#include <linux/bitrev.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
+#include <linux/types.h>
+#include <net/dsa.h>
+#include <stdlib.h>
+#include <spi.h>
+#include <miiphy.h>
+#include <dm/of_extra.h>
+
+enum packing_op {
+	PACK,
+	UNPACK,
+};
+
+#define ETHER_CRC32_POLY				0x04C11DB7
+#define ETH_P_SJA1105					0xdadb
+#define SJA1105_NUM_PORTS				5
+#define SJA1110_NUM_PORTS				11
+#define SJA1105_MAX_NUM_PORTS				SJA1110_NUM_PORTS
+#define SJA1105_NUM_TC					8
+#define SJA1105ET_FDB_BIN_SIZE				4
+#define SJA1105_SIZE_CGU_CMD				4
+#define SJA1105_SIZE_RESET_CMD				4
+#define SJA1105_SIZE_MDIO_CMD				4
+#define SJA1105_SIZE_SPI_MSG_HEADER			4
+#define SJA1105_SIZE_SPI_MSG_MAXLEN			(64 * 4)
+#define SJA1105_SIZE_DEVICE_ID				4
+#define SJA1105_SIZE_TABLE_HEADER			12
+#define SJA1105_SIZE_L2_POLICING_ENTRY			8
+#define SJA1105_SIZE_VLAN_LOOKUP_ENTRY			8
+#define SJA1110_SIZE_VLAN_LOOKUP_ENTRY			12
+#define SJA1105_SIZE_L2_FORWARDING_ENTRY		8
+#define SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY		12
+#define SJA1105_SIZE_XMII_PARAMS_ENTRY			4
+#define SJA1110_SIZE_XMII_PARAMS_ENTRY			8
+#define SJA1105ET_SIZE_MAC_CONFIG_ENTRY			28
+#define SJA1105ET_SIZE_GENERAL_PARAMS_ENTRY		40
+#define SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY		32
+#define SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY		44
+#define SJA1110_SIZE_GENERAL_PARAMS_ENTRY		56
+
+#define SJA1105_MAX_L2_LOOKUP_COUNT			1024
+#define SJA1105_MAX_L2_POLICING_COUNT			45
+#define SJA1110_MAX_L2_POLICING_COUNT			110
+#define SJA1105_MAX_VLAN_LOOKUP_COUNT			4096
+#define SJA1105_MAX_L2_FORWARDING_COUNT			13
+#define SJA1110_MAX_L2_FORWARDING_COUNT			19
+#define SJA1105_MAX_MAC_CONFIG_COUNT			5
+#define SJA1110_MAX_MAC_CONFIG_COUNT			11
+#define SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT		1
+#define SJA1105_MAX_GENERAL_PARAMS_COUNT		1
+#define SJA1105_MAX_XMII_PARAMS_COUNT			1
+
+#define SJA1105_MAX_FRAME_MEMORY			929
+
+#define SJA1105E_DEVICE_ID				0x9C00000Cull
+#define SJA1105T_DEVICE_ID				0x9E00030Eull
+#define SJA1105PR_DEVICE_ID				0xAF00030Eull
+#define SJA1105QS_DEVICE_ID				0xAE00030Eull
+#define SJA1110_DEVICE_ID				0xB700030Full
+
+#define SJA1105ET_PART_NO				0x9A83
+#define SJA1105P_PART_NO				0x9A84
+#define SJA1105Q_PART_NO				0x9A85
+#define SJA1105R_PART_NO				0x9A86
+#define SJA1105S_PART_NO				0x9A87
+#define SJA1110A_PART_NO				0x1110
+#define SJA1110B_PART_NO				0x1111
+#define SJA1110C_PART_NO				0x1112
+#define SJA1110D_PART_NO				0x1113
+
+#define SJA1110_ACU			0x1c4400
+#define SJA1110_RGU			0x1c6000
+#define SJA1110_CGU			0x1c6400
+
+#define SJA1110_SPI_ADDR(x)		((x) / 4)
+#define SJA1110_ACU_ADDR(x)		(SJA1110_ACU + SJA1110_SPI_ADDR(x))
+#define SJA1110_CGU_ADDR(x)		(SJA1110_CGU + SJA1110_SPI_ADDR(x))
+#define SJA1110_RGU_ADDR(x)		(SJA1110_RGU + SJA1110_SPI_ADDR(x))
+
+#define SJA1105_RSV_ADDR		0xffffffffffffffffull
+
+#define SJA1110_PCS_BANK_REG		SJA1110_SPI_ADDR(0x3fc)
+
+#define DSA_8021Q_DIR_TX		BIT(11)
+#define DSA_8021Q_PORT_SHIFT		0
+#define DSA_8021Q_PORT_MASK		GENMASK(3, 0)
+#define DSA_8021Q_PORT(x)		(((x) << DSA_8021Q_PORT_SHIFT) & \
+						 DSA_8021Q_PORT_MASK)
+
+#define SJA1105_RATE_MBPS(speed) (((speed) * 64000) / 1000)
+
+/* XPCS registers */
+
+/* VR MII MMD registers offsets */
+#define DW_VR_MII_DIG_CTRL1		0x8000
+#define DW_VR_MII_AN_CTRL		0x8001
+#define DW_VR_MII_DIG_CTRL2		0x80e1
+
+/* VR_MII_DIG_CTRL1 */
+#define DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW		BIT(9)
+
+/* VR_MII_DIG_CTRL2 */
+#define DW_VR_MII_DIG_CTRL2_TX_POL_INV		BIT(4)
+
+/* VR_MII_AN_CTRL */
+#define DW_VR_MII_AN_CTRL_TX_CONFIG_SHIFT	3
+#define DW_VR_MII_TX_CONFIG_MASK		BIT(3)
+#define DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII	0x0
+#define DW_VR_MII_AN_CTRL_PCS_MODE_SHIFT	1
+#define DW_VR_MII_PCS_MODE_MASK			GENMASK(2, 1)
+#define DW_VR_MII_PCS_MODE_C37_SGMII		0x2
+
+/* PMA registers */
+
+/* LANE_DRIVER1_0 register */
+#define SJA1110_LANE_DRIVER1_0		0x8038
+#define SJA1110_TXDRV(x)		(((x) << 12) & GENMASK(14, 12))
+
+/* LANE_DRIVER2_0 register */
+#define SJA1110_LANE_DRIVER2_0		0x803a
+#define SJA1110_TXDRVTRIM_LSB(x)	((x) & GENMASK_ULL(15, 0))
+
+/* LANE_DRIVER2_1 register */
+#define SJA1110_LANE_DRIVER2_1		0x803b
+#define SJA1110_LANE_DRIVER2_1_RSV	BIT(9)
+#define SJA1110_TXDRVTRIM_MSB(x)	(((x) & GENMASK_ULL(23, 16)) >> 16)
+
+/* LANE_TRIM register */
+#define SJA1110_LANE_TRIM		0x8040
+#define SJA1110_TXTEN			BIT(11)
+#define SJA1110_TXRTRIM(x)		(((x) << 8) & GENMASK(10, 8))
+#define SJA1110_TXPLL_BWSEL		BIT(7)
+#define SJA1110_RXTEN			BIT(6)
+#define SJA1110_RXRTRIM(x)		(((x) << 3) & GENMASK(5, 3))
+#define SJA1110_CDR_GAIN		BIT(2)
+#define SJA1110_ACCOUPLE_RXVCM_EN	BIT(0)
+
+/* LANE_DATAPATH_1 register */
+#define SJA1110_LANE_DATAPATH_1		0x8037
+
+/* POWERDOWN_ENABLE register */
+#define SJA1110_POWERDOWN_ENABLE	0x8041
+#define SJA1110_TXPLL_PD		BIT(12)
+#define SJA1110_TXPD			BIT(11)
+#define SJA1110_RXPKDETEN		BIT(10)
+#define SJA1110_RXCH_PD			BIT(9)
+#define SJA1110_RXBIAS_PD		BIT(8)
+#define SJA1110_RESET_SER_EN		BIT(7)
+#define SJA1110_RESET_SER		BIT(6)
+#define SJA1110_RESET_DES		BIT(5)
+#define SJA1110_RCVEN			BIT(4)
+
+/* RXPLL_CTRL0 register */
+#define SJA1110_RXPLL_CTRL0		0x8065
+#define SJA1110_RXPLL_FBDIV(x)		(((x) << 2) & GENMASK(9, 2))
+
+/* RXPLL_CTRL1 register */
+#define SJA1110_RXPLL_CTRL1		0x8066
+#define SJA1110_RXPLL_REFDIV(x)		((x) & GENMASK(4, 0))
+
+/* TXPLL_CTRL0 register */
+#define SJA1110_TXPLL_CTRL0		0x806d
+#define SJA1110_TXPLL_FBDIV(x)		((x) & GENMASK(11, 0))
+
+/* TXPLL_CTRL1 register */
+#define SJA1110_TXPLL_CTRL1		0x806e
+#define SJA1110_TXPLL_REFDIV(x)		((x) & GENMASK(5, 0))
+
+/* RX_DATA_DETECT register */
+#define SJA1110_RX_DATA_DETECT		0x8045
+
+/* RX_CDR_CTLE register */
+#define SJA1110_RX_CDR_CTLE		0x8042
+
+/* UM10944.pdf Page 11, Table 2. Configuration Blocks */
+enum {
+	BLKID_L2_POLICING				= 0x06,
+	BLKID_VLAN_LOOKUP				= 0x07,
+	BLKID_L2_FORWARDING				= 0x08,
+	BLKID_MAC_CONFIG				= 0x09,
+	BLKID_L2_FORWARDING_PARAMS			= 0x0E,
+	BLKID_GENERAL_PARAMS				= 0x11,
+	BLKID_XMII_PARAMS				= 0x4E,
+};
+
+enum sja1105_blk_idx {
+	BLK_IDX_L2_POLICING = 0,
+	BLK_IDX_VLAN_LOOKUP,
+	BLK_IDX_L2_FORWARDING,
+	BLK_IDX_MAC_CONFIG,
+	BLK_IDX_L2_FORWARDING_PARAMS,
+	BLK_IDX_GENERAL_PARAMS,
+	BLK_IDX_XMII_PARAMS,
+	BLK_IDX_MAX,
+};
+
+struct sja1105_general_params_entry {
+	u64 mac_fltres1;
+	u64 mac_fltres0;
+	u64 mac_flt1;
+	u64 mac_flt0;
+	u64 casc_port;
+	u64 host_port;
+	u64 mirr_port;
+	u64 tpid;
+	u64 tpid2;
+};
+
+struct sja1105_vlan_lookup_entry {
+	u64 vmemb_port;
+	u64 vlan_bc;
+	u64 tag_port;
+	u64 vlanid;
+	u64 type_entry; /* SJA1110 only */
+};
+
+struct sja1105_l2_forwarding_entry {
+	u64 bc_domain;
+	u64 reach_port;
+	u64 fl_domain;
+};
+
+struct sja1105_l2_forwarding_params_entry {
+	u64 part_spc[SJA1105_NUM_TC];
+};
+
+struct sja1105_l2_policing_entry {
+	u64 sharindx;
+	u64 smax;
+	u64 rate;
+	u64 maxlen;
+	u64 partition;
+};
+
+struct sja1105_mac_config_entry {
+	u64 top[SJA1105_NUM_TC];
+	u64 base[SJA1105_NUM_TC];
+	u64 enabled[SJA1105_NUM_TC];
+	u64 speed;
+	u64 vlanid;
+	u64 egress;
+	u64 ingress;
+};
+
+struct sja1105_xmii_params_entry {
+	u64 phy_mac[SJA1105_MAX_NUM_PORTS];
+	u64 xmii_mode[SJA1105_MAX_NUM_PORTS];
+	u64 special[SJA1105_MAX_NUM_PORTS];
+};
+
+struct sja1105_table_header {
+	u64 block_id;
+	u64 len;
+	u64 crc;
+};
+
+struct sja1105_table_ops {
+	size_t (*packing)(void *buf, void *entry_ptr, enum packing_op op);
+	size_t unpacked_entry_size;
+	size_t packed_entry_size;
+	size_t max_entry_count;
+};
+
+struct sja1105_table {
+	const struct sja1105_table_ops *ops;
+	size_t entry_count;
+	void *entries;
+};
+
+struct sja1105_static_config {
+	u64 device_id;
+	struct sja1105_table tables[BLK_IDX_MAX];
+};
+
+struct sja1105_xpcs_cfg {
+	bool inband_an;
+	int speed;
+};
+
+struct sja1105_private {
+	struct sja1105_static_config static_config;
+	bool rgmii_rx_delay[SJA1105_MAX_NUM_PORTS];
+	bool rgmii_tx_delay[SJA1105_MAX_NUM_PORTS];
+	u16 pvid[SJA1105_MAX_NUM_PORTS];
+	struct sja1105_xpcs_cfg xpcs_cfg[SJA1105_MAX_NUM_PORTS];
+	struct mii_dev *mdio_pcs;
+	const struct sja1105_info *info;
+	struct udevice *dev;
+};
+
+typedef enum {
+	SPI_READ = 0,
+	SPI_WRITE = 1,
+} sja1105_spi_rw_mode_t;
+
+typedef enum {
+	XMII_MAC = 0,
+	XMII_PHY = 1,
+} sja1105_mii_role_t;
+
+typedef enum {
+	XMII_MODE_MII		= 0,
+	XMII_MODE_RMII		= 1,
+	XMII_MODE_RGMII		= 2,
+	XMII_MODE_SGMII		= 3,
+} sja1105_phy_interface_t;
+
+enum {
+	SJA1105_SPEED_AUTO,
+	SJA1105_SPEED_10MBPS,
+	SJA1105_SPEED_100MBPS,
+	SJA1105_SPEED_1000MBPS,
+	SJA1105_SPEED_MAX,
+};
+
+enum sja1110_vlan_type {
+	SJA1110_VLAN_INVALID = 0,
+	SJA1110_VLAN_C_TAG = 1, /* Single inner VLAN tag */
+	SJA1110_VLAN_S_TAG = 2, /* Single outer VLAN tag */
+	SJA1110_VLAN_D_TAG = 3, /* Double tagged, use outer tag for lookup */
+};
+
+/* Keeps the different addresses between E/T and P/Q/R/S */
+struct sja1105_regs {
+	u64 device_id;
+	u64 prod_id;
+	u64 status;
+	u64 port_control;
+	u64 rgu;
+	u64 config;
+	u64 rmii_pll1;
+	u64 pad_mii_tx[SJA1105_MAX_NUM_PORTS];
+	u64 pad_mii_rx[SJA1105_MAX_NUM_PORTS];
+	u64 pad_mii_id[SJA1105_MAX_NUM_PORTS];
+	u64 cgu_idiv[SJA1105_MAX_NUM_PORTS];
+	u64 mii_tx_clk[SJA1105_MAX_NUM_PORTS];
+	u64 mii_rx_clk[SJA1105_MAX_NUM_PORTS];
+	u64 mii_ext_tx_clk[SJA1105_MAX_NUM_PORTS];
+	u64 mii_ext_rx_clk[SJA1105_MAX_NUM_PORTS];
+	u64 rgmii_tx_clk[SJA1105_MAX_NUM_PORTS];
+	u64 rmii_ref_clk[SJA1105_MAX_NUM_PORTS];
+	u64 rmii_ext_tx_clk[SJA1105_MAX_NUM_PORTS];
+	u64 pcs_base[SJA1105_MAX_NUM_PORTS];
+};
+
+struct sja1105_info {
+	u64 device_id;
+	u64 part_no;
+	const struct sja1105_table_ops *static_ops;
+	const struct sja1105_regs *regs;
+	int (*reset_cmd)(struct sja1105_private *priv);
+	int (*setup_rgmii_delay)(struct sja1105_private *priv, int port);
+	int (*pcs_mdio_read)(struct mii_dev *bus, int phy, int mmd, int reg);
+	int (*pcs_mdio_write)(struct mii_dev *bus, int phy, int mmd, int reg,
+			      u16 val);
+	int (*pma_config)(struct sja1105_private *priv, int port);
+	const char *name;
+	bool supports_mii[SJA1105_MAX_NUM_PORTS];
+	bool supports_rmii[SJA1105_MAX_NUM_PORTS];
+	bool supports_rgmii[SJA1105_MAX_NUM_PORTS];
+	bool supports_sgmii[SJA1105_MAX_NUM_PORTS];
+	const u64 port_speed[SJA1105_SPEED_MAX];
+};
+
+struct sja1105_chunk {
+	u8	*buf;
+	size_t	len;
+	u64	reg_addr;
+};
+
+struct sja1105_spi_message {
+	u64 access;
+	u64 read_count;
+	u64 address;
+};
+
+/* Common structure for CFG_PAD_MIIx_RX and CFG_PAD_MIIx_TX */
+struct sja1105_cfg_pad_mii {
+	u64 d32_os;
+	u64 d32_ih;
+	u64 d32_ipud;
+	u64 d10_ih;
+	u64 d10_os;
+	u64 d10_ipud;
+	u64 ctrl_os;
+	u64 ctrl_ih;
+	u64 ctrl_ipud;
+	u64 clk_os;
+	u64 clk_ih;
+	u64 clk_ipud;
+};
+
+struct sja1105_cfg_pad_mii_id {
+	u64 rxc_stable_ovr;
+	u64 rxc_delay;
+	u64 rxc_bypass;
+	u64 rxc_pd;
+	u64 txc_stable_ovr;
+	u64 txc_delay;
+	u64 txc_bypass;
+	u64 txc_pd;
+};
+
+struct sja1105_cgu_idiv {
+	u64 clksrc;
+	u64 autoblock;
+	u64 idiv;
+	u64 pd;
+};
+
+struct sja1105_cgu_pll_ctrl {
+	u64 pllclksrc;
+	u64 msel;
+	u64 autoblock;
+	u64 psel;
+	u64 direct;
+	u64 fbsel;
+	u64 bypass;
+	u64 pd;
+};
+
+enum {
+	CLKSRC_MII0_TX_CLK	= 0x00,
+	CLKSRC_MII0_RX_CLK	= 0x01,
+	CLKSRC_MII1_TX_CLK	= 0x02,
+	CLKSRC_MII1_RX_CLK	= 0x03,
+	CLKSRC_MII2_TX_CLK	= 0x04,
+	CLKSRC_MII2_RX_CLK	= 0x05,
+	CLKSRC_MII3_TX_CLK	= 0x06,
+	CLKSRC_MII3_RX_CLK	= 0x07,
+	CLKSRC_MII4_TX_CLK	= 0x08,
+	CLKSRC_MII4_RX_CLK	= 0x09,
+	CLKSRC_PLL0		= 0x0B,
+	CLKSRC_PLL1		= 0x0E,
+	CLKSRC_IDIV0		= 0x11,
+	CLKSRC_IDIV1		= 0x12,
+	CLKSRC_IDIV2		= 0x13,
+	CLKSRC_IDIV3		= 0x14,
+	CLKSRC_IDIV4		= 0x15,
+};
+
+struct sja1105_cgu_mii_ctrl {
+	u64 clksrc;
+	u64 autoblock;
+	u64 pd;
+};
+
+static int get_reverse_lsw32_offset(int offset, size_t len)
+{
+	int closest_multiple_of_4;
+	int word_index;
+
+	word_index = offset / 4;
+	closest_multiple_of_4 = word_index * 4;
+	offset -= closest_multiple_of_4;
+	word_index = (len / 4) - word_index - 1;
+	return word_index * 4 + offset;
+}
+
+/* Simplified version of the "packing" function from Linux, adapted
+ * to support only sja1105's quirk: QUIRK_LSW32_IS_FIRST
+ */
+static void sja1105_packing(void *pbuf, u64 *uval, int startbit, int endbit,
+			    size_t pbuflen, enum packing_op op)
+{
+	int plogical_first_u8, plogical_last_u8, box;
+
+	if (op == UNPACK)
+		*uval = 0;
+
+	plogical_first_u8 = startbit / 8;
+	plogical_last_u8  = endbit / 8;
+
+	for (box = plogical_first_u8; box >= plogical_last_u8; box--) {
+		int box_start_bit, box_end_bit, box_addr;
+		int proj_start_bit, proj_end_bit;
+		u64 proj_mask;
+		u8  box_mask;
+
+		if (box == plogical_first_u8)
+			box_start_bit = startbit % 8;
+		else
+			box_start_bit = 7;
+		if (box == plogical_last_u8)
+			box_end_bit = endbit % 8;
+		else
+			box_end_bit = 0;
+
+		proj_start_bit = ((box * 8) + box_start_bit) - endbit;
+		proj_end_bit   = ((box * 8) + box_end_bit) - endbit;
+		proj_mask = GENMASK_ULL(proj_start_bit, proj_end_bit);
+		box_mask  = GENMASK_ULL(box_start_bit, box_end_bit);
+
+		box_addr = pbuflen - box - 1;
+		box_addr = get_reverse_lsw32_offset(box_addr, pbuflen);
+
+		if (op == UNPACK) {
+			u64 pval;
+
+			/* Read from pbuf, write to uval */
+			pval = ((u8 *)pbuf)[box_addr] & box_mask;
+
+			pval >>= box_end_bit;
+			pval <<= proj_end_bit;
+			*uval &= ~proj_mask;
+			*uval |= pval;
+		} else {
+			u64 pval;
+
+			/* Write to pbuf, read from uval */
+			pval = (*uval) & proj_mask;
+			pval >>= proj_end_bit;
+
+			pval <<= box_end_bit;
+			((u8 *)pbuf)[box_addr] &= ~box_mask;
+			((u8 *)pbuf)[box_addr] |= pval;
+		}
+	}
+}
+
+static u32 crc32_add(u32 crc, u8 byte)
+{
+	u32 byte32 = bitrev32(byte);
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		if ((crc ^ byte32) & BIT(31)) {
+			crc <<= 1;
+			crc ^= ETHER_CRC32_POLY;
+		} else {
+			crc <<= 1;
+		}
+		byte32 <<= 1;
+	}
+	return crc;
+}
+
+/* Little-endian Ethernet CRC32 of data packed as big-endian u32 words */
+static uint32_t sja1105_crc32(void *buf, size_t len)
+{
+	unsigned int i;
+	u64 chunk;
+	u32 crc;
+
+	/* seed */
+	crc = 0xFFFFFFFF;
+	for (i = 0; i < len; i += 4) {
+		sja1105_packing(buf + i, &chunk, 31, 0, 4, UNPACK);
+		crc = crc32_add(crc, chunk & 0xFF);
+		crc = crc32_add(crc, (chunk >> 8) & 0xFF);
+		crc = crc32_add(crc, (chunk >> 16) & 0xFF);
+		crc = crc32_add(crc, (chunk >> 24) & 0xFF);
+	}
+	return bitrev32(~crc);
+}
+
+static void sja1105_spi_message_pack(void *buf, struct sja1105_spi_message *msg)
+{
+	const int size = SJA1105_SIZE_SPI_MSG_HEADER;
+
+	memset(buf, 0, size);
+
+	sja1105_packing(buf, &msg->access,     31, 31, size, PACK);
+	sja1105_packing(buf, &msg->read_count, 30, 25, size, PACK);
+	sja1105_packing(buf, &msg->address,    24,  4, size, PACK);
+}
+
+static int sja1105_xfer_buf(const struct sja1105_private *priv,
+			    sja1105_spi_rw_mode_t rw, u64 reg_addr,
+			    u8 *buf, size_t len)
+{
+	struct udevice *dev = priv->dev;
+	struct sja1105_chunk chunk = {
+		.len = min_t(size_t, len, SJA1105_SIZE_SPI_MSG_MAXLEN),
+		.reg_addr = reg_addr,
+		.buf = buf,
+	};
+	int num_chunks;
+	int rc, i;
+
+	rc = dm_spi_claim_bus(dev);
+	if (rc)
+		return rc;
+
+	num_chunks = DIV_ROUND_UP(len, SJA1105_SIZE_SPI_MSG_MAXLEN);
+
+	for (i = 0; i < num_chunks; i++) {
+		u8 hdr_buf[SJA1105_SIZE_SPI_MSG_HEADER];
+		struct sja1105_spi_message msg;
+		u8 *rx_buf = NULL;
+		u8 *tx_buf = NULL;
+
+		/* Populate the transfer's header buffer */
+		msg.address = chunk.reg_addr;
+		msg.access = rw;
+		if (rw == SPI_READ)
+			msg.read_count = chunk.len / 4;
+		else
+			/* Ignored */
+			msg.read_count = 0;
+		sja1105_spi_message_pack(hdr_buf, &msg);
+		rc = dm_spi_xfer(dev, SJA1105_SIZE_SPI_MSG_HEADER * 8, hdr_buf,
+				 NULL, SPI_XFER_BEGIN);
+		if (rc)
+			goto out;
+
+		/* Populate the transfer's data buffer */
+		if (rw == SPI_READ)
+			rx_buf = chunk.buf;
+		else
+			tx_buf = chunk.buf;
+		rc = dm_spi_xfer(dev, chunk.len * 8, tx_buf, rx_buf,
+				 SPI_XFER_END);
+		if (rc)
+			goto out;
+
+		/* Calculate next chunk */
+		chunk.buf += chunk.len;
+		chunk.reg_addr += chunk.len / 4;
+		chunk.len = min_t(size_t, (ptrdiff_t)(buf + len - chunk.buf),
+				  SJA1105_SIZE_SPI_MSG_MAXLEN);
+	}
+
+out:
+	dm_spi_release_bus(dev);
+
+	return rc;
+}
+
+static int sja1105et_reset_cmd(struct sja1105_private *priv)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SJA1105_SIZE_RESET_CMD] = {0};
+	const int size = SJA1105_SIZE_RESET_CMD;
+	u64 cold_rst = 1;
+
+	sja1105_packing(packed_buf, &cold_rst, 3, 3, size, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->rgu, packed_buf,
+				SJA1105_SIZE_RESET_CMD);
+}
+
+static int sja1105pqrs_reset_cmd(struct sja1105_private *priv)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SJA1105_SIZE_RESET_CMD] = {0};
+	const int size = SJA1105_SIZE_RESET_CMD;
+	u64 cold_rst = 1;
+
+	sja1105_packing(packed_buf, &cold_rst, 2, 2, size, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->rgu, packed_buf,
+				SJA1105_SIZE_RESET_CMD);
+}
+
+static int sja1110_reset_cmd(struct sja1105_private *priv)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SJA1105_SIZE_RESET_CMD] = {0};
+	const int size = SJA1105_SIZE_RESET_CMD;
+	u64 switch_rst = 1;
+
+	/* Only reset the switch core.
+	 * A full cold reset would re-enable the BASE_MCSS_CLOCK PLL which
+	 * would turn on the microcontroller, potentially letting it execute
+	 * code which could interfere with our configuration.
+	 */
+	sja1105_packing(packed_buf, &switch_rst, 20, 20, size, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->rgu, packed_buf,
+				SJA1105_SIZE_RESET_CMD);
+}
+
+static size_t sja1105et_general_params_entry_packing(void *buf, void *entry_ptr,
+						     enum packing_op op)
+{
+	const size_t size = SJA1105ET_SIZE_GENERAL_PARAMS_ENTRY;
+	struct sja1105_general_params_entry *entry = entry_ptr;
+
+	sja1105_packing(buf, &entry->mac_fltres1, 311, 264, size, op);
+	sja1105_packing(buf, &entry->mac_fltres0, 263, 216, size, op);
+	sja1105_packing(buf, &entry->mac_flt1,    215, 168, size, op);
+	sja1105_packing(buf, &entry->mac_flt0,    167, 120, size, op);
+	sja1105_packing(buf, &entry->casc_port,   115, 113, size, op);
+	sja1105_packing(buf, &entry->host_port,   112, 110, size, op);
+	sja1105_packing(buf, &entry->mirr_port,   109, 107, size, op);
+	sja1105_packing(buf, &entry->tpid,         42,  27, size, op);
+	sja1105_packing(buf, &entry->tpid2,        25,  10, size, op);
+	return size;
+}
+
+static size_t sja1110_general_params_entry_packing(void *buf, void *entry_ptr,
+						   enum packing_op op)
+{
+	struct sja1105_general_params_entry *entry = entry_ptr;
+	const size_t size = SJA1110_SIZE_GENERAL_PARAMS_ENTRY;
+
+	sja1105_packing(buf, &entry->mac_fltres1,  438, 391, size, op);
+	sja1105_packing(buf, &entry->mac_fltres0,  390, 343, size, op);
+	sja1105_packing(buf, &entry->mac_flt1,     342, 295, size, op);
+	sja1105_packing(buf, &entry->mac_flt0,     294, 247, size, op);
+	sja1105_packing(buf, &entry->casc_port,    242, 232, size, op);
+	sja1105_packing(buf, &entry->host_port,    231, 228, size, op);
+	sja1105_packing(buf, &entry->mirr_port,    227, 224, size, op);
+	sja1105_packing(buf, &entry->tpid2,        159, 144, size, op);
+	sja1105_packing(buf, &entry->tpid,         142, 127, size, op);
+	return size;
+}
+
+static size_t
+sja1105pqrs_general_params_entry_packing(void *buf, void *entry_ptr,
+					 enum packing_op op)
+{
+	const size_t size = SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY;
+	struct sja1105_general_params_entry *entry = entry_ptr;
+
+	sja1105_packing(buf, &entry->mac_fltres1, 343, 296, size, op);
+	sja1105_packing(buf, &entry->mac_fltres0, 295, 248, size, op);
+	sja1105_packing(buf, &entry->mac_flt1,    247, 200, size, op);
+	sja1105_packing(buf, &entry->mac_flt0,    199, 152, size, op);
+	sja1105_packing(buf, &entry->casc_port,   147, 145, size, op);
+	sja1105_packing(buf, &entry->host_port,   144, 142, size, op);
+	sja1105_packing(buf, &entry->mirr_port,   141, 139, size, op);
+	sja1105_packing(buf, &entry->tpid,         74,  59, size, op);
+	sja1105_packing(buf, &entry->tpid2,        57,  42, size, op);
+	return size;
+}
+
+static size_t
+sja1105_l2_forwarding_params_entry_packing(void *buf, void *entry_ptr,
+					   enum packing_op op)
+{
+	const size_t size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY;
+	struct sja1105_l2_forwarding_params_entry *entry = entry_ptr;
+	int offset, i;
+
+	for (i = 0, offset = 13; i < SJA1105_NUM_TC; i++, offset += 10)
+		sja1105_packing(buf, &entry->part_spc[i],
+				offset + 9, offset + 0, size, op);
+	return size;
+}
+
+static size_t
+sja1110_l2_forwarding_params_entry_packing(void *buf, void *entry_ptr,
+					   enum packing_op op)
+{
+	struct sja1105_l2_forwarding_params_entry *entry = entry_ptr;
+	const size_t size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY;
+	int offset, i;
+
+	for (i = 0, offset = 5; i < 8; i++, offset += 11)
+		sja1105_packing(buf, &entry->part_spc[i],
+				offset + 10, offset + 0, size, op);
+	return size;
+}
+
+static size_t sja1105_l2_forwarding_entry_packing(void *buf, void *entry_ptr,
+						  enum packing_op op)
+{
+	const size_t size = SJA1105_SIZE_L2_FORWARDING_ENTRY;
+	struct sja1105_l2_forwarding_entry *entry = entry_ptr;
+
+	sja1105_packing(buf, &entry->bc_domain,  63, 59, size, op);
+	sja1105_packing(buf, &entry->reach_port, 58, 54, size, op);
+	sja1105_packing(buf, &entry->fl_domain,  53, 49, size, op);
+	return size;
+}
+
+static size_t sja1110_l2_forwarding_entry_packing(void *buf, void *entry_ptr,
+						  enum packing_op op)
+{
+	struct sja1105_l2_forwarding_entry *entry = entry_ptr;
+	const size_t size = SJA1105_SIZE_L2_FORWARDING_ENTRY;
+
+	sja1105_packing(buf, &entry->bc_domain,  63, 53, size, op);
+	sja1105_packing(buf, &entry->reach_port, 52, 42, size, op);
+	sja1105_packing(buf, &entry->fl_domain,  41, 31, size, op);
+	return size;
+}
+
+static size_t sja1105_l2_policing_entry_packing(void *buf, void *entry_ptr,
+						enum packing_op op)
+{
+	struct sja1105_l2_policing_entry *entry = entry_ptr;
+	const size_t size = SJA1105_SIZE_L2_POLICING_ENTRY;
+
+	sja1105_packing(buf, &entry->sharindx,  63, 58, size, op);
+	sja1105_packing(buf, &entry->smax,      57, 42, size, op);
+	sja1105_packing(buf, &entry->rate,      41, 26, size, op);
+	sja1105_packing(buf, &entry->maxlen,    25, 15, size, op);
+	sja1105_packing(buf, &entry->partition, 14, 12, size, op);
+	return size;
+}
+
+static size_t sja1110_l2_policing_entry_packing(void *buf, void *entry_ptr,
+						enum packing_op op)
+{
+	struct sja1105_l2_policing_entry *entry = entry_ptr;
+	const size_t size = SJA1105_SIZE_L2_POLICING_ENTRY;
+
+	sja1105_packing(buf, &entry->sharindx, 63, 57, size, op);
+	sja1105_packing(buf, &entry->smax,     56, 39, size, op);
+	sja1105_packing(buf, &entry->rate,     38, 21, size, op);
+	sja1105_packing(buf, &entry->maxlen,   20, 10, size, op);
+	sja1105_packing(buf, &entry->partition, 9,  7, size, op);
+	return size;
+}
+
+static size_t sja1105et_mac_config_entry_packing(void *buf, void *entry_ptr,
+						 enum packing_op op)
+{
+	const size_t size = SJA1105ET_SIZE_MAC_CONFIG_ENTRY;
+	struct sja1105_mac_config_entry *entry = entry_ptr;
+	int offset, i;
+
+	for (i = 0, offset = 72; i < SJA1105_NUM_TC; i++, offset += 19) {
+		sja1105_packing(buf, &entry->enabled[i],
+				offset +  0, offset +  0, size, op);
+		sja1105_packing(buf, &entry->base[i],
+				offset +  9, offset +  1, size, op);
+		sja1105_packing(buf, &entry->top[i],
+				offset + 18, offset + 10, size, op);
+	}
+	sja1105_packing(buf, &entry->speed,     66, 65, size, op);
+	sja1105_packing(buf, &entry->vlanid,    21, 10, size, op);
+	sja1105_packing(buf, &entry->egress,     2,  2, size, op);
+	sja1105_packing(buf, &entry->ingress,    1,  1, size, op);
+	return size;
+}
+
+static size_t sja1105pqrs_mac_config_entry_packing(void *buf, void *entry_ptr,
+						   enum packing_op op)
+{
+	const size_t size = SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY;
+	struct sja1105_mac_config_entry *entry = entry_ptr;
+	int offset, i;
+
+	for (i = 0, offset = 104; i < SJA1105_NUM_TC; i++, offset += 19) {
+		sja1105_packing(buf, &entry->enabled[i],
+				offset +  0, offset +  0, size, op);
+		sja1105_packing(buf, &entry->base[i],
+				offset +  9, offset +  1, size, op);
+		sja1105_packing(buf, &entry->top[i],
+				offset + 18, offset + 10, size, op);
+	}
+	sja1105_packing(buf, &entry->speed,      98, 97, size, op);
+	sja1105_packing(buf, &entry->vlanid,     53, 42, size, op);
+	sja1105_packing(buf, &entry->egress,     32, 32, size, op);
+	sja1105_packing(buf, &entry->ingress,    31, 31, size, op);
+	return size;
+}
+
+static size_t sja1110_mac_config_entry_packing(void *buf, void *entry_ptr,
+					       enum packing_op op)
+{
+	const size_t size = SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY;
+	struct sja1105_mac_config_entry *entry = entry_ptr;
+	int offset, i;
+
+	for (i = 0, offset = 104; i < 8; i++, offset += 19) {
+		sja1105_packing(buf, &entry->enabled[i],
+				offset +  0, offset +  0, size, op);
+		sja1105_packing(buf, &entry->base[i],
+				offset +  9, offset +  1, size, op);
+		sja1105_packing(buf, &entry->top[i],
+				offset + 18, offset + 10, size, op);
+	}
+	sja1105_packing(buf, &entry->speed,      98, 96, size, op);
+	sja1105_packing(buf, &entry->vlanid,     52, 41, size, op);
+	sja1105_packing(buf, &entry->egress,     31, 31, size, op);
+	sja1105_packing(buf, &entry->ingress,    30, 30, size, op);
+	return size;
+}
+
+static size_t sja1105_vlan_lookup_entry_packing(void *buf, void *entry_ptr,
+						enum packing_op op)
+{
+	const size_t size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY;
+	struct sja1105_vlan_lookup_entry *entry = entry_ptr;
+
+	sja1105_packing(buf, &entry->vmemb_port, 53, 49, size, op);
+	sja1105_packing(buf, &entry->vlan_bc,    48, 44, size, op);
+	sja1105_packing(buf, &entry->tag_port,   43, 39, size, op);
+	sja1105_packing(buf, &entry->vlanid,     38, 27, size, op);
+	return size;
+}
+
+static size_t sja1110_vlan_lookup_entry_packing(void *buf, void *entry_ptr,
+						enum packing_op op)
+{
+	struct sja1105_vlan_lookup_entry *entry = entry_ptr;
+	const size_t size = SJA1110_SIZE_VLAN_LOOKUP_ENTRY;
+
+	sja1105_packing(buf, &entry->vmemb_port, 73, 63, size, op);
+	sja1105_packing(buf, &entry->vlan_bc,    62, 52, size, op);
+	sja1105_packing(buf, &entry->tag_port,   51, 41, size, op);
+	sja1105_packing(buf, &entry->type_entry, 40, 39, size, op);
+	sja1105_packing(buf, &entry->vlanid,     38, 27, size, op);
+	return size;
+}
+
+static size_t sja1105_xmii_params_entry_packing(void *buf, void *entry_ptr,
+						enum packing_op op)
+{
+	const size_t size = SJA1105_SIZE_XMII_PARAMS_ENTRY;
+	struct sja1105_xmii_params_entry *entry = entry_ptr;
+	int offset, i;
+
+	for (i = 0, offset = 17; i < SJA1105_NUM_PORTS; i++, offset += 3) {
+		sja1105_packing(buf, &entry->xmii_mode[i],
+				offset + 1, offset + 0, size, op);
+		sja1105_packing(buf, &entry->phy_mac[i],
+				offset + 2, offset + 2, size, op);
+	}
+	return size;
+}
+
+static size_t sja1110_xmii_params_entry_packing(void *buf, void *entry_ptr,
+						enum packing_op op)
+{
+	const size_t size = SJA1110_SIZE_XMII_PARAMS_ENTRY;
+	struct sja1105_xmii_params_entry *entry = entry_ptr;
+	int offset, i;
+
+	for (i = 0, offset = 20; i < SJA1110_NUM_PORTS; i++, offset += 4) {
+		sja1105_packing(buf, &entry->xmii_mode[i],
+				offset + 1, offset + 0, size, op);
+		sja1105_packing(buf, &entry->phy_mac[i],
+				offset + 2, offset + 2, size, op);
+		sja1105_packing(buf, &entry->special[i],
+				offset + 3, offset + 3, size, op);
+	}
+	return size;
+}
+
+static size_t sja1105_table_header_packing(void *buf, void *entry_ptr,
+					   enum packing_op op)
+{
+	const size_t size = SJA1105_SIZE_TABLE_HEADER;
+	struct sja1105_table_header *entry = entry_ptr;
+
+	sja1105_packing(buf, &entry->block_id, 31, 24, size, op);
+	sja1105_packing(buf, &entry->len,      55, 32, size, op);
+	sja1105_packing(buf, &entry->crc,      95, 64, size, op);
+	return size;
+}
+
+static void
+sja1105_table_header_pack_with_crc(void *buf, struct sja1105_table_header *hdr)
+{
+	/* First pack the table as-is, then calculate the CRC, and
+	 * finally put the proper CRC into the packed buffer
+	 */
+	memset(buf, 0, SJA1105_SIZE_TABLE_HEADER);
+	sja1105_table_header_packing(buf, hdr, PACK);
+	hdr->crc = sja1105_crc32(buf, SJA1105_SIZE_TABLE_HEADER - 4);
+	sja1105_packing(buf + SJA1105_SIZE_TABLE_HEADER - 4, &hdr->crc,
+			31, 0, 4, PACK);
+}
+
+static void sja1105_table_write_crc(u8 *table_start, u8 *crc_ptr)
+{
+	u64 computed_crc;
+	int len_bytes;
+
+	len_bytes = (uintptr_t)(crc_ptr - table_start);
+	computed_crc = sja1105_crc32(table_start, len_bytes);
+	sja1105_packing(crc_ptr, &computed_crc, 31, 0, 4, PACK);
+}
+
+/* The block IDs that the switches support are unfortunately sparse, so keep a
+ * mapping table to "block indices" and translate back and forth.
+ */
+static const u64 blk_id_map[BLK_IDX_MAX] = {
+	[BLK_IDX_L2_POLICING] = BLKID_L2_POLICING,
+	[BLK_IDX_VLAN_LOOKUP] = BLKID_VLAN_LOOKUP,
+	[BLK_IDX_L2_FORWARDING] = BLKID_L2_FORWARDING,
+	[BLK_IDX_MAC_CONFIG] = BLKID_MAC_CONFIG,
+	[BLK_IDX_L2_FORWARDING_PARAMS] = BLKID_L2_FORWARDING_PARAMS,
+	[BLK_IDX_GENERAL_PARAMS] = BLKID_GENERAL_PARAMS,
+	[BLK_IDX_XMII_PARAMS] = BLKID_XMII_PARAMS,
+};
+
+static void
+sja1105_static_config_pack(void *buf, struct sja1105_static_config *config)
+{
+	struct sja1105_table_header header = {0};
+	enum sja1105_blk_idx i;
+	u8 *p = buf;
+	int j;
+
+	sja1105_packing(p, &config->device_id, 31, 0, 4, PACK);
+	p += SJA1105_SIZE_DEVICE_ID;
+
+	for (i = 0; i < BLK_IDX_MAX; i++) {
+		const struct sja1105_table *table;
+		u8 *table_start;
+
+		table = &config->tables[i];
+		if (!table->entry_count)
+			continue;
+
+		header.block_id = blk_id_map[i];
+		header.len = table->entry_count *
+			     table->ops->packed_entry_size / 4;
+		sja1105_table_header_pack_with_crc(p, &header);
+		p += SJA1105_SIZE_TABLE_HEADER;
+		table_start = p;
+		for (j = 0; j < table->entry_count; j++) {
+			u8 *entry_ptr = table->entries;
+
+			entry_ptr += j * table->ops->unpacked_entry_size;
+			memset(p, 0, table->ops->packed_entry_size);
+			table->ops->packing(p, entry_ptr, PACK);
+			p += table->ops->packed_entry_size;
+		}
+		sja1105_table_write_crc(table_start, p);
+		p += 4;
+	}
+	/* Final header:
+	 * Block ID does not matter
+	 * Length of 0 marks that header is final
+	 * CRC will be replaced on-the-fly
+	 */
+	header.block_id = 0;
+	header.len = 0;
+	header.crc = 0xDEADBEEF;
+	memset(p, 0, SJA1105_SIZE_TABLE_HEADER);
+	sja1105_table_header_packing(p, &header, PACK);
+}
+
+static size_t
+sja1105_static_config_get_length(const struct sja1105_static_config *config)
+{
+	unsigned int header_count;
+	enum sja1105_blk_idx i;
+	unsigned int sum;
+
+	/* Ending header */
+	header_count = 1;
+	sum = SJA1105_SIZE_DEVICE_ID;
+
+	/* Tables (headers and entries) */
+	for (i = 0; i < BLK_IDX_MAX; i++) {
+		const struct sja1105_table *table;
+
+		table = &config->tables[i];
+		if (table->entry_count)
+			header_count++;
+
+		sum += table->ops->packed_entry_size * table->entry_count;
+	}
+	/* Headers have an additional CRC at the end */
+	sum += header_count * (SJA1105_SIZE_TABLE_HEADER + 4);
+	/* Last header does not have an extra CRC because there is no data */
+	sum -= 4;
+
+	return sum;
+}
+
+/* Compatibility matrices */
+static const struct sja1105_table_ops sja1105et_table_ops[BLK_IDX_MAX] = {
+	[BLK_IDX_L2_POLICING] = {
+		.packing = sja1105_l2_policing_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_l2_policing_entry),
+		.packed_entry_size = SJA1105_SIZE_L2_POLICING_ENTRY,
+		.max_entry_count = SJA1105_MAX_L2_POLICING_COUNT,
+	},
+	[BLK_IDX_VLAN_LOOKUP] = {
+		.packing = sja1105_vlan_lookup_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_vlan_lookup_entry),
+		.packed_entry_size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY,
+		.max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT,
+	},
+	[BLK_IDX_L2_FORWARDING] = {
+		.packing = sja1105_l2_forwarding_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_entry),
+		.packed_entry_size = SJA1105_SIZE_L2_FORWARDING_ENTRY,
+		.max_entry_count = SJA1105_MAX_L2_FORWARDING_COUNT,
+	},
+	[BLK_IDX_MAC_CONFIG] = {
+		.packing = sja1105et_mac_config_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_mac_config_entry),
+		.packed_entry_size = SJA1105ET_SIZE_MAC_CONFIG_ENTRY,
+		.max_entry_count = SJA1105_MAX_MAC_CONFIG_COUNT,
+	},
+	[BLK_IDX_L2_FORWARDING_PARAMS] = {
+		.packing = sja1105_l2_forwarding_params_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_params_entry),
+		.packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY,
+		.max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT,
+	},
+	[BLK_IDX_GENERAL_PARAMS] = {
+		.packing = sja1105et_general_params_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_general_params_entry),
+		.packed_entry_size = SJA1105ET_SIZE_GENERAL_PARAMS_ENTRY,
+		.max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT,
+	},
+	[BLK_IDX_XMII_PARAMS] = {
+		.packing = sja1105_xmii_params_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_xmii_params_entry),
+		.packed_entry_size = SJA1105_SIZE_XMII_PARAMS_ENTRY,
+		.max_entry_count = SJA1105_MAX_XMII_PARAMS_COUNT,
+	},
+};
+
+static const struct sja1105_table_ops sja1105pqrs_table_ops[BLK_IDX_MAX] = {
+	[BLK_IDX_L2_POLICING] = {
+		.packing = sja1105_l2_policing_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_l2_policing_entry),
+		.packed_entry_size = SJA1105_SIZE_L2_POLICING_ENTRY,
+		.max_entry_count = SJA1105_MAX_L2_POLICING_COUNT,
+	},
+	[BLK_IDX_VLAN_LOOKUP] = {
+		.packing = sja1105_vlan_lookup_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_vlan_lookup_entry),
+		.packed_entry_size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY,
+		.max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT,
+	},
+	[BLK_IDX_L2_FORWARDING] = {
+		.packing = sja1105_l2_forwarding_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_entry),
+		.packed_entry_size = SJA1105_SIZE_L2_FORWARDING_ENTRY,
+		.max_entry_count = SJA1105_MAX_L2_FORWARDING_COUNT,
+	},
+	[BLK_IDX_MAC_CONFIG] = {
+		.packing = sja1105pqrs_mac_config_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_mac_config_entry),
+		.packed_entry_size = SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY,
+		.max_entry_count = SJA1105_MAX_MAC_CONFIG_COUNT,
+	},
+	[BLK_IDX_L2_FORWARDING_PARAMS] = {
+		.packing = sja1105_l2_forwarding_params_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_params_entry),
+		.packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY,
+		.max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT,
+	},
+	[BLK_IDX_GENERAL_PARAMS] = {
+		.packing = sja1105pqrs_general_params_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_general_params_entry),
+		.packed_entry_size = SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY,
+		.max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT,
+	},
+	[BLK_IDX_XMII_PARAMS] = {
+		.packing = sja1105_xmii_params_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_xmii_params_entry),
+		.packed_entry_size = SJA1105_SIZE_XMII_PARAMS_ENTRY,
+		.max_entry_count = SJA1105_MAX_XMII_PARAMS_COUNT,
+	},
+};
+
+static const struct sja1105_table_ops sja1110_table_ops[BLK_IDX_MAX] = {
+	[BLK_IDX_L2_POLICING] = {
+		.packing = sja1110_l2_policing_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_l2_policing_entry),
+		.packed_entry_size = SJA1105_SIZE_L2_POLICING_ENTRY,
+		.max_entry_count = SJA1110_MAX_L2_POLICING_COUNT,
+	},
+	[BLK_IDX_VLAN_LOOKUP] = {
+		.packing = sja1110_vlan_lookup_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_vlan_lookup_entry),
+		.packed_entry_size = SJA1110_SIZE_VLAN_LOOKUP_ENTRY,
+		.max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT,
+	},
+	[BLK_IDX_L2_FORWARDING] = {
+		.packing = sja1110_l2_forwarding_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_entry),
+		.packed_entry_size = SJA1105_SIZE_L2_FORWARDING_ENTRY,
+		.max_entry_count = SJA1110_MAX_L2_FORWARDING_COUNT,
+	},
+	[BLK_IDX_MAC_CONFIG] = {
+		.packing = sja1110_mac_config_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_mac_config_entry),
+		.packed_entry_size = SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY,
+		.max_entry_count = SJA1110_MAX_MAC_CONFIG_COUNT,
+	},
+	[BLK_IDX_L2_FORWARDING_PARAMS] = {
+		.packing = sja1110_l2_forwarding_params_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_params_entry),
+		.packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY,
+		.max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT,
+	},
+	[BLK_IDX_GENERAL_PARAMS] = {
+		.packing = sja1110_general_params_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_general_params_entry),
+		.packed_entry_size = SJA1110_SIZE_GENERAL_PARAMS_ENTRY,
+		.max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT,
+	},
+	[BLK_IDX_XMII_PARAMS] = {
+		.packing = sja1110_xmii_params_entry_packing,
+		.unpacked_entry_size = sizeof(struct sja1105_xmii_params_entry),
+		.packed_entry_size = SJA1110_SIZE_XMII_PARAMS_ENTRY,
+		.max_entry_count = SJA1105_MAX_XMII_PARAMS_COUNT,
+	},
+};
+
+static int sja1105_init_mii_settings(struct sja1105_private *priv)
+{
+	struct sja1105_table *table;
+
+	table = &priv->static_config.tables[BLK_IDX_XMII_PARAMS];
+
+	table->entries = calloc(SJA1105_MAX_XMII_PARAMS_COUNT,
+				table->ops->unpacked_entry_size);
+	if (!table->entries)
+		return -ENOMEM;
+
+	/* Table will be populated at runtime */
+	table->entry_count = SJA1105_MAX_XMII_PARAMS_COUNT;
+
+	return 0;
+}
+
+static void sja1105_setup_tagging(struct sja1105_private *priv, int port)
+{
+	struct dsa_pdata *pdata = dev_get_uclass_plat(priv->dev);
+	struct sja1105_vlan_lookup_entry *vlan;
+	int cpu = pdata->cpu_port;
+
+	/* The CPU port is implicitly configured by
+	 * configuring the front-panel ports
+	 */
+	if (port == cpu)
+		return;
+
+	vlan = priv->static_config.tables[BLK_IDX_VLAN_LOOKUP].entries;
+
+	priv->pvid[port] = DSA_8021Q_DIR_TX | DSA_8021Q_PORT(port);
+
+	vlan[port].vmemb_port	= BIT(port) | BIT(cpu);
+	vlan[port].vlan_bc	= BIT(port) | BIT(cpu);
+	vlan[port].tag_port	= BIT(cpu);
+	vlan[port].vlanid	= priv->pvid[port];
+	vlan[port].type_entry	= SJA1110_VLAN_D_TAG;
+}
+
+static int sja1105_init_vlan(struct sja1105_private *priv)
+{
+	struct dsa_pdata *pdata = dev_get_uclass_plat(priv->dev);
+	struct sja1105_table *table;
+	int port;
+
+	table = &priv->static_config.tables[BLK_IDX_VLAN_LOOKUP];
+
+	table->entries = calloc(pdata->num_ports,
+				table->ops->unpacked_entry_size);
+	if (!table->entries)
+		return -ENOMEM;
+
+	table->entry_count = pdata->num_ports;
+
+	for (port = 0; port < pdata->num_ports; port++)
+		sja1105_setup_tagging(priv, port);
+
+	return 0;
+}
+
+static void
+sja1105_port_allow_traffic(struct sja1105_l2_forwarding_entry *l2_fwd,
+			   int from, int to)
+{
+	l2_fwd[from].bc_domain  |= BIT(to);
+	l2_fwd[from].reach_port |= BIT(to);
+	l2_fwd[from].fl_domain  |= BIT(to);
+}
+
+static int sja1105_init_l2_forwarding(struct sja1105_private *priv)
+{
+	struct dsa_pdata *pdata = dev_get_uclass_plat(priv->dev);
+	struct sja1105_l2_forwarding_entry *l2fwd;
+	struct sja1105_table *table;
+	int cpu = pdata->cpu_port;
+	int i;
+
+	table = &priv->static_config.tables[BLK_IDX_L2_FORWARDING];
+
+	table->entries = calloc(SJA1105_MAX_L2_FORWARDING_COUNT,
+				table->ops->unpacked_entry_size);
+	if (!table->entries)
+		return -ENOMEM;
+
+	table->entry_count = SJA1105_MAX_L2_FORWARDING_COUNT;
+
+	l2fwd = table->entries;
+
+	/* First 5 entries define the forwarding rules */
+	for (i = 0; i < pdata->num_ports; i++) {
+		if (i == cpu)
+			continue;
+
+		sja1105_port_allow_traffic(l2fwd, i, cpu);
+		sja1105_port_allow_traffic(l2fwd, cpu, i);
+	}
+	/* Next 8 entries define VLAN PCP mapping from ingress to egress.
+	 * Leave them unpopulated (implicitly 0) but present.
+	 */
+	return 0;
+}
+
+static int sja1105_init_l2_forwarding_params(struct sja1105_private *priv)
+{
+	struct sja1105_l2_forwarding_params_entry default_l2fwd_params = {
+		/* Use a single memory partition for all ingress queues */
+		.part_spc = { SJA1105_MAX_FRAME_MEMORY, 0, 0, 0, 0, 0, 0, 0 },
+	};
+	struct sja1105_table *table;
+
+	table = &priv->static_config.tables[BLK_IDX_L2_FORWARDING_PARAMS];
+
+	table->entries = calloc(SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT,
+				table->ops->unpacked_entry_size);
+	if (!table->entries)
+		return -ENOMEM;
+
+	table->entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT;
+
+	/* This table only has a single entry */
+	((struct sja1105_l2_forwarding_params_entry *)table->entries)[0] =
+				default_l2fwd_params;
+
+	return 0;
+}
+
+static int sja1105_init_general_params(struct sja1105_private *priv)
+{
+	struct dsa_pdata *pdata = dev_get_uclass_plat(priv->dev);
+	struct sja1105_general_params_entry default_general_params = {
+		/* No frame trapping */
+		.mac_fltres1 = 0x0,
+		.mac_flt1    = 0xffffffffffff,
+		.mac_fltres0 = 0x0,
+		.mac_flt0    = 0xffffffffffff,
+		.host_port = pdata->num_ports,
+		/* No mirroring => specify an out-of-range port value */
+		.mirr_port = pdata->num_ports,
+		/* No link-local trapping => specify an out-of-range port value
+		 */
+		.casc_port = pdata->num_ports,
+		/* Force the switch to see all traffic as untagged. */
+		.tpid = ETH_P_SJA1105,
+		.tpid2 = ETH_P_SJA1105,
+	};
+	struct sja1105_table *table;
+
+	table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS];
+
+	table->entries = calloc(SJA1105_MAX_GENERAL_PARAMS_COUNT,
+				table->ops->unpacked_entry_size);
+	if (!table->entries)
+		return -ENOMEM;
+
+	table->entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT;
+
+	/* This table only has a single entry */
+	((struct sja1105_general_params_entry *)table->entries)[0] =
+				default_general_params;
+
+	return 0;
+}
+
+static void sja1105_setup_policer(struct sja1105_l2_policing_entry *policing,
+				  int index, int mtu)
+{
+	policing[index].sharindx = index;
+	policing[index].smax = 65535; /* Burst size in bytes */
+	policing[index].rate = SJA1105_RATE_MBPS(1000);
+	policing[index].maxlen = mtu;
+	policing[index].partition = 0;
+}
+
+static int sja1105_init_l2_policing(struct sja1105_private *priv)
+{
+	struct dsa_pdata *pdata = dev_get_uclass_plat(priv->dev);
+	struct sja1105_l2_policing_entry *policing;
+	struct sja1105_table *table;
+	int cpu = pdata->cpu_port;
+	int i, j, k;
+
+	table = &priv->static_config.tables[BLK_IDX_L2_POLICING];
+
+	table->entries = calloc(SJA1105_MAX_L2_POLICING_COUNT,
+				table->ops->unpacked_entry_size);
+	if (!table->entries)
+		return -ENOMEM;
+
+	table->entry_count = SJA1105_MAX_L2_POLICING_COUNT;
+
+	policing = table->entries;
+
+	/* k sweeps through all unicast policers (0-39).
+	 * bcast sweeps through policers 40-44.
+	 */
+	for (i = 0, k = 0; i < pdata->num_ports; i++) {
+		int bcast = (pdata->num_ports * SJA1105_NUM_TC) + i;
+		int mtu = VLAN_ETH_FRAME_LEN + ETH_FCS_LEN;
+
+		if (i == cpu)
+			mtu += VLAN_HLEN;
+
+		for (j = 0; j < SJA1105_NUM_TC; j++, k++)
+			sja1105_setup_policer(policing, k, mtu);
+
+		/* Set up this port's policer for broadcast traffic */
+		sja1105_setup_policer(policing, bcast, mtu);
+	}
+	return 0;
+}
+
+static int sja1105_init_mac_settings(struct sja1105_private *priv)
+{
+	struct sja1105_mac_config_entry default_mac = {
+		/* Enable 1 priority queue on egress. */
+		.top  = {0x1FF, 0, 0, 0, 0, 0, 0},
+		.base = {0x0, 0, 0, 0, 0, 0, 0, 0},
+		.enabled = {1, 0, 0, 0, 0, 0, 0, 0},
+		/* Will be overridden in sja1105_port_enable. */
+		.speed = priv->info->port_speed[SJA1105_SPEED_AUTO],
+		.egress = true,
+		.ingress = true,
+	};
+	struct dsa_pdata *pdata = dev_get_uclass_plat(priv->dev);
+	struct sja1105_mac_config_entry *mac;
+	struct sja1105_table *table;
+	int port;
+
+	table = &priv->static_config.tables[BLK_IDX_MAC_CONFIG];
+
+	table->entries = calloc(pdata->num_ports,
+				table->ops->unpacked_entry_size);
+	if (!table->entries)
+		return -ENOMEM;
+
+	table->entry_count = pdata->num_ports;
+
+	mac = table->entries;
+
+	for (port = 0; port < pdata->num_ports; port++) {
+		mac[port] = default_mac;
+		/* Internal VLAN (pvid) to apply to untagged ingress */
+		mac[port].vlanid = priv->pvid[port];
+	}
+
+	return 0;
+}
+
+static int sja1105_static_config_init(struct sja1105_private *priv)
+{
+	struct sja1105_static_config *config = &priv->static_config;
+	const struct sja1105_table_ops *static_ops = priv->info->static_ops;
+	u64 device_id = priv->info->device_id;
+	enum sja1105_blk_idx i;
+	int rc;
+
+	*config = (struct sja1105_static_config) {0};
+
+	/* Transfer static_ops array from priv into per-table ops
+	 * for handier access
+	 */
+	for (i = 0; i < BLK_IDX_MAX; i++)
+		config->tables[i].ops = &static_ops[i];
+
+	config->device_id = device_id;
+
+	/* Build initial static configuration, to be fixed up during runtime */
+	rc = sja1105_init_vlan(priv);
+	if (rc < 0)
+		return rc;
+	rc = sja1105_init_mac_settings(priv);
+	if (rc < 0)
+		return rc;
+	rc = sja1105_init_mii_settings(priv);
+	if (rc < 0)
+		return rc;
+	rc = sja1105_init_l2_forwarding(priv);
+	if (rc < 0)
+		return rc;
+	rc = sja1105_init_l2_forwarding_params(priv);
+	if (rc < 0)
+		return rc;
+	rc = sja1105_init_l2_policing(priv);
+	if (rc < 0)
+		return rc;
+	rc = sja1105_init_general_params(priv);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static void sja1105_static_config_free(struct sja1105_static_config *config)
+{
+	enum sja1105_blk_idx i;
+
+	for (i = 0; i < BLK_IDX_MAX; i++) {
+		if (config->tables[i].entry_count) {
+			free(config->tables[i].entries);
+			config->tables[i].entry_count = 0;
+		}
+	}
+}
+
+static void sja1105_cgu_idiv_packing(void *buf, struct sja1105_cgu_idiv *idiv,
+				     enum packing_op op)
+{
+	const int size = 4;
+
+	sja1105_packing(buf, &idiv->clksrc,    28, 24, size, op);
+	sja1105_packing(buf, &idiv->autoblock, 11, 11, size, op);
+	sja1105_packing(buf, &idiv->idiv,       5,  2, size, op);
+	sja1105_packing(buf, &idiv->pd,         0,  0, size, op);
+}
+
+static int sja1105_cgu_idiv_config(struct sja1105_private *priv, int port,
+				   bool enabled, int factor)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+	struct sja1105_cgu_idiv idiv;
+
+	if (regs->cgu_idiv[port] == SJA1105_RSV_ADDR)
+		return 0;
+
+	if (enabled && factor != 1 && factor != 10)
+		return -ERANGE;
+
+	/* Payload for packed_buf */
+	idiv.clksrc    = 0x0A;            /* 25MHz */
+	idiv.autoblock = 1;               /* Block clk automatically */
+	idiv.idiv      = factor - 1;      /* Divide by 1 or 10 */
+	idiv.pd        = enabled ? 0 : 1; /* Power down? */
+	sja1105_cgu_idiv_packing(packed_buf, &idiv, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->cgu_idiv[port],
+				packed_buf, SJA1105_SIZE_CGU_CMD);
+}
+
+static void
+sja1105_cgu_mii_control_packing(void *buf, struct sja1105_cgu_mii_ctrl *cmd,
+				enum packing_op op)
+{
+	const int size = 4;
+
+	sja1105_packing(buf, &cmd->clksrc,    28, 24, size, op);
+	sja1105_packing(buf, &cmd->autoblock, 11, 11, size, op);
+	sja1105_packing(buf, &cmd->pd,         0,  0, size, op);
+}
+
+static int sja1105_cgu_mii_tx_clk_config(struct sja1105_private *priv,
+					 int port, sja1105_mii_role_t role)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	struct sja1105_cgu_mii_ctrl mii_tx_clk;
+	const int mac_clk_sources[] = {
+		CLKSRC_MII0_TX_CLK,
+		CLKSRC_MII1_TX_CLK,
+		CLKSRC_MII2_TX_CLK,
+		CLKSRC_MII3_TX_CLK,
+		CLKSRC_MII4_TX_CLK,
+	};
+	const int phy_clk_sources[] = {
+		CLKSRC_IDIV0,
+		CLKSRC_IDIV1,
+		CLKSRC_IDIV2,
+		CLKSRC_IDIV3,
+		CLKSRC_IDIV4,
+	};
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+	int clksrc;
+
+	if (regs->mii_tx_clk[port] == SJA1105_RSV_ADDR)
+		return 0;
+
+	if (role == XMII_MAC)
+		clksrc = mac_clk_sources[port];
+	else
+		clksrc = phy_clk_sources[port];
+
+	/* Payload for packed_buf */
+	mii_tx_clk.clksrc    = clksrc;
+	mii_tx_clk.autoblock = 1;  /* Autoblock clk while changing clksrc */
+	mii_tx_clk.pd        = 0;  /* Power Down off => enabled */
+	sja1105_cgu_mii_control_packing(packed_buf, &mii_tx_clk, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->mii_tx_clk[port],
+				packed_buf, SJA1105_SIZE_CGU_CMD);
+}
+
+static int
+sja1105_cgu_mii_rx_clk_config(struct sja1105_private *priv, int port)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+	struct sja1105_cgu_mii_ctrl mii_rx_clk;
+	const int clk_sources[] = {
+		CLKSRC_MII0_RX_CLK,
+		CLKSRC_MII1_RX_CLK,
+		CLKSRC_MII2_RX_CLK,
+		CLKSRC_MII3_RX_CLK,
+		CLKSRC_MII4_RX_CLK,
+	};
+
+	if (regs->mii_rx_clk[port] == SJA1105_RSV_ADDR)
+		return 0;
+
+	/* Payload for packed_buf */
+	mii_rx_clk.clksrc    = clk_sources[port];
+	mii_rx_clk.autoblock = 1;  /* Autoblock clk while changing clksrc */
+	mii_rx_clk.pd        = 0;  /* Power Down off => enabled */
+	sja1105_cgu_mii_control_packing(packed_buf, &mii_rx_clk, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->mii_rx_clk[port],
+				packed_buf, SJA1105_SIZE_CGU_CMD);
+}
+
+static int
+sja1105_cgu_mii_ext_tx_clk_config(struct sja1105_private *priv, int port)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	struct sja1105_cgu_mii_ctrl mii_ext_tx_clk;
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+	const int clk_sources[] = {
+		CLKSRC_IDIV0,
+		CLKSRC_IDIV1,
+		CLKSRC_IDIV2,
+		CLKSRC_IDIV3,
+		CLKSRC_IDIV4,
+	};
+
+	if (regs->mii_ext_tx_clk[port] == SJA1105_RSV_ADDR)
+		return 0;
+
+	/* Payload for packed_buf */
+	mii_ext_tx_clk.clksrc    = clk_sources[port];
+	mii_ext_tx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */
+	mii_ext_tx_clk.pd        = 0; /* Power Down off => enabled */
+	sja1105_cgu_mii_control_packing(packed_buf, &mii_ext_tx_clk, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->mii_ext_tx_clk[port],
+				packed_buf, SJA1105_SIZE_CGU_CMD);
+}
+
+static int
+sja1105_cgu_mii_ext_rx_clk_config(struct sja1105_private *priv, int port)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	struct sja1105_cgu_mii_ctrl mii_ext_rx_clk;
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+	const int clk_sources[] = {
+		CLKSRC_IDIV0,
+		CLKSRC_IDIV1,
+		CLKSRC_IDIV2,
+		CLKSRC_IDIV3,
+		CLKSRC_IDIV4,
+	};
+
+	if (regs->mii_ext_rx_clk[port] == SJA1105_RSV_ADDR)
+		return 0;
+
+	/* Payload for packed_buf */
+	mii_ext_rx_clk.clksrc    = clk_sources[port];
+	mii_ext_rx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */
+	mii_ext_rx_clk.pd        = 0; /* Power Down off => enabled */
+	sja1105_cgu_mii_control_packing(packed_buf, &mii_ext_rx_clk, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->mii_ext_rx_clk[port],
+				packed_buf, SJA1105_SIZE_CGU_CMD);
+}
+
+static int sja1105_mii_clocking_setup(struct sja1105_private *priv, int port,
+				      sja1105_mii_role_t role)
+{
+	int rc;
+
+	rc = sja1105_cgu_idiv_config(priv, port, (role == XMII_PHY), 1);
+	if (rc < 0)
+		return rc;
+
+	rc = sja1105_cgu_mii_tx_clk_config(priv, port, role);
+	if (rc < 0)
+		return rc;
+
+	rc = sja1105_cgu_mii_rx_clk_config(priv, port);
+	if (rc < 0)
+		return rc;
+
+	if (role == XMII_PHY) {
+		rc = sja1105_cgu_mii_ext_tx_clk_config(priv, port);
+		if (rc < 0)
+			return rc;
+
+		rc = sja1105_cgu_mii_ext_rx_clk_config(priv, port);
+		if (rc < 0)
+			return rc;
+	}
+	return 0;
+}
+
+static void
+sja1105_cgu_pll_control_packing(void *buf, struct sja1105_cgu_pll_ctrl *cmd,
+				enum packing_op op)
+{
+	const int size = 4;
+
+	sja1105_packing(buf, &cmd->pllclksrc, 28, 24, size, op);
+	sja1105_packing(buf, &cmd->msel,      23, 16, size, op);
+	sja1105_packing(buf, &cmd->autoblock, 11, 11, size, op);
+	sja1105_packing(buf, &cmd->psel,       9,  8, size, op);
+	sja1105_packing(buf, &cmd->direct,     7,  7, size, op);
+	sja1105_packing(buf, &cmd->fbsel,      6,  6, size, op);
+	sja1105_packing(buf, &cmd->bypass,     1,  1, size, op);
+	sja1105_packing(buf, &cmd->pd,         0,  0, size, op);
+}
+
+static int sja1105_cgu_rgmii_tx_clk_config(struct sja1105_private *priv,
+					   int port, u64 speed)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	struct sja1105_cgu_mii_ctrl txc;
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+	int clksrc;
+
+	if (regs->rgmii_tx_clk[port] == SJA1105_RSV_ADDR)
+		return 0;
+
+	if (speed == priv->info->port_speed[SJA1105_SPEED_1000MBPS]) {
+		clksrc = CLKSRC_PLL0;
+	} else {
+		int clk_sources[] = {CLKSRC_IDIV0, CLKSRC_IDIV1, CLKSRC_IDIV2,
+				     CLKSRC_IDIV3, CLKSRC_IDIV4};
+		clksrc = clk_sources[port];
+	}
+
+	/* RGMII: 125MHz for 1000, 25MHz for 100, 2.5MHz for 10 */
+	txc.clksrc = clksrc;
+	/* Autoblock clk while changing clksrc */
+	txc.autoblock = 1;
+	/* Power Down off => enabled */
+	txc.pd = 0;
+	sja1105_cgu_mii_control_packing(packed_buf, &txc, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->rgmii_tx_clk[port],
+				packed_buf, SJA1105_SIZE_CGU_CMD);
+}
+
+/* AGU */
+static void
+sja1105_cfg_pad_mii_packing(void *buf, struct sja1105_cfg_pad_mii *cmd,
+			    enum packing_op op)
+{
+	const int size = 4;
+
+	sja1105_packing(buf, &cmd->d32_os,   28, 27, size, op);
+	sja1105_packing(buf, &cmd->d32_ih,   26, 26, size, op);
+	sja1105_packing(buf, &cmd->d32_ipud, 25, 24, size, op);
+	sja1105_packing(buf, &cmd->d10_os,   20, 19, size, op);
+	sja1105_packing(buf, &cmd->d10_ih,   18, 18, size, op);
+	sja1105_packing(buf, &cmd->d10_ipud, 17, 16, size, op);
+	sja1105_packing(buf, &cmd->ctrl_os,  12, 11, size, op);
+	sja1105_packing(buf, &cmd->ctrl_ih,  10, 10, size, op);
+	sja1105_packing(buf, &cmd->ctrl_ipud, 9,  8, size, op);
+	sja1105_packing(buf, &cmd->clk_os,    4,  3, size, op);
+	sja1105_packing(buf, &cmd->clk_ih,    2,  2, size, op);
+	sja1105_packing(buf, &cmd->clk_ipud,  1,  0, size, op);
+}
+
+static void
+sja1110_cfg_pad_mii_id_packing(void *buf, struct sja1105_cfg_pad_mii_id *cmd,
+			       enum packing_op op)
+{
+	const int size = SJA1105_SIZE_CGU_CMD;
+	u64 range = 4;
+
+	/* Fields RXC_RANGE and TXC_RANGE select the input frequency range:
+	 * 0 = 2.5MHz
+	 * 1 = 25MHz
+	 * 2 = 50MHz
+	 * 3 = 125MHz
+	 * 4 = Automatically determined by port speed.
+	 * There's no point in defining a structure different than the one for
+	 * SJA1105, so just hardcode the frequency range to automatic, just as
+	 * before.
+	 */
+	sja1105_packing(buf, &cmd->rxc_stable_ovr, 26, 26, size, op);
+	sja1105_packing(buf, &cmd->rxc_delay,      25, 21, size, op);
+	sja1105_packing(buf, &range,               20, 18, size, op);
+	sja1105_packing(buf, &cmd->rxc_bypass,     17, 17, size, op);
+	sja1105_packing(buf, &cmd->rxc_pd,         16, 16, size, op);
+	sja1105_packing(buf, &cmd->txc_stable_ovr, 10, 10, size, op);
+	sja1105_packing(buf, &cmd->txc_delay,       9,  5, size, op);
+	sja1105_packing(buf, &range,                4,  2, size, op);
+	sja1105_packing(buf, &cmd->txc_bypass,      1,  1, size, op);
+	sja1105_packing(buf, &cmd->txc_pd,          0,  0, size, op);
+}
+
+static int sja1105_rgmii_cfg_pad_tx_config(struct sja1105_private *priv,
+					   int port)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	struct sja1105_cfg_pad_mii pad_mii_tx = {0};
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+
+	if (regs->pad_mii_tx[port] == SJA1105_RSV_ADDR)
+		return 0;
+
+	/* Payload */
+	pad_mii_tx.d32_os    = 3; /* TXD[3:2] output stage: */
+				  /*          high noise/high speed */
+	pad_mii_tx.d10_os    = 3; /* TXD[1:0] output stage: */
+				  /*          high noise/high speed */
+	pad_mii_tx.d32_ipud  = 2; /* TXD[3:2] input stage: */
+				  /*          plain input (default) */
+	pad_mii_tx.d10_ipud  = 2; /* TXD[1:0] input stage: */
+				  /*          plain input (default) */
+	pad_mii_tx.ctrl_os   = 3; /* TX_CTL / TX_ER output stage */
+	pad_mii_tx.ctrl_ipud = 2; /* TX_CTL / TX_ER input stage (default) */
+	pad_mii_tx.clk_os    = 3; /* TX_CLK output stage */
+	pad_mii_tx.clk_ih    = 0; /* TX_CLK input hysteresis (default) */
+	pad_mii_tx.clk_ipud  = 2; /* TX_CLK input stage (default) */
+	sja1105_cfg_pad_mii_packing(packed_buf, &pad_mii_tx, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_tx[port],
+				packed_buf, SJA1105_SIZE_CGU_CMD);
+}
+
+static int sja1105_cfg_pad_rx_config(struct sja1105_private *priv, int port)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	struct sja1105_cfg_pad_mii pad_mii_rx = {0};
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+
+	if (regs->pad_mii_rx[port] == SJA1105_RSV_ADDR)
+		return 0;
+
+	/* Payload */
+	pad_mii_rx.d32_ih    = 0; /* RXD[3:2] input stage hysteresis: */
+				  /*          non-Schmitt (default) */
+	pad_mii_rx.d32_ipud  = 2; /* RXD[3:2] input weak pull-up/down */
+				  /*          plain input (default) */
+	pad_mii_rx.d10_ih    = 0; /* RXD[1:0] input stage hysteresis: */
+				  /*          non-Schmitt (default) */
+	pad_mii_rx.d10_ipud  = 2; /* RXD[1:0] input weak pull-up/down */
+				  /*          plain input (default) */
+	pad_mii_rx.ctrl_ih   = 0; /* RX_DV/CRS_DV/RX_CTL and RX_ER */
+				  /* input stage hysteresis: */
+				  /* non-Schmitt (default) */
+	pad_mii_rx.ctrl_ipud = 3; /* RX_DV/CRS_DV/RX_CTL and RX_ER */
+				  /* input stage weak pull-up/down: */
+				  /* pull-down */
+	pad_mii_rx.clk_os    = 2; /* RX_CLK/RXC output stage: */
+				  /* medium noise/fast speed (default) */
+	pad_mii_rx.clk_ih    = 0; /* RX_CLK/RXC input hysteresis: */
+				  /* non-Schmitt (default) */
+	pad_mii_rx.clk_ipud  = 2; /* RX_CLK/RXC input pull-up/down: */
+				  /* plain input (default) */
+	sja1105_cfg_pad_mii_packing(packed_buf, &pad_mii_rx, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_rx[port],
+				packed_buf, SJA1105_SIZE_CGU_CMD);
+}
+
+static void
+sja1105_cfg_pad_mii_id_packing(void *buf, struct sja1105_cfg_pad_mii_id *cmd,
+			       enum packing_op op)
+{
+	const int size = SJA1105_SIZE_CGU_CMD;
+
+	sja1105_packing(buf, &cmd->rxc_stable_ovr, 15, 15, size, op);
+	sja1105_packing(buf, &cmd->rxc_delay,      14, 10, size, op);
+	sja1105_packing(buf, &cmd->rxc_bypass,      9,  9, size, op);
+	sja1105_packing(buf, &cmd->rxc_pd,          8,  8, size, op);
+	sja1105_packing(buf, &cmd->txc_stable_ovr,  7,  7, size, op);
+	sja1105_packing(buf, &cmd->txc_delay,       6,  2, size, op);
+	sja1105_packing(buf, &cmd->txc_bypass,      1,  1, size, op);
+	sja1105_packing(buf, &cmd->txc_pd,          0,  0, size, op);
+}
+
+/* Valid range in degrees is an integer between 73.8 and 101.7 */
+static u64 sja1105_rgmii_delay(u64 phase)
+{
+	/* UM11040.pdf: The delay in degree phase is 73.8 + delay_tune * 0.9.
+	 * To avoid floating point operations we'll multiply by 10
+	 * and get 1 decimal point precision.
+	 */
+	phase *= 10;
+	return (phase - 738) / 9;
+}
+
+static int sja1105pqrs_setup_rgmii_delay(struct sja1105_private *priv, int port)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	struct sja1105_cfg_pad_mii_id pad_mii_id = {0};
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+	int rc;
+
+	if (priv->rgmii_rx_delay[port])
+		pad_mii_id.rxc_delay = sja1105_rgmii_delay(90);
+	if (priv->rgmii_tx_delay[port])
+		pad_mii_id.txc_delay = sja1105_rgmii_delay(90);
+
+	/* Stage 1: Turn the RGMII delay lines off. */
+	pad_mii_id.rxc_bypass = 1;
+	pad_mii_id.rxc_pd = 1;
+	pad_mii_id.txc_bypass = 1;
+	pad_mii_id.txc_pd = 1;
+	sja1105_cfg_pad_mii_id_packing(packed_buf, &pad_mii_id, PACK);
+
+	rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_id[port],
+			      packed_buf, SJA1105_SIZE_CGU_CMD);
+	if (rc < 0)
+		return rc;
+
+	/* Stage 2: Turn the RGMII delay lines on. */
+	if (priv->rgmii_rx_delay[port]) {
+		pad_mii_id.rxc_bypass = 0;
+		pad_mii_id.rxc_pd = 0;
+	}
+	if (priv->rgmii_tx_delay[port]) {
+		pad_mii_id.txc_bypass = 0;
+		pad_mii_id.txc_pd = 0;
+	}
+	sja1105_cfg_pad_mii_id_packing(packed_buf, &pad_mii_id, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_id[port],
+				packed_buf, SJA1105_SIZE_CGU_CMD);
+}
+
+static int sja1110_setup_rgmii_delay(struct sja1105_private *priv, int port)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	struct sja1105_cfg_pad_mii_id pad_mii_id = {0};
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+
+	pad_mii_id.rxc_pd = 1;
+	pad_mii_id.txc_pd = 1;
+
+	if (priv->rgmii_rx_delay[port]) {
+		pad_mii_id.rxc_delay = sja1105_rgmii_delay(90);
+		/* The "BYPASS" bit in SJA1110 is actually a "don't bypass" */
+		pad_mii_id.rxc_bypass = 1;
+		pad_mii_id.rxc_pd = 0;
+	}
+
+	if (priv->rgmii_tx_delay[port]) {
+		pad_mii_id.txc_delay = sja1105_rgmii_delay(90);
+		pad_mii_id.txc_bypass = 1;
+		pad_mii_id.txc_pd = 0;
+	}
+
+	sja1110_cfg_pad_mii_id_packing(packed_buf, &pad_mii_id, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_id[port],
+				packed_buf, SJA1105_SIZE_CGU_CMD);
+}
+
+static int sja1105_rgmii_clocking_setup(struct sja1105_private *priv, int port,
+					sja1105_mii_role_t role)
+{
+	struct sja1105_mac_config_entry *mac;
+	struct udevice *dev = priv->dev;
+	u64 speed;
+	int rc;
+
+	mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries;
+	speed = mac[port].speed;
+
+	if (speed == priv->info->port_speed[SJA1105_SPEED_1000MBPS]) {
+		/* 1000Mbps, IDIV disabled (125 MHz) */
+		rc = sja1105_cgu_idiv_config(priv, port, false, 1);
+	} else if (speed == priv->info->port_speed[SJA1105_SPEED_100MBPS]) {
+		/* 100Mbps, IDIV enabled, divide by 1 (25 MHz) */
+		rc = sja1105_cgu_idiv_config(priv, port, true, 1);
+	} else if (speed == priv->info->port_speed[SJA1105_SPEED_10MBPS]) {
+		/* 10Mbps, IDIV enabled, divide by 10 (2.5 MHz) */
+		rc = sja1105_cgu_idiv_config(priv, port, true, 10);
+	} else if (speed == priv->info->port_speed[SJA1105_SPEED_AUTO]) {
+		/* Skip CGU configuration if there is no speed available
+		 * (e.g. link is not established yet)
+		 */
+		dev_dbg(dev, "Speed not available, skipping CGU config\n");
+		return 0;
+	} else {
+		rc = -EINVAL;
+	}
+
+	if (rc < 0) {
+		dev_err(dev, "Failed to configure idiv\n");
+		return rc;
+	}
+	rc = sja1105_cgu_rgmii_tx_clk_config(priv, port, speed);
+	if (rc < 0) {
+		dev_err(dev, "Failed to configure RGMII Tx clock\n");
+		return rc;
+	}
+	rc = sja1105_rgmii_cfg_pad_tx_config(priv, port);
+	if (rc < 0) {
+		dev_err(dev, "Failed to configure Tx pad registers\n");
+		return rc;
+	}
+
+	if (!priv->info->setup_rgmii_delay)
+		return 0;
+
+	return priv->info->setup_rgmii_delay(priv, port);
+}
+
+static int sja1105_cgu_rmii_ref_clk_config(struct sja1105_private *priv,
+					   int port)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+	struct sja1105_cgu_mii_ctrl ref_clk;
+	const int clk_sources[] = {
+		CLKSRC_MII0_TX_CLK,
+		CLKSRC_MII1_TX_CLK,
+		CLKSRC_MII2_TX_CLK,
+		CLKSRC_MII3_TX_CLK,
+		CLKSRC_MII4_TX_CLK,
+	};
+
+	if (regs->rmii_ref_clk[port] == SJA1105_RSV_ADDR)
+		return 0;
+
+	/* Payload for packed_buf */
+	ref_clk.clksrc    = clk_sources[port];
+	ref_clk.autoblock = 1;      /* Autoblock clk while changing clksrc */
+	ref_clk.pd        = 0;      /* Power Down off => enabled */
+	sja1105_cgu_mii_control_packing(packed_buf, &ref_clk, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->rmii_ref_clk[port],
+				packed_buf, SJA1105_SIZE_CGU_CMD);
+}
+
+static int
+sja1105_cgu_rmii_ext_tx_clk_config(struct sja1105_private *priv, int port)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	struct sja1105_cgu_mii_ctrl ext_tx_clk;
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+
+	if (regs->rmii_ext_tx_clk[port] == SJA1105_RSV_ADDR)
+		return 0;
+
+	/* Payload for packed_buf */
+	ext_tx_clk.clksrc    = CLKSRC_PLL1;
+	ext_tx_clk.autoblock = 1;   /* Autoblock clk while changing clksrc */
+	ext_tx_clk.pd        = 0;   /* Power Down off => enabled */
+	sja1105_cgu_mii_control_packing(packed_buf, &ext_tx_clk, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->rmii_ext_tx_clk[port],
+				packed_buf, SJA1105_SIZE_CGU_CMD);
+}
+
+static int sja1105_cgu_rmii_pll_config(struct sja1105_private *priv)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
+	struct sja1105_cgu_pll_ctrl pll = {0};
+	int rc;
+
+	if (regs->rmii_pll1 == SJA1105_RSV_ADDR)
+		return 0;
+
+	/* Step 1: PLL1 setup for 50Mhz */
+	pll.pllclksrc = 0xA;
+	pll.msel      = 0x1;
+	pll.autoblock = 0x1;
+	pll.psel      = 0x1;
+	pll.direct    = 0x0;
+	pll.fbsel     = 0x1;
+	pll.bypass    = 0x0;
+	pll.pd        = 0x1;
+
+	sja1105_cgu_pll_control_packing(packed_buf, &pll, PACK);
+	rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->rmii_pll1, packed_buf,
+			      SJA1105_SIZE_CGU_CMD);
+	if (rc < 0)
+		return rc;
+
+	/* Step 2: Enable PLL1 */
+	pll.pd = 0x0;
+
+	sja1105_cgu_pll_control_packing(packed_buf, &pll, PACK);
+	rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->rmii_pll1, packed_buf,
+			      SJA1105_SIZE_CGU_CMD);
+	return rc;
+}
+
+static int sja1105_rmii_clocking_setup(struct sja1105_private *priv, int port,
+				       sja1105_mii_role_t role)
+{
+	int rc;
+
+	/* AH1601.pdf chapter 2.5.1. Sources */
+	if (role == XMII_MAC) {
+		/* Configure and enable PLL1 for 50Mhz output */
+		rc = sja1105_cgu_rmii_pll_config(priv);
+		if (rc < 0)
+			return rc;
+	}
+	/* Disable IDIV for this port */
+	rc = sja1105_cgu_idiv_config(priv, port, false, 1);
+	if (rc < 0)
+		return rc;
+	/* Source to sink mappings */
+	rc = sja1105_cgu_rmii_ref_clk_config(priv, port);
+	if (rc < 0)
+		return rc;
+	if (role == XMII_MAC) {
+		rc = sja1105_cgu_rmii_ext_tx_clk_config(priv, port);
+		if (rc < 0)
+			return rc;
+	}
+	return 0;
+}
+
+static int sja1105_pcs_read(struct sja1105_private *priv, int addr,
+			    int devad, int regnum)
+{
+	return priv->mdio_pcs->read(priv->mdio_pcs, addr, devad, regnum);
+}
+
+static int sja1105_pcs_write(struct sja1105_private *priv, int addr,
+			     int devad, int regnum, u16 val)
+{
+	return priv->mdio_pcs->write(priv->mdio_pcs, addr, devad, regnum, val);
+}
+
+/* In NXP SJA1105, the PCS is integrated with a PMA that has the TX lane
+ * polarity inverted by default (PLUS is MINUS, MINUS is PLUS). To obtain
+ * normal non-inverted behavior, the TX lane polarity must be inverted in the
+ * PCS, via the DIGITAL_CONTROL_2 register.
+ */
+static int sja1105_pma_config(struct sja1105_private *priv, int port)
+{
+	return sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+				 DW_VR_MII_DIG_CTRL2,
+				 DW_VR_MII_DIG_CTRL2_TX_POL_INV);
+}
+
+static int sja1110_pma_config(struct sja1105_private *priv, int port)
+{
+	u16 txpll_fbdiv = 0x19, txpll_refdiv = 0x1;
+	u16 rxpll_fbdiv = 0x19, rxpll_refdiv = 0x1;
+	u16 rx_cdr_ctle = 0x212a;
+	u16 val;
+	int rc;
+
+	/* Program TX PLL feedback divider and reference divider settings for
+	 * correct oscillation frequency.
+	 */
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, SJA1110_TXPLL_CTRL0,
+			       SJA1110_TXPLL_FBDIV(txpll_fbdiv));
+	if (rc < 0)
+		return rc;
+
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, SJA1110_TXPLL_CTRL1,
+			       SJA1110_TXPLL_REFDIV(txpll_refdiv));
+	if (rc < 0)
+		return rc;
+
+	/* Program transmitter amplitude and disable amplitude trimming */
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+			       SJA1110_LANE_DRIVER1_0, SJA1110_TXDRV(0x5));
+	if (rc < 0)
+		return rc;
+
+	val = SJA1110_TXDRVTRIM_LSB(0xffffffull);
+
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+			       SJA1110_LANE_DRIVER2_0, val);
+	if (rc < 0)
+		return rc;
+
+	val = SJA1110_TXDRVTRIM_MSB(0xffffffull) | SJA1110_LANE_DRIVER2_1_RSV;
+
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+			       SJA1110_LANE_DRIVER2_1, val);
+	if (rc < 0)
+		return rc;
+
+	/* Enable input and output resistor terminations for low BER. */
+	val = SJA1110_ACCOUPLE_RXVCM_EN | SJA1110_CDR_GAIN |
+	      SJA1110_RXRTRIM(4) | SJA1110_RXTEN | SJA1110_TXPLL_BWSEL |
+	      SJA1110_TXRTRIM(3) | SJA1110_TXTEN;
+
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, SJA1110_LANE_TRIM,
+			       val);
+	if (rc < 0)
+		return rc;
+
+	/* Select PCS as transmitter data source. */
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+			       SJA1110_LANE_DATAPATH_1, 0);
+	if (rc < 0)
+		return rc;
+
+	/* Program RX PLL feedback divider and reference divider for correct
+	 * oscillation frequency.
+	 */
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, SJA1110_RXPLL_CTRL0,
+			       SJA1110_RXPLL_FBDIV(rxpll_fbdiv));
+	if (rc < 0)
+		return rc;
+
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, SJA1110_RXPLL_CTRL1,
+			       SJA1110_RXPLL_REFDIV(rxpll_refdiv));
+	if (rc < 0)
+		return rc;
+
+	/* Program threshold for receiver signal detector.
+	 * Enable control of RXPLL by receiver signal detector to disable RXPLL
+	 * when an input signal is not present.
+	 */
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+			       SJA1110_RX_DATA_DETECT, 0x0005);
+	if (rc < 0)
+		return rc;
+
+	/* Enable TX and RX PLLs and circuits.
+	 * Release reset of PMA to enable data flow to/from PCS.
+	 */
+	rc = sja1105_pcs_read(priv, port, MDIO_MMD_VEND2,
+			      SJA1110_POWERDOWN_ENABLE);
+	if (rc < 0)
+		return rc;
+
+	val = rc & ~(SJA1110_TXPLL_PD | SJA1110_TXPD | SJA1110_RXCH_PD |
+		     SJA1110_RXBIAS_PD | SJA1110_RESET_SER_EN |
+		     SJA1110_RESET_SER | SJA1110_RESET_DES);
+	val |= SJA1110_RXPKDETEN | SJA1110_RCVEN;
+
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+			       SJA1110_POWERDOWN_ENABLE, val);
+	if (rc < 0)
+		return rc;
+
+	/* Program continuous-time linear equalizer (CTLE) settings. */
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, SJA1110_RX_CDR_CTLE,
+			       rx_cdr_ctle);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static int sja1105_xpcs_config_aneg_c37_sgmii(struct sja1105_private *priv,
+					      int port)
+{
+	int rc;
+
+	rc = sja1105_pcs_read(priv, port, MDIO_MMD_VEND2, MDIO_CTRL1);
+	if (rc < 0)
+		return rc;
+	rc &= ~MDIO_AN_CTRL1_ENABLE;
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, MDIO_CTRL1,
+			       rc);
+	if (rc < 0)
+		return rc;
+
+	rc = sja1105_pcs_read(priv, port, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL);
+	if (rc < 0)
+		return rc;
+
+	rc &= ~(DW_VR_MII_PCS_MODE_MASK | DW_VR_MII_TX_CONFIG_MASK);
+	rc |= (DW_VR_MII_PCS_MODE_C37_SGMII <<
+	       DW_VR_MII_AN_CTRL_PCS_MODE_SHIFT &
+	       DW_VR_MII_PCS_MODE_MASK);
+	rc |= (DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII <<
+	       DW_VR_MII_AN_CTRL_TX_CONFIG_SHIFT &
+	       DW_VR_MII_TX_CONFIG_MASK);
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL,
+			       rc);
+	if (rc < 0)
+		return rc;
+
+	rc = sja1105_pcs_read(priv, port, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1);
+	if (rc < 0)
+		return rc;
+
+	if (priv->xpcs_cfg[port].inband_an)
+		rc |= DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
+	else
+		rc &= ~DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
+
+	rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1,
+			       rc);
+	if (rc < 0)
+		return rc;
+
+	rc = sja1105_pcs_read(priv, port, MDIO_MMD_VEND2, MDIO_CTRL1);
+	if (rc < 0)
+		return rc;
+
+	if (priv->xpcs_cfg[port].inband_an)
+		rc |= MDIO_AN_CTRL1_ENABLE;
+	else
+		rc &= ~MDIO_AN_CTRL1_ENABLE;
+
+	return sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, MDIO_CTRL1, rc);
+}
+
+static int sja1105_xpcs_link_up_sgmii(struct sja1105_private *priv, int port)
+{
+	int val = BMCR_FULLDPLX;
+
+	if (priv->xpcs_cfg[port].inband_an)
+		return 0;
+
+	switch (priv->xpcs_cfg[port].speed) {
+	case SPEED_1000:
+		val = BMCR_SPEED1000;
+		break;
+	case SPEED_100:
+		val = BMCR_SPEED100;
+		break;
+	case SPEED_10:
+		val = BMCR_SPEED10;
+		break;
+	default:
+		dev_err(priv->dev, "Invalid PCS speed %d\n",
+			priv->xpcs_cfg[port].speed);
+		return -EINVAL;
+	}
+
+	return sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, MDIO_CTRL1, val);
+}
+
+static int sja1105_sgmii_setup(struct sja1105_private *priv, int port)
+{
+	int rc;
+
+	rc = sja1105_xpcs_config_aneg_c37_sgmii(priv, port);
+	if (rc)
+		return rc;
+
+	rc = sja1105_xpcs_link_up_sgmii(priv, port);
+	if (rc)
+		return rc;
+
+	return priv->info->pma_config(priv, port);
+}
+
+static int sja1105_clocking_setup_port(struct sja1105_private *priv, int port)
+{
+	struct sja1105_xmii_params_entry *mii;
+	sja1105_phy_interface_t phy_mode;
+	sja1105_mii_role_t role;
+	int rc;
+
+	mii = priv->static_config.tables[BLK_IDX_XMII_PARAMS].entries;
+
+	/* RGMII etc */
+	phy_mode = mii->xmii_mode[port];
+	/* MAC or PHY, for applicable types (not RGMII) */
+	role = mii->phy_mac[port];
+
+	switch (phy_mode) {
+	case XMII_MODE_MII:
+		rc = sja1105_mii_clocking_setup(priv, port, role);
+		break;
+	case XMII_MODE_RMII:
+		rc = sja1105_rmii_clocking_setup(priv, port, role);
+		break;
+	case XMII_MODE_RGMII:
+		rc = sja1105_rgmii_clocking_setup(priv, port, role);
+		break;
+	case XMII_MODE_SGMII:
+		rc = sja1105_sgmii_setup(priv, port);
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (rc)
+		return rc;
+
+	/* Internally pull down the RX_DV/CRS_DV/RX_CTL and RX_ER inputs */
+	return sja1105_cfg_pad_rx_config(priv, port);
+}
+
+static int sja1105_clocking_setup(struct sja1105_private *priv)
+{
+	struct dsa_pdata *pdata = dev_get_uclass_plat(priv->dev);
+	int port, rc;
+
+	for (port = 0; port < pdata->num_ports; port++) {
+		rc = sja1105_clocking_setup_port(priv, port);
+		if (rc < 0)
+			return rc;
+	}
+	return 0;
+}
+
+static int sja1105_pcs_mdio_read(struct mii_dev *bus, int phy, int mmd, int reg)
+{
+	u8 packed_buf[SJA1105_SIZE_MDIO_CMD] = {0};
+	struct sja1105_private *priv = bus->priv;
+	const int size = SJA1105_SIZE_MDIO_CMD;
+	u64 addr, tmp;
+	int rc;
+
+	if (mmd == MDIO_DEVAD_NONE)
+		return -ENODEV;
+
+	if (!priv->info->supports_sgmii[phy])
+		return -ENODEV;
+
+	addr = (mmd << 16) | (reg & GENMASK(15, 0));
+
+	if (mmd != MDIO_MMD_VEND1 && mmd != MDIO_MMD_VEND2)
+		return 0xffff;
+
+	rc = sja1105_xfer_buf(priv, SPI_READ, addr, packed_buf, size);
+	if (rc < 0)
+		return rc;
+
+	sja1105_packing(packed_buf, &tmp, 31, 0, size, UNPACK);
+
+	return tmp & 0xffff;
+}
+
+static int sja1105_pcs_mdio_write(struct mii_dev *bus, int phy, int mmd,
+				  int reg, u16 val)
+{
+	u8 packed_buf[SJA1105_SIZE_MDIO_CMD] = {0};
+	struct sja1105_private *priv = bus->priv;
+	const int size = SJA1105_SIZE_MDIO_CMD;
+	u64 addr, tmp;
+
+	if (mmd == MDIO_DEVAD_NONE)
+		return -ENODEV;
+
+	if (!priv->info->supports_sgmii[phy])
+		return -ENODEV;
+
+	addr = (mmd << 16) | (reg & GENMASK(15, 0));
+	tmp = val;
+
+	if (mmd != MDIO_MMD_VEND1 && mmd != MDIO_MMD_VEND2)
+		return -ENODEV;
+
+	sja1105_packing(packed_buf, &tmp, 31, 0, size, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, addr, packed_buf, size);
+}
+
+static int sja1110_pcs_mdio_read(struct mii_dev *bus, int phy, int mmd, int reg)
+{
+	struct sja1105_private *priv = bus->priv;
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SJA1105_SIZE_MDIO_CMD] = {0};
+	const int size = SJA1105_SIZE_MDIO_CMD;
+	int offset, bank;
+	u64 addr, tmp;
+	int rc;
+
+	if (mmd == MDIO_DEVAD_NONE)
+		return -ENODEV;
+
+	if (regs->pcs_base[phy] == SJA1105_RSV_ADDR)
+		return -ENODEV;
+
+	addr = (mmd << 16) | (reg & GENMASK(15, 0));
+
+	bank = addr >> 8;
+	offset = addr & GENMASK(7, 0);
+
+	/* This addressing scheme reserves register 0xff for the bank address
+	 * register, so that can never be addressed.
+	 */
+	if (offset == 0xff)
+		return -ENODEV;
+
+	tmp = bank;
+
+	sja1105_packing(packed_buf, &tmp, 31, 0, size, PACK);
+
+	rc = sja1105_xfer_buf(priv, SPI_WRITE,
+			      regs->pcs_base[phy] + SJA1110_PCS_BANK_REG,
+			      packed_buf, size);
+	if (rc < 0)
+		return rc;
+
+	rc = sja1105_xfer_buf(priv, SPI_READ, regs->pcs_base[phy] + offset,
+			      packed_buf, size);
+	if (rc < 0)
+		return rc;
+
+	sja1105_packing(packed_buf, &tmp, 31, 0, size, UNPACK);
+
+	return tmp & 0xffff;
+}
+
+static int sja1110_pcs_mdio_write(struct mii_dev *bus, int phy, int mmd,
+				  int reg, u16 val)
+{
+	struct sja1105_private *priv = bus->priv;
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SJA1105_SIZE_MDIO_CMD] = {0};
+	const int size = SJA1105_SIZE_MDIO_CMD;
+	int offset, bank;
+	u64 addr, tmp;
+	int rc;
+
+	if (mmd == MDIO_DEVAD_NONE)
+		return -ENODEV;
+
+	if (regs->pcs_base[phy] == SJA1105_RSV_ADDR)
+		return -ENODEV;
+
+	addr = (mmd << 16) | (reg & GENMASK(15, 0));
+
+	bank = addr >> 8;
+	offset = addr & GENMASK(7, 0);
+
+	/* This addressing scheme reserves register 0xff for the bank address
+	 * register, so that can never be addressed.
+	 */
+	if (offset == 0xff)
+		return -ENODEV;
+
+	tmp = bank;
+	sja1105_packing(packed_buf, &tmp, 31, 0, size, PACK);
+
+	rc = sja1105_xfer_buf(priv, SPI_WRITE,
+			      regs->pcs_base[phy] + SJA1110_PCS_BANK_REG,
+			      packed_buf, size);
+	if (rc < 0)
+		return rc;
+
+	tmp = val;
+	sja1105_packing(packed_buf, &tmp, 31, 0, size, PACK);
+
+	return sja1105_xfer_buf(priv, SPI_WRITE, regs->pcs_base[phy] + offset,
+				packed_buf, size);
+}
+
+static int sja1105_mdiobus_register(struct sja1105_private *priv)
+{
+	struct udevice *dev = priv->dev;
+	struct mii_dev *bus;
+	int rc;
+
+	if (!priv->info->pcs_mdio_read || !priv->info->pcs_mdio_write)
+		return 0;
+
+	bus = mdio_alloc();
+	if (!bus)
+		return -ENOMEM;
+
+	snprintf(bus->name, MDIO_NAME_LEN, "%s-pcs", dev->name);
+	bus->read = priv->info->pcs_mdio_read;
+	bus->write = priv->info->pcs_mdio_write;
+	bus->priv = priv;
+
+	rc = mdio_register(bus);
+	if (rc) {
+		mdio_free(bus);
+		return rc;
+	}
+
+	priv->mdio_pcs = bus;
+
+	return 0;
+}
+
+static void sja1105_mdiobus_unregister(struct sja1105_private *priv)
+{
+	if (!priv->mdio_pcs)
+		return;
+
+	mdio_unregister(priv->mdio_pcs);
+	mdio_free(priv->mdio_pcs);
+}
+
+static const struct sja1105_regs sja1105et_regs = {
+	.device_id = 0x0,
+	.prod_id = 0x100BC3,
+	.status = 0x1,
+	.port_control = 0x11,
+	.config = 0x020000,
+	.rgu = 0x100440,
+	/* UM10944.pdf, Table 86, ACU Register overview */
+	.pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808},
+	.pad_mii_rx = {0x100801, 0x100803, 0x100805, 0x100807, 0x100809},
+	.rmii_pll1 = 0x10000A,
+	.cgu_idiv = {0x10000B, 0x10000C, 0x10000D, 0x10000E, 0x10000F},
+	/* UM10944.pdf, Table 78, CGU Register overview */
+	.mii_tx_clk = {0x100013, 0x10001A, 0x100021, 0x100028, 0x10002F},
+	.mii_rx_clk = {0x100014, 0x10001B, 0x100022, 0x100029, 0x100030},
+	.mii_ext_tx_clk = {0x100018, 0x10001F, 0x100026, 0x10002D, 0x100034},
+	.mii_ext_rx_clk = {0x100019, 0x100020, 0x100027, 0x10002E, 0x100035},
+	.rgmii_tx_clk = {0x100016, 0x10001D, 0x100024, 0x10002B, 0x100032},
+	.rmii_ref_clk = {0x100015, 0x10001C, 0x100023, 0x10002A, 0x100031},
+	.rmii_ext_tx_clk = {0x100018, 0x10001F, 0x100026, 0x10002D, 0x100034},
+};
+
+static const struct sja1105_regs sja1105pqrs_regs = {
+	.device_id = 0x0,
+	.prod_id = 0x100BC3,
+	.status = 0x1,
+	.port_control = 0x12,
+	.config = 0x020000,
+	.rgu = 0x100440,
+	/* UM10944.pdf, Table 86, ACU Register overview */
+	.pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808},
+	.pad_mii_rx = {0x100801, 0x100803, 0x100805, 0x100807, 0x100809},
+	.pad_mii_id = {0x100810, 0x100811, 0x100812, 0x100813, 0x100814},
+	.rmii_pll1 = 0x10000A,
+	.cgu_idiv = {0x10000B, 0x10000C, 0x10000D, 0x10000E, 0x10000F},
+	/* UM11040.pdf, Table 114 */
+	.mii_tx_clk = {0x100013, 0x100019, 0x10001F, 0x100025, 0x10002B},
+	.mii_rx_clk = {0x100014, 0x10001A, 0x100020, 0x100026, 0x10002C},
+	.mii_ext_tx_clk = {0x100017, 0x10001D, 0x100023, 0x100029, 0x10002F},
+	.mii_ext_rx_clk = {0x100018, 0x10001E, 0x100024, 0x10002A, 0x100030},
+	.rgmii_tx_clk = {0x100016, 0x10001C, 0x100022, 0x100028, 0x10002E},
+	.rmii_ref_clk = {0x100015, 0x10001B, 0x100021, 0x100027, 0x10002D},
+	.rmii_ext_tx_clk = {0x100017, 0x10001D, 0x100023, 0x100029, 0x10002F},
+};
+
+static const struct sja1105_regs sja1110_regs = {
+	.device_id = SJA1110_SPI_ADDR(0x0),
+	.prod_id = SJA1110_ACU_ADDR(0xf00),
+	.status = SJA1110_SPI_ADDR(0x4),
+	.port_control = SJA1110_SPI_ADDR(0x50), /* actually INHIB_TX */
+	.config = 0x020000,
+	.rgu = SJA1110_RGU_ADDR(0x100), /* Reset Control Register 0 */
+	/* Ports 2 and 3 are capable of xMII, but there isn't anything to
+	 * configure in the CGU/ACU for them.
+	 */
+	.pad_mii_tx = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR},
+	.pad_mii_rx = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR},
+	.pad_mii_id = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1110_ACU_ADDR(0x18), SJA1110_ACU_ADDR(0x28),
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR},
+	.rmii_pll1 = SJA1105_RSV_ADDR,
+	.cgu_idiv = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		     SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		     SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		     SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
+	.mii_tx_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
+	.mii_rx_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		       SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
+	.mii_ext_tx_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			   SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			   SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			   SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
+	.mii_ext_rx_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			   SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			   SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			   SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
+	.rgmii_tx_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			 SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			 SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			 SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
+	.rmii_ref_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			 SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			 SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			 SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
+	.rmii_ext_tx_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			    SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			    SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			    SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			    SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+			    SJA1105_RSV_ADDR},
+	.pcs_base = {SJA1105_RSV_ADDR, 0x1c1400, 0x1c1800, 0x1c1c00, 0x1c2000,
+		     SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+		     SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
+};
+
+enum sja1105_switch_id {
+	SJA1105E = 0,
+	SJA1105T,
+	SJA1105P,
+	SJA1105Q,
+	SJA1105R,
+	SJA1105S,
+	SJA1110A,
+	SJA1110B,
+	SJA1110C,
+	SJA1110D,
+	SJA1105_MAX_SWITCH_ID,
+};
+
+static const struct sja1105_info sja1105_info[] = {
+	[SJA1105E] = {
+		.device_id		= SJA1105E_DEVICE_ID,
+		.part_no		= SJA1105ET_PART_NO,
+		.static_ops		= sja1105et_table_ops,
+		.reset_cmd		= sja1105et_reset_cmd,
+		.regs			= &sja1105et_regs,
+		.port_speed		= {
+			[SJA1105_SPEED_AUTO] = 0,
+			[SJA1105_SPEED_10MBPS] = 3,
+			[SJA1105_SPEED_100MBPS] = 2,
+			[SJA1105_SPEED_1000MBPS] = 1,
+		},
+		.supports_mii		= {true, true, true, true, true},
+		.supports_rmii		= {true, true, true, true, true},
+		.supports_rgmii		= {true, true, true, true, true},
+		.name			= "SJA1105E",
+	},
+	[SJA1105T] = {
+		.device_id		= SJA1105T_DEVICE_ID,
+		.part_no		= SJA1105ET_PART_NO,
+		.static_ops		= sja1105et_table_ops,
+		.reset_cmd		= sja1105et_reset_cmd,
+		.regs			= &sja1105et_regs,
+		.port_speed		= {
+			[SJA1105_SPEED_AUTO] = 0,
+			[SJA1105_SPEED_10MBPS] = 3,
+			[SJA1105_SPEED_100MBPS] = 2,
+			[SJA1105_SPEED_1000MBPS] = 1,
+		},
+		.supports_mii		= {true, true, true, true, true},
+		.supports_rmii		= {true, true, true, true, true},
+		.supports_rgmii		= {true, true, true, true, true},
+		.name			= "SJA1105T",
+	},
+	[SJA1105P] = {
+		.device_id		= SJA1105PR_DEVICE_ID,
+		.part_no		= SJA1105P_PART_NO,
+		.static_ops		= sja1105pqrs_table_ops,
+		.setup_rgmii_delay	= sja1105pqrs_setup_rgmii_delay,
+		.reset_cmd		= sja1105pqrs_reset_cmd,
+		.regs			= &sja1105pqrs_regs,
+		.port_speed		= {
+			[SJA1105_SPEED_AUTO] = 0,
+			[SJA1105_SPEED_10MBPS] = 3,
+			[SJA1105_SPEED_100MBPS] = 2,
+			[SJA1105_SPEED_1000MBPS] = 1,
+		},
+		.supports_mii		= {true, true, true, true, true},
+		.supports_rmii		= {true, true, true, true, true},
+		.supports_rgmii		= {true, true, true, true, true},
+		.name			= "SJA1105P",
+	},
+	[SJA1105Q] = {
+		.device_id		= SJA1105QS_DEVICE_ID,
+		.part_no		= SJA1105Q_PART_NO,
+		.static_ops		= sja1105pqrs_table_ops,
+		.setup_rgmii_delay	= sja1105pqrs_setup_rgmii_delay,
+		.reset_cmd		= sja1105pqrs_reset_cmd,
+		.regs			= &sja1105pqrs_regs,
+		.port_speed		= {
+			[SJA1105_SPEED_AUTO] = 0,
+			[SJA1105_SPEED_10MBPS] = 3,
+			[SJA1105_SPEED_100MBPS] = 2,
+			[SJA1105_SPEED_1000MBPS] = 1,
+		},
+		.supports_mii		= {true, true, true, true, true},
+		.supports_rmii		= {true, true, true, true, true},
+		.supports_rgmii		= {true, true, true, true, true},
+		.name			= "SJA1105Q",
+	},
+	[SJA1105R] = {
+		.device_id		= SJA1105PR_DEVICE_ID,
+		.part_no		= SJA1105R_PART_NO,
+		.static_ops		= sja1105pqrs_table_ops,
+		.setup_rgmii_delay	= sja1105pqrs_setup_rgmii_delay,
+		.reset_cmd		= sja1105pqrs_reset_cmd,
+		.regs			= &sja1105pqrs_regs,
+		.pcs_mdio_read		= sja1105_pcs_mdio_read,
+		.pcs_mdio_write		= sja1105_pcs_mdio_write,
+		.pma_config		= sja1105_pma_config,
+		.port_speed		= {
+			[SJA1105_SPEED_AUTO] = 0,
+			[SJA1105_SPEED_10MBPS] = 3,
+			[SJA1105_SPEED_100MBPS] = 2,
+			[SJA1105_SPEED_1000MBPS] = 1,
+		},
+		.supports_mii		= {true, true, true, true, true},
+		.supports_rmii		= {true, true, true, true, true},
+		.supports_rgmii		= {true, true, true, true, true},
+		.supports_sgmii		= {false, false, false, false, true},
+		.name			= "SJA1105R",
+	},
+	[SJA1105S] = {
+		.device_id		= SJA1105QS_DEVICE_ID,
+		.part_no		= SJA1105S_PART_NO,
+		.static_ops		= sja1105pqrs_table_ops,
+		.setup_rgmii_delay	= sja1105pqrs_setup_rgmii_delay,
+		.reset_cmd		= sja1105pqrs_reset_cmd,
+		.regs			= &sja1105pqrs_regs,
+		.pcs_mdio_read		= sja1105_pcs_mdio_read,
+		.pcs_mdio_write		= sja1105_pcs_mdio_write,
+		.pma_config		= sja1105_pma_config,
+		.port_speed		= {
+			[SJA1105_SPEED_AUTO] = 0,
+			[SJA1105_SPEED_10MBPS] = 3,
+			[SJA1105_SPEED_100MBPS] = 2,
+			[SJA1105_SPEED_1000MBPS] = 1,
+		},
+		.supports_mii		= {true, true, true, true, true},
+		.supports_rmii		= {true, true, true, true, true},
+		.supports_rgmii		= {true, true, true, true, true},
+		.supports_sgmii		= {false, false, false, false, true},
+		.name			= "SJA1105S",
+	},
+	[SJA1110A] = {
+		.device_id		= SJA1110_DEVICE_ID,
+		.part_no		= SJA1110A_PART_NO,
+		.static_ops		= sja1110_table_ops,
+		.setup_rgmii_delay	= sja1110_setup_rgmii_delay,
+		.reset_cmd		= sja1110_reset_cmd,
+		.regs			= &sja1110_regs,
+		.pcs_mdio_read		= sja1110_pcs_mdio_read,
+		.pcs_mdio_write		= sja1110_pcs_mdio_write,
+		.pma_config		= sja1110_pma_config,
+		.port_speed		= {
+			[SJA1105_SPEED_AUTO] = 0,
+			[SJA1105_SPEED_10MBPS] = 4,
+			[SJA1105_SPEED_100MBPS] = 3,
+			[SJA1105_SPEED_1000MBPS] = 2,
+		},
+		.supports_mii		= {true, true, true, true, false,
+					   true, true, true, true, true, true},
+		.supports_rmii		= {false, false, true, true, false,
+					   false, false, false, false, false, false},
+		.supports_rgmii		= {false, false, true, true, false,
+					   false, false, false, false, false, false},
+		.supports_sgmii		= {false, true, true, true, true,
+					   false, false, false, false, false, false},
+		.name			= "SJA1110A",
+	},
+	[SJA1110B] = {
+		.device_id		= SJA1110_DEVICE_ID,
+		.part_no		= SJA1110B_PART_NO,
+		.static_ops		= sja1110_table_ops,
+		.setup_rgmii_delay	= sja1110_setup_rgmii_delay,
+		.reset_cmd		= sja1110_reset_cmd,
+		.regs			= &sja1110_regs,
+		.pcs_mdio_read		= sja1110_pcs_mdio_read,
+		.pcs_mdio_write		= sja1110_pcs_mdio_write,
+		.pma_config		= sja1110_pma_config,
+		.port_speed		= {
+			[SJA1105_SPEED_AUTO] = 0,
+			[SJA1105_SPEED_10MBPS] = 4,
+			[SJA1105_SPEED_100MBPS] = 3,
+			[SJA1105_SPEED_1000MBPS] = 2,
+		},
+		.supports_mii		= {true, true, true, true, false,
+					   true, true, true, true, true, false},
+		.supports_rmii		= {false, false, true, true, false,
+					   false, false, false, false, false, false},
+		.supports_rgmii		= {false, false, true, true, false,
+					   false, false, false, false, false, false},
+		.supports_sgmii		= {false, false, false, true, true,
+					   false, false, false, false, false, false},
+		.name			= "SJA1110B",
+	},
+	[SJA1110C] = {
+		.device_id		= SJA1110_DEVICE_ID,
+		.part_no		= SJA1110C_PART_NO,
+		.static_ops		= sja1110_table_ops,
+		.setup_rgmii_delay	= sja1110_setup_rgmii_delay,
+		.reset_cmd		= sja1110_reset_cmd,
+		.regs			= &sja1110_regs,
+		.pcs_mdio_read		= sja1110_pcs_mdio_read,
+		.pcs_mdio_write		= sja1110_pcs_mdio_write,
+		.pma_config		= sja1110_pma_config,
+		.port_speed		= {
+			[SJA1105_SPEED_AUTO] = 0,
+			[SJA1105_SPEED_10MBPS] = 4,
+			[SJA1105_SPEED_100MBPS] = 3,
+			[SJA1105_SPEED_1000MBPS] = 2,
+		},
+		.supports_mii		= {true, true, true, true, false,
+					   true, true, true, false, false, false},
+		.supports_rmii		= {false, false, true, true, false,
+					   false, false, false, false, false, false},
+		.supports_rgmii		= {false, false, true, true, false,
+					   false, false, false, false, false, false},
+		.supports_sgmii		= {false, false, false, false, true,
+					   false, false, false, false, false, false},
+		.name			= "SJA1110C",
+	},
+	[SJA1110D] = {
+		.device_id		= SJA1110_DEVICE_ID,
+		.part_no		= SJA1110D_PART_NO,
+		.static_ops		= sja1110_table_ops,
+		.setup_rgmii_delay	= sja1110_setup_rgmii_delay,
+		.reset_cmd		= sja1110_reset_cmd,
+		.regs			= &sja1110_regs,
+		.pcs_mdio_read		= sja1110_pcs_mdio_read,
+		.pcs_mdio_write		= sja1110_pcs_mdio_write,
+		.pma_config		= sja1110_pma_config,
+		.port_speed		= {
+			[SJA1105_SPEED_AUTO] = 0,
+			[SJA1105_SPEED_10MBPS] = 4,
+			[SJA1105_SPEED_100MBPS] = 3,
+			[SJA1105_SPEED_1000MBPS] = 2,
+		},
+		.supports_mii		= {true, false, true, false, false,
+					   true, true, true, false, false, false},
+		.supports_rmii		= {false, false, true, false, false,
+					   false, false, false, false, false, false},
+		.supports_rgmii		= {false, false, true, false, false,
+					   false, false, false, false, false, false},
+		.supports_sgmii		= {false, true, true, true, true,
+					   false, false, false, false, false, false},
+		.name			= "SJA1110D",
+	},
+};
+
+struct sja1105_status {
+	u64 configs;
+	u64 crcchkl;
+	u64 ids;
+	u64 crcchkg;
+};
+
+static void sja1105_status_unpack(void *buf, struct sja1105_status *status)
+{
+	sja1105_packing(buf, &status->configs, 31, 31, 4, UNPACK);
+	sja1105_packing(buf, &status->crcchkl, 30, 30, 4, UNPACK);
+	sja1105_packing(buf, &status->ids,     29, 29, 4, UNPACK);
+	sja1105_packing(buf, &status->crcchkg, 28, 28, 4, UNPACK);
+}
+
+static int sja1105_status_get(struct sja1105_private *priv,
+			      struct sja1105_status *status)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[4];
+	int rc;
+
+	rc = sja1105_xfer_buf(priv, SPI_READ, regs->status, packed_buf, 4);
+	if (rc < 0)
+		return rc;
+
+	sja1105_status_unpack(packed_buf, status);
+
+	return 0;
+}
+
+/* Not const because unpacking priv->static_config into buffers and preparing
+ * for upload requires the recalculation of table CRCs and updating the
+ * structures with these.
+ */
+static int
+static_config_buf_prepare_for_upload(struct sja1105_private *priv,
+				     void *config_buf, int buf_len)
+{
+	struct sja1105_static_config *config = &priv->static_config;
+	struct sja1105_table_header final_header;
+	char *final_header_ptr;
+	int crc_len;
+
+	/* Write Device ID and config tables to config_buf */
+	sja1105_static_config_pack(config_buf, config);
+	/* Recalculate CRC of the last header (right now 0xDEADBEEF).
+	 * Don't include the CRC field itself.
+	 */
+	crc_len = buf_len - 4;
+	/* Read the whole table header */
+	final_header_ptr = config_buf + buf_len - SJA1105_SIZE_TABLE_HEADER;
+	sja1105_table_header_packing(final_header_ptr, &final_header, UNPACK);
+	/* Modify */
+	final_header.crc = sja1105_crc32(config_buf, crc_len);
+	/* Rewrite */
+	sja1105_table_header_packing(final_header_ptr, &final_header, PACK);
+
+	return 0;
+}
+
+static int sja1105_static_config_upload(struct sja1105_private *priv)
+{
+	struct sja1105_static_config *config = &priv->static_config;
+	const struct sja1105_regs *regs = priv->info->regs;
+	struct sja1105_status status;
+	u8 *config_buf;
+	int buf_len;
+	int rc;
+
+	buf_len = sja1105_static_config_get_length(config);
+	config_buf = calloc(buf_len, sizeof(char));
+	if (!config_buf)
+		return -ENOMEM;
+
+	rc = static_config_buf_prepare_for_upload(priv, config_buf, buf_len);
+	if (rc < 0) {
+		printf("Invalid config, cannot upload\n");
+		rc = -EINVAL;
+		goto out;
+	}
+	/* Put the SJA1105 in programming mode */
+	rc = priv->info->reset_cmd(priv);
+	if (rc < 0) {
+		printf("Failed to reset switch\n");
+		goto out;
+	}
+	/* Wait for the switch to come out of reset */
+	udelay(1000);
+	/* Upload the static config to the device */
+	rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->config,
+			      config_buf, buf_len);
+	if (rc < 0) {
+		printf("Failed to upload config\n");
+		goto out;
+	}
+	/* Check that SJA1105 responded well to the config upload */
+	rc = sja1105_status_get(priv, &status);
+	if (rc < 0)
+		goto out;
+
+	if (status.ids == 1) {
+		printf("Mismatch between hardware and static config device id. "
+		       "Wrote 0x%llx, wants 0x%llx\n",
+		       config->device_id, priv->info->device_id);
+		rc = -EIO;
+		goto out;
+	}
+	if (status.crcchkl == 1 || status.crcchkg == 1) {
+		printf("Switch reported invalid CRC on static config\n");
+		rc = -EIO;
+		goto out;
+	}
+	if (status.configs == 0) {
+		printf("Switch reported that config is invalid\n");
+		rc = -EIO;
+		goto out;
+	}
+
+out:
+	free(config_buf);
+	return rc;
+}
+
+static int sja1105_static_config_reload(struct sja1105_private *priv)
+{
+	int rc;
+
+	rc = sja1105_static_config_upload(priv);
+	if (rc < 0) {
+		printf("Failed to load static config: %d\n", rc);
+		return rc;
+	}
+
+	/* Configure the CGU (PHY link modes and speeds) */
+	rc = sja1105_clocking_setup(priv);
+	if (rc < 0) {
+		printf("Failed to configure MII clocking: %d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int sja1105_port_probe(struct udevice *dev, int port,
+			      struct phy_device *phy)
+{
+	struct sja1105_private *priv = dev_get_priv(dev);
+	ofnode node = dsa_port_get_ofnode(dev, port);
+	phy_interface_t phy_mode = phy->interface;
+
+	priv->xpcs_cfg[port].inband_an = ofnode_eth_uses_inband_aneg(node);
+
+	if (phy_mode == PHY_INTERFACE_MODE_MII ||
+	    phy_mode == PHY_INTERFACE_MODE_RMII) {
+		phy->supported &= PHY_BASIC_FEATURES;
+		phy->advertising &= PHY_BASIC_FEATURES;
+	} else {
+		phy->supported &= PHY_GBIT_FEATURES;
+		phy->advertising &= PHY_GBIT_FEATURES;
+	}
+
+	return phy_config(phy);
+}
+
+static int sja1105_port_enable(struct udevice *dev, int port,
+			       struct phy_device *phy)
+{
+	struct sja1105_private *priv = dev_get_priv(dev);
+	phy_interface_t phy_mode = phy->interface;
+	struct sja1105_xmii_params_entry *mii;
+	struct sja1105_mac_config_entry *mac;
+	int rc;
+
+	rc = phy_startup(phy);
+	if (rc)
+		return rc;
+
+	mii = priv->static_config.tables[BLK_IDX_XMII_PARAMS].entries;
+	mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries;
+
+	switch (phy_mode) {
+	case PHY_INTERFACE_MODE_MII:
+		if (!priv->info->supports_mii[port])
+			goto unsupported;
+
+		mii->xmii_mode[port] = XMII_MODE_MII;
+		break;
+	case PHY_INTERFACE_MODE_RMII:
+		if (!priv->info->supports_rmii[port])
+			goto unsupported;
+
+		mii->xmii_mode[port] = XMII_MODE_RMII;
+		break;
+	case PHY_INTERFACE_MODE_RGMII:
+	case PHY_INTERFACE_MODE_RGMII_ID:
+	case PHY_INTERFACE_MODE_RGMII_RXID:
+	case PHY_INTERFACE_MODE_RGMII_TXID:
+		if (!priv->info->supports_rgmii[port])
+			goto unsupported;
+
+		mii->xmii_mode[port] = XMII_MODE_RGMII;
+		break;
+	case PHY_INTERFACE_MODE_SGMII:
+		if (!priv->info->supports_sgmii[port])
+			goto unsupported;
+
+		mii->xmii_mode[port] = XMII_MODE_SGMII;
+		mii->special[port] = true;
+		break;
+unsupported:
+	default:
+		dev_err(dev, "Unsupported PHY mode %d on port %d!\n",
+			phy_mode, port);
+		return -EINVAL;
+	}
+
+	/* RevMII, RevRMII not supported */
+	mii->phy_mac[port] = XMII_MAC;
+
+	/* Let the PHY handle the RGMII delays, if present. */
+	if (phy->phy_id == PHY_FIXED_ID) {
+		if (phy_mode == PHY_INTERFACE_MODE_RGMII_RXID ||
+		    phy_mode == PHY_INTERFACE_MODE_RGMII_ID)
+			priv->rgmii_rx_delay[port] = true;
+
+		if (phy_mode == PHY_INTERFACE_MODE_RGMII_TXID ||
+		    phy_mode == PHY_INTERFACE_MODE_RGMII_ID)
+			priv->rgmii_tx_delay[port] = true;
+
+		if ((priv->rgmii_rx_delay[port] ||
+		     priv->rgmii_tx_delay[port]) &&
+		     !priv->info->setup_rgmii_delay) {
+			printf("Chip does not support internal RGMII delays\n");
+			return -EINVAL;
+		}
+	}
+
+	if (mii->xmii_mode[port] == XMII_MODE_SGMII) {
+		mac[port].speed = priv->info->port_speed[SJA1105_SPEED_1000MBPS];
+		priv->xpcs_cfg[port].speed = phy->speed;
+	} else if (phy->speed == SPEED_1000) {
+		mac[port].speed = priv->info->port_speed[SJA1105_SPEED_1000MBPS];
+	} else if (phy->speed == SPEED_100) {
+		mac[port].speed = priv->info->port_speed[SJA1105_SPEED_100MBPS];
+	} else if (phy->speed == SPEED_10) {
+		mac[port].speed = priv->info->port_speed[SJA1105_SPEED_10MBPS];
+	} else {
+		printf("Invalid PHY speed %d on port %d\n", phy->speed, port);
+		return -EINVAL;
+	}
+
+	return sja1105_static_config_reload(priv);
+}
+
+static void sja1105_port_disable(struct udevice *dev, int port,
+				 struct phy_device *phy)
+{
+	phy_shutdown(phy);
+}
+
+static int sja1105_xmit(struct udevice *dev, int port, void *packet, int length)
+{
+	struct sja1105_private *priv = dev_get_priv(dev);
+	u8 *from = (u8 *)packet + VLAN_HLEN;
+	struct vlan_ethhdr *hdr = packet;
+	u8 *dest = (u8 *)packet;
+
+	memmove(dest, from, 2 * ETH_ALEN);
+	hdr->h_vlan_proto = htons(ETH_P_SJA1105);
+	hdr->h_vlan_TCI = htons(priv->pvid[port]);
+
+	return 0;
+}
+
+static int sja1105_rcv(struct udevice *dev, int *port, void *packet, int length)
+{
+	struct vlan_ethhdr *hdr = packet;
+	u8 *dest = packet + VLAN_HLEN;
+	u8 *from = packet;
+
+	if (ntohs(hdr->h_vlan_proto) != ETH_P_SJA1105)
+		return -EINVAL;
+
+	*port = ntohs(hdr->h_vlan_TCI) & DSA_8021Q_PORT_MASK;
+	memmove(dest, from, 2 * ETH_ALEN);
+
+	return 0;
+}
+
+static const struct dsa_ops sja1105_dsa_ops = {
+	.port_probe	= sja1105_port_probe,
+	.port_enable	= sja1105_port_enable,
+	.port_disable	= sja1105_port_disable,
+	.xmit		= sja1105_xmit,
+	.rcv		= sja1105_rcv,
+};
+
+static int sja1105_init(struct sja1105_private *priv)
+{
+	int rc;
+
+	rc = sja1105_static_config_init(priv);
+	if (rc) {
+		printf("Failed to initialize static config: %d\n", rc);
+		return rc;
+	}
+
+	rc = sja1105_mdiobus_register(priv);
+	if (rc) {
+		printf("Failed to register MDIO bus: %d\n", rc);
+		goto err_mdiobus_register;
+	}
+
+	return 0;
+
+err_mdiobus_register:
+	sja1105_static_config_free(&priv->static_config);
+
+	return rc;
+}
+
+static int sja1105_check_device_id(struct sja1105_private *priv)
+{
+	const struct sja1105_regs *regs = priv->info->regs;
+	u8 packed_buf[SJA1105_SIZE_DEVICE_ID] = {0};
+	enum sja1105_switch_id id;
+	u64 device_id;
+	u64 part_no;
+	int rc;
+
+	rc = sja1105_xfer_buf(priv, SPI_READ, regs->device_id, packed_buf,
+			      SJA1105_SIZE_DEVICE_ID);
+	if (rc < 0)
+		return rc;
+
+	sja1105_packing(packed_buf, &device_id, 31, 0, SJA1105_SIZE_DEVICE_ID,
+			UNPACK);
+
+	if (device_id != priv->info->device_id) {
+		printf("Expected device ID 0x%llx but read 0x%llx\n",
+		       priv->info->device_id, device_id);
+		return -ENODEV;
+	}
+
+	rc = sja1105_xfer_buf(priv, SPI_READ, regs->prod_id, packed_buf,
+			      SJA1105_SIZE_DEVICE_ID);
+	if (rc < 0)
+		return rc;
+
+	sja1105_packing(packed_buf, &part_no, 19, 4, SJA1105_SIZE_DEVICE_ID,
+			UNPACK);
+
+	for (id = 0; id < SJA1105_MAX_SWITCH_ID; id++) {
+		const struct sja1105_info *info = &sja1105_info[id];
+
+		/* Is what's been probed in our match table at all? */
+		if (info->device_id != device_id || info->part_no != part_no)
+			continue;
+
+		/* But is it what's in the device tree? */
+		if (priv->info->device_id != device_id ||
+		    priv->info->part_no != part_no) {
+			printf("Device tree specifies chip %s but found %s, please fix it!\n",
+			       priv->info->name, info->name);
+			/* It isn't. No problem, pick that up. */
+			priv->info = info;
+		}
+
+		return 0;
+	}
+
+	printf("Unexpected {device ID, part number}: 0x%llx 0x%llx\n",
+	       device_id, part_no);
+
+	return -ENODEV;
+}
+
+static int sja1105_probe(struct udevice *dev)
+{
+	enum sja1105_switch_id id = dev_get_driver_data(dev);
+	struct sja1105_private *priv = dev_get_priv(dev);
+	int rc;
+
+	if (ofnode_valid(dev_ofnode(dev)) &&
+	    !ofnode_is_available(dev_ofnode(dev))) {
+		dev_dbg(dev, "switch disabled\n");
+		return -ENODEV;
+	}
+
+	priv->info = &sja1105_info[id];
+	priv->dev = dev;
+
+	rc = sja1105_check_device_id(priv);
+	if (rc < 0) {
+		dev_err(dev, "Device ID check failed: %d\n", rc);
+		return rc;
+	}
+
+	dsa_set_tagging(dev, VLAN_HLEN, 0);
+
+	return sja1105_init(priv);
+}
+
+static int sja1105_remove(struct udevice *dev)
+{
+	struct sja1105_private *priv = dev_get_priv(dev);
+
+	sja1105_mdiobus_unregister(priv);
+	sja1105_static_config_free(&priv->static_config);
+
+	return 0;
+}
+
+static const struct udevice_id sja1105_ids[] = {
+	{ .compatible = "nxp,sja1105e", .data = SJA1105E },
+	{ .compatible = "nxp,sja1105t", .data = SJA1105T },
+	{ .compatible = "nxp,sja1105p", .data = SJA1105P },
+	{ .compatible = "nxp,sja1105q", .data = SJA1105Q },
+	{ .compatible = "nxp,sja1105r", .data = SJA1105R },
+	{ .compatible = "nxp,sja1105s", .data = SJA1105S },
+	{ .compatible = "nxp,sja1110a", .data = SJA1110A },
+	{ .compatible = "nxp,sja1110b", .data = SJA1110B },
+	{ .compatible = "nxp,sja1110c", .data = SJA1110C },
+	{ .compatible = "nxp,sja1110d", .data = SJA1110D },
+	{ }
+};
+
+U_BOOT_DRIVER(sja1105) = {
+	.name		= "sja1105",
+	.id		= UCLASS_DSA,
+	.of_match	= sja1105_ids,
+	.probe		= sja1105_probe,
+	.remove		= sja1105_remove,
+	.ops		= &sja1105_dsa_ops,
+	.priv_auto	= sizeof(struct sja1105_private),
+};
diff --git a/drivers/net/tsec.c b/drivers/net/tsec.c
index 0ce9765..beca886 100644
--- a/drivers/net/tsec.c
+++ b/drivers/net/tsec.c
@@ -156,6 +156,19 @@
 	return 0;
 }
 
+static int __maybe_unused tsec_set_promisc(struct udevice *dev, bool enable)
+{
+	struct tsec_private *priv = dev_get_priv(dev);
+	struct tsec __iomem *regs = priv->regs;
+
+	if (enable)
+		setbits_be32(&regs->rctrl, RCTRL_PROM);
+	else
+		clrbits_be32(&regs->rctrl, RCTRL_PROM);
+
+	return 0;
+}
+
 /*
  * Initialized required registers to appropriate values, zeroing
  * those we don't care about (unless zero is bad, in which case,
@@ -186,8 +199,6 @@
 	out_be32(&regs->hash.gaddr6, 0);
 	out_be32(&regs->hash.gaddr7, 0);
 
-	out_be32(&regs->rctrl, 0x00000000);
-
 	/* Init RMON mib registers */
 	memset((void *)&regs->rmon, 0, sizeof(regs->rmon));
 
@@ -432,7 +443,7 @@
  * of the eTSEC port initialization sequence,
  * the eTSEC Rx logic may not be properly initialized.
  */
-void redundant_init(struct tsec_private *priv)
+static void redundant_init(struct tsec_private *priv)
 {
 	struct tsec __iomem *regs = priv->regs;
 	uint t, count = 0;
@@ -454,7 +465,7 @@
 		0x71, 0x72};
 
 	/* Enable promiscuous mode */
-	setbits_be32(&regs->rctrl, 0x8);
+	setbits_be32(&regs->rctrl, RCTRL_PROM);
 	/* Enable loopback mode */
 	setbits_be32(&regs->maccfg1, MACCFG1_LOOPBACK);
 	/* Enable transmit and receive */
@@ -506,7 +517,7 @@
 	if (fail)
 		panic("eTSEC init fail!\n");
 	/* Disable promiscuous mode */
-	clrbits_be32(&regs->rctrl, 0x8);
+	clrbits_be32(&regs->rctrl, RCTRL_PROM);
 	/* Disable loopback mode */
 	clrbits_be32(&regs->maccfg1, MACCFG1_LOOPBACK);
 }
@@ -932,6 +943,7 @@
 	.free_pkt = tsec_free_pkt,
 	.stop = tsec_halt,
 	.mcast = tsec_mcast_addr,
+	.set_promisc = tsec_set_promisc,
 };
 
 static struct tsec_data etsec2_data = {
diff --git a/include/dm/of_extra.h b/include/dm/of_extra.h
index f0d2054..c2498aa 100644
--- a/include/dm/of_extra.h
+++ b/include/dm/of_extra.h
@@ -114,4 +114,18 @@
  */
 bool ofnode_phy_is_fixed_link(ofnode eth_node, ofnode *phy_node);
 
+/**
+ * ofnode_eth_uses_inband_aneg() - Detect whether MAC should use in-band autoneg
+ *
+ * This function detects whether the Ethernet controller should use IEEE 802.3
+ * clause 37 in-band autonegotiation for serial protocols such as 1000base-x,
+ * SGMII, USXGMII, etc. The property is relevant when the Ethernet controller
+ * is connected to an on-board PHY or an SFP cage, and is not relevant when it
+ * has a fixed link (in that case, in-band autoneg should not be used).
+ *
+ * @param eth_node	ofnode belonging to the Ethernet controller
+ * @return true if in-band autoneg should be used, false otherwise
+ */
+bool ofnode_eth_uses_inband_aneg(ofnode eth_node);
+
 #endif
diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h
new file mode 100644
index 0000000..cbc82f4
--- /dev/null
+++ b/include/linux/if_vlan.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * VLAN		An implementation of 802.1Q VLAN tagging.
+ *
+ * Authors:	Ben Greear <greearb@candelatech.com>
+ */
+#ifndef _LINUX_IF_VLAN_H_
+#define _LINUX_IF_VLAN_H_
+
+/**
+ *	struct vlan_ethhdr - vlan ethernet header (ethhdr + vlan_hdr)
+ *	@h_dest: destination ethernet address
+ *	@h_source: source ethernet address
+ *	@h_vlan_proto: ethernet protocol
+ *	@h_vlan_TCI: priority and VLAN ID
+ *	@h_vlan_encapsulated_proto: packet type ID or len
+ */
+struct vlan_ethhdr {
+	unsigned char	h_dest[ETH_ALEN];
+	unsigned char	h_source[ETH_ALEN];
+	__be16		h_vlan_proto;
+	__be16		h_vlan_TCI;
+	__be16		h_vlan_encapsulated_proto;
+};
+
+#endif /* !(_LINUX_IF_VLAN_H_) */
diff --git a/include/net/dsa.h b/include/net/dsa.h
index a339a49..1b1068c 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -6,6 +6,7 @@
 #ifndef __DSA_H__
 #define __DSA_H__
 
+#include <dm/ofnode.h>
 #include <phy.h>
 #include <net.h>
 
@@ -146,6 +147,17 @@
 struct udevice *dsa_get_master(struct udevice *dev);
 
 /**
+ * dsa_port_get_ofnode() - Return a reference to the given port's OF node
+ *
+ * Can be called at driver probe time or later.
+ *
+ * @dev:	DSA switch udevice pointer
+ * @port:	Port index
+ * @return OF node reference if OK, NULL on error
+ */
+ofnode dsa_port_get_ofnode(struct udevice *dev, int port);
+
+/**
  * dsa_port_get_pdata() - Helper that returns the platdata of an active
  *			(non-CPU) DSA port device.
  *
diff --git a/include/tsec.h b/include/tsec.h
index c301c28..72f3485 100644
--- a/include/tsec.h
+++ b/include/tsec.h
@@ -122,6 +122,8 @@
 #define ECNTRL_REDUCED_MII_MODE	0x00000004
 #define ECNTRL_SGMII_MODE	0x00000002
 
+#define RCTRL_PROM		0x00000008
+
 #ifndef CONFIG_SYS_TBIPA_VALUE
 # define CONFIG_SYS_TBIPA_VALUE	0x1f
 #endif
diff --git a/net/bootp.c b/net/bootp.c
index 655b9cc..58e30cd 100644
--- a/net/bootp.c
+++ b/net/bootp.c
@@ -647,7 +647,7 @@
 	*e++ = (576 - 312 + OPT_FIELD_SIZE) & 0xff;
 #endif
 
-	add_vci(e);
+	e = add_vci(e);
 
 #if defined(CONFIG_BOOTP_SUBNETMASK)
 	*e++ = 1;		/* Subnet mask request */
diff --git a/net/dsa-uclass.c b/net/dsa-uclass.c
index bf762cd..606b153 100644
--- a/net/dsa-uclass.c
+++ b/net/dsa-uclass.c
@@ -44,6 +44,26 @@
 	return 0;
 }
 
+ofnode dsa_port_get_ofnode(struct udevice *dev, int port)
+{
+	struct dsa_pdata *pdata = dev_get_uclass_plat(dev);
+	struct dsa_port_pdata *port_pdata;
+	struct udevice *pdev;
+
+	if (port == pdata->cpu_port)
+		return pdata->cpu_port_node;
+
+	for (device_find_first_child(dev, &pdev);
+	     pdev;
+	     device_find_next_child(&pdev)) {
+		port_pdata = dev_get_parent_plat(pdev);
+		if (port_pdata->index == port)
+			return dev_ofnode(pdev);
+	}
+
+	return ofnode_null();
+}
+
 /* returns the DSA master Ethernet device */
 struct udevice *dsa_get_master(struct udevice *dev)
 {
@@ -250,7 +270,7 @@
 		struct eth_ops *eth_ops = eth_get_ops(master);
 
 		if (eth_ops->set_promisc)
-			eth_ops->set_promisc(master, 1);
+			eth_ops->set_promisc(master, true);
 
 		return;
 	}