mediatek: add Airoha AN8855 gigabit switch driver
authorDim Fish <dimfish@gmail.com>
Fri, 11 Oct 2024 16:25:29 +0000 (19:25 +0300)
committerChristian Marangi <ansuelsmth@gmail.com>
Thu, 23 Jan 2025 14:27:25 +0000 (15:27 +0100)
New revisions of Xiaomi AX3000T with 1.0.84+ stock firmware contain new hardware.
This commit add support for Airoha AN8855 gigabit switch driver with 6.6 kernel patches

Based on https://patchwork.kernel.org/project/netdevbpf/cover/20241209134459.27110-1-ansuelsmth@gmail.com/

Signed-off-by: Dim Fish <dimfish@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/16709
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
15 files changed:
target/linux/mediatek/dts/mt7981b-xiaomi-mi-router-common.dtsi
target/linux/mediatek/files-6.6/drivers/mfd/airoha-an8855.c [new file with mode: 0644]
target/linux/mediatek/files-6.6/drivers/net/dsa/an8855.c [new file with mode: 0644]
target/linux/mediatek/files-6.6/drivers/net/dsa/an8855.h [new file with mode: 0644]
target/linux/mediatek/files-6.6/drivers/net/mdio/mdio-an8855.c [new file with mode: 0644]
target/linux/mediatek/files-6.6/drivers/net/phy/air_an8855.c [new file with mode: 0644]
target/linux/mediatek/files-6.6/drivers/nvmem/an8855-efuse.c [new file with mode: 0644]
target/linux/mediatek/files-6.6/include/linux/mfd/airoha-an8855-mfd.h [new file with mode: 0644]
target/linux/mediatek/filogic/config-6.6
target/linux/mediatek/mt7622/config-6.6
target/linux/mediatek/mt7623/config-6.6
target/linux/mediatek/mt7629/config-6.6
target/linux/mediatek/patches-6.6/737-net-dsa-add-Airoha-AN8855.patch [new file with mode: 0644]
target/linux/mediatek/patches-6.6/738-net-phylink-move-phylink_pcs_neg_mode.patch [new file with mode: 0644]
target/linux/mediatek/patches-6.6/739-net-add-negotiation-of-in-band-capabilities.patch [new file with mode: 0644]

index d6872395a9017a0794477f3e94e739d8693a89e9..c64b55cf6fa4ee1390c33c78a764b82314b19b83 100644 (file)
                interrupt-parent = <&pio>;
                interrupts = <38 IRQ_TYPE_LEVEL_HIGH>;
        };
+
+       mfd: mfd@1 {
+               compatible = "airoha,an8855-mfd";
+               reg = <1>;
+       };
 };
 
 &switch {
        };
 };
 
+&mfd {
+       efuse {
+               compatible = "airoha,an8855-efuse";
+               #nvmem-cell-cells = <0>;
+
+               nvmem-layout {
+                       compatible = "fixed-layout";
+                       #address-cells = <1>;
+                       #size-cells = <1>;
+
+                       shift_sel_port0_tx_a: shift-sel-port0-tx-a@c {
+                               reg = <0xc 0x4>;
+                       };
+
+                       shift_sel_port0_tx_b: shift-sel-port0-tx-b@10 {
+                               reg = <0x10 0x4>;
+                       };
+
+                       shift_sel_port0_tx_c: shift-sel-port0-tx-c@14 {
+                               reg = <0x14 0x4>;
+                       };
+
+                       shift_sel_port0_tx_d: shift-sel-port0-tx-d@18 {
+                               reg = <0x18 0x4>;
+                       };
+
+                       shift_sel_port1_tx_a: shift-sel-port1-tx-a@1c {
+                               reg = <0x1c 0x4>;
+                       };
+
+                       shift_sel_port1_tx_b: shift-sel-port1-tx-b@20 {
+                               reg = <0x20 0x4>;
+                       };
+
+                       shift_sel_port1_tx_c: shift-sel-port1-tx-c@24 {
+                               reg = <0x24 0x4>;
+                       };
+
+                       shift_sel_port1_tx_d: shift-sel-port1-tx-d@28 {
+                               reg = <0x28 0x4>;
+                       };
+
+                       shift_sel_port2_tx_a: shift-sel-port2-tx-a@2c {
+                               reg = <0x2c 0x4>;
+                       };
+
+                       shift_sel_port2_tx_b: shift-sel-port2-tx-b@30 {
+                               reg = <0x30 0x4>;
+                       };
+
+                       shift_sel_port2_tx_c: shift-sel-port2-tx-c@34 {
+                               reg = <0x34 0x4>;
+                       };
+
+                       shift_sel_port2_tx_d: shift-sel-port2-tx-d@38 {
+                               reg = <0x38 0x4>;
+                       };
+
+                       shift_sel_port3_tx_a: shift-sel-port3-tx-a@4c {
+                               reg = <0x4c 0x4>;
+                       };
+
+                       shift_sel_port3_tx_b: shift-sel-port3-tx-b@50 {
+                               reg = <0x50 0x4>;
+                       };
+
+                       shift_sel_port3_tx_c: shift-sel-port3-tx-c@54 {
+                               reg = <0x54 0x4>;
+                       };
+
+                       shift_sel_port3_tx_d: shift-sel-port3-tx-d@58 {
+                               reg = <0x58 0x4>;
+                       };
+               };
+       };
+
+       ethernet-switch {
+               compatible = "airoha,an8855-switch";
+               reset-gpios = <&pio 39 GPIO_ACTIVE_HIGH>;
+               airoha,ext-surge;
+
+               ports {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+
+                       port@0 {
+                               reg = <0>;
+                               label = "wan";
+                               phy-mode = "internal";
+                               phy-handle = <&internal_phy1>;
+                       };
+
+                       port@1 {
+                               reg = <1>;
+                               label = "lan2";
+                               phy-mode = "internal";
+                               phy-handle = <&internal_phy2>;
+                       };
+
+                       port@2 {
+                               reg = <2>;
+                               label = "lan3";
+                               phy-mode = "internal";
+                               phy-handle = <&internal_phy3>;
+                       };
+
+                       port@3 {
+                               reg = <3>;
+                               label = "lan4";
+                               phy-mode = "internal";
+                               phy-handle = <&internal_phy4>;
+                       };
+
+                       port@5 {
+                               reg = <5>;
+                               label = "cpu";
+                               ethernet = <&gmac0>;
+                               phy-mode = "2500base-x";
+
+                               fixed-link {
+                                       speed = <2500>;
+                                       full-duplex;
+                                       pause;
+                               };
+                       };
+               };
+       };
+
+       mdio {
+               compatible = "airoha,an8855-mdio";
+               #address-cells = <1>;
+               #size-cells = <0>;
+
+               internal_phy1: phy@1 {
+                       reg = <1>;
+
+                       nvmem-cells = <&shift_sel_port0_tx_a>,
+                                       <&shift_sel_port0_tx_b>,
+                                       <&shift_sel_port0_tx_c>,
+                                       <&shift_sel_port0_tx_d>;
+                       nvmem-cell-names = "tx_a", "tx_b", "tx_c", "tx_d";
+               };
+
+               internal_phy2: phy@2 {
+                       reg = <2>;
+
+                       nvmem-cells = <&shift_sel_port1_tx_a>,
+                                       <&shift_sel_port1_tx_b>,
+                                       <&shift_sel_port1_tx_c>,
+                                       <&shift_sel_port1_tx_d>;
+                       nvmem-cell-names = "tx_a", "tx_b", "tx_c", "tx_d";
+               };
+
+               internal_phy3: phy@3 {
+                       reg = <3>;
+
+                       nvmem-cells = <&shift_sel_port2_tx_a>,
+                                       <&shift_sel_port2_tx_b>,
+                                       <&shift_sel_port2_tx_c>,
+                                       <&shift_sel_port2_tx_d>;
+                       nvmem-cell-names = "tx_a", "tx_b", "tx_c", "tx_d";
+               };
+
+               internal_phy4: phy@4 {
+                       reg = <4>;
+
+                       nvmem-cells = <&shift_sel_port3_tx_a>,
+                                       <&shift_sel_port3_tx_b>,
+                                       <&shift_sel_port3_tx_c>,
+                                       <&shift_sel_port3_tx_d>;
+                       nvmem-cell-names = "tx_a", "tx_b", "tx_c", "tx_d";
+               };
+       };
+};
+
 &spi0 {
        pinctrl-names = "default";
        pinctrl-0 = <&spi0_flash_pins>;
diff --git a/target/linux/mediatek/files-6.6/drivers/mfd/airoha-an8855.c b/target/linux/mediatek/files-6.6/drivers/mfd/airoha-an8855.c
new file mode 100644 (file)
index 0000000..eeaea34
--- /dev/null
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * MFD driver for Airoha AN8855 Switch
+ */
+
+#include <linux/mfd/airoha-an8855-mfd.h>
+#include <linux/mfd/core.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/regmap.h>
+
+static const struct mfd_cell an8855_mfd_devs[] = {
+       {
+               .name = "an8855-efuse",
+               .of_compatible = "airoha,an8855-efuse",
+       }, {
+               .name = "an8855-switch",
+               .of_compatible = "airoha,an8855-switch",
+       }, {
+               .name = "an8855-mdio",
+               .of_compatible = "airoha,an8855-mdio",
+       }
+};
+
+int an8855_mii_set_page(struct an8855_mfd_priv *priv, u8 phy_id,
+                       u8 page) __must_hold(&priv->bus->mdio_lock)
+{
+       struct mii_bus *bus = priv->bus;
+       int ret;
+
+       ret = __mdiobus_write(bus, phy_id, AN8855_PHY_SELECT_PAGE, page);
+       if (ret < 0)
+               dev_err_ratelimited(&bus->dev,
+                                   "failed to set an8855 mii page\n");
+
+       /* Cache current page if next mii read/write is for switch */
+       priv->current_page = page;
+       return ret < 0 ? ret : 0;
+}
+EXPORT_SYMBOL_GPL(an8855_mii_set_page);
+
+static int an8855_mii_read32(struct mii_bus *bus, u8 phy_id, u32 reg,
+                            u32 *val) __must_hold(&bus->mdio_lock)
+{
+       int lo, hi, ret;
+
+       ret = __mdiobus_write(bus, phy_id, AN8855_PBUS_MODE,
+                             AN8855_PBUS_MODE_ADDR_FIXED);
+       if (ret < 0)
+               goto err;
+
+       ret = __mdiobus_write(bus, phy_id, AN8855_PBUS_RD_ADDR_HIGH,
+                             upper_16_bits(reg));
+       if (ret < 0)
+               goto err;
+       ret = __mdiobus_write(bus, phy_id, AN8855_PBUS_RD_ADDR_LOW,
+                             lower_16_bits(reg));
+       if (ret < 0)
+               goto err;
+
+       hi = __mdiobus_read(bus, phy_id, AN8855_PBUS_RD_DATA_HIGH);
+       if (hi < 0) {
+               ret = hi;
+               goto err;
+       }
+       lo = __mdiobus_read(bus, phy_id, AN8855_PBUS_RD_DATA_LOW);
+       if (lo < 0) {
+               ret = lo;
+               goto err;
+       }
+
+       *val = ((u16)hi << 16) | ((u16)lo & 0xffff);
+
+       return 0;
+err:
+       dev_err_ratelimited(&bus->dev,
+                           "failed to read an8855 register\n");
+       return ret;
+}
+
+static int an8855_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
+{
+       struct an8855_mfd_priv *priv = ctx;
+       struct mii_bus *bus = priv->bus;
+       u16 addr = priv->switch_addr;
+       int ret;
+
+       mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+       ret = an8855_mii_set_page(priv, addr, AN8855_PHY_PAGE_EXTENDED_4);
+       if (ret < 0)
+               goto exit;
+
+       ret = an8855_mii_read32(bus, addr, reg, val);
+
+exit:
+       mutex_unlock(&bus->mdio_lock);
+
+       return ret < 0 ? ret : 0;
+}
+
+static int an8855_mii_write32(struct mii_bus *bus, u8 phy_id, u32 reg,
+                             u32 val) __must_hold(&bus->mdio_lock)
+{
+       int ret;
+
+       ret = __mdiobus_write(bus, phy_id, AN8855_PBUS_MODE,
+                             AN8855_PBUS_MODE_ADDR_FIXED);
+       if (ret < 0)
+               goto err;
+
+       ret = __mdiobus_write(bus, phy_id, AN8855_PBUS_WR_ADDR_HIGH,
+                             upper_16_bits(reg));
+       if (ret < 0)
+               goto err;
+       ret = __mdiobus_write(bus, phy_id, AN8855_PBUS_WR_ADDR_LOW,
+                             lower_16_bits(reg));
+       if (ret < 0)
+               goto err;
+
+       ret = __mdiobus_write(bus, phy_id, AN8855_PBUS_WR_DATA_HIGH,
+                             upper_16_bits(val));
+       if (ret < 0)
+               goto err;
+       ret = __mdiobus_write(bus, phy_id, AN8855_PBUS_WR_DATA_LOW,
+                             lower_16_bits(val));
+       if (ret < 0)
+               goto err;
+
+       return 0;
+err:
+       dev_err_ratelimited(&bus->dev,
+                           "failed to write an8855 register\n");
+       return ret;
+}
+
+static int
+an8855_regmap_write(void *ctx, uint32_t reg, uint32_t val)
+{
+       struct an8855_mfd_priv *priv = ctx;
+       struct mii_bus *bus = priv->bus;
+       u16 addr = priv->switch_addr;
+       int ret;
+
+       mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+       ret = an8855_mii_set_page(priv, addr, AN8855_PHY_PAGE_EXTENDED_4);
+       if (ret < 0)
+               goto exit;
+
+       ret = an8855_mii_write32(bus, addr, reg, val);
+
+exit:
+       mutex_unlock(&bus->mdio_lock);
+
+       return ret < 0 ? ret : 0;
+}
+
+static int an8855_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask,
+                                    uint32_t write_val)
+{
+       struct an8855_mfd_priv *priv = ctx;
+       struct mii_bus *bus = priv->bus;
+       u16 addr = priv->switch_addr;
+       u32 val;
+       int ret;
+
+       mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+       ret = an8855_mii_set_page(priv, addr, AN8855_PHY_PAGE_EXTENDED_4);
+       if (ret < 0)
+               goto exit;
+
+       ret = an8855_mii_read32(bus, addr, reg, &val);
+       if (ret < 0)
+               goto exit;
+
+       val &= ~mask;
+       val |= write_val;
+       ret = an8855_mii_write32(bus, addr, reg, val);
+
+exit:
+       mutex_unlock(&bus->mdio_lock);
+
+       return ret < 0 ? ret : 0;
+}
+
+static const struct regmap_range an8855_readable_ranges[] = {
+       regmap_reg_range(0x10000000, 0x10000fff), /* SCU */
+       regmap_reg_range(0x10001000, 0x10001fff), /* RBUS */
+       regmap_reg_range(0x10002000, 0x10002fff), /* MCU */
+       regmap_reg_range(0x10005000, 0x10005fff), /* SYS SCU */
+       regmap_reg_range(0x10007000, 0x10007fff), /* I2C Slave */
+       regmap_reg_range(0x10008000, 0x10008fff), /* I2C Master */
+       regmap_reg_range(0x10009000, 0x10009fff), /* PDMA */
+       regmap_reg_range(0x1000a100, 0x1000a2ff), /* General Purpose Timer */
+       regmap_reg_range(0x1000a200, 0x1000a2ff), /* GPU timer */
+       regmap_reg_range(0x1000a300, 0x1000a3ff), /* GPIO */
+       regmap_reg_range(0x1000a400, 0x1000a5ff), /* EFUSE */
+       regmap_reg_range(0x1000c000, 0x1000cfff), /* GDMP CSR */
+       regmap_reg_range(0x10010000, 0x1001ffff), /* GDMP SRAM */
+       regmap_reg_range(0x10200000, 0x10203fff), /* Switch - ARL Global */
+       regmap_reg_range(0x10204000, 0x10207fff), /* Switch - BMU */
+       regmap_reg_range(0x10208000, 0x1020bfff), /* Switch - ARL Port */
+       regmap_reg_range(0x1020c000, 0x1020cfff), /* Switch - SCH */
+       regmap_reg_range(0x10210000, 0x10213fff), /* Switch - MAC */
+       regmap_reg_range(0x10214000, 0x10217fff), /* Switch - MIB */
+       regmap_reg_range(0x10218000, 0x1021bfff), /* Switch - Port Control */
+       regmap_reg_range(0x1021c000, 0x1021ffff), /* Switch - TOP */
+       regmap_reg_range(0x10220000, 0x1022ffff), /* SerDes */
+       regmap_reg_range(0x10286000, 0x10286fff), /* RG Batcher */
+       regmap_reg_range(0x1028c000, 0x1028ffff), /* ETHER_SYS */
+       regmap_reg_range(0x30000000, 0x37ffffff), /* I2C EEPROM */
+       regmap_reg_range(0x38000000, 0x3fffffff), /* BOOT_ROM */
+       regmap_reg_range(0xa0000000, 0xbfffffff), /* GPHY */
+};
+
+static const struct regmap_access_table an8855_readable_table = {
+       .yes_ranges = an8855_readable_ranges,
+       .n_yes_ranges = ARRAY_SIZE(an8855_readable_ranges),
+};
+
+static const struct regmap_config an8855_regmap_config = {
+       .reg_bits = 32,
+       .val_bits = 32,
+       .reg_stride = 4,
+       .max_register = 0xbfffffff,
+       .reg_read = an8855_regmap_read,
+       .reg_write = an8855_regmap_write,
+       .reg_update_bits = an8855_regmap_update_bits,
+       .disable_locking = true,
+       .rd_table = &an8855_readable_table,
+};
+
+static int an8855_mfd_probe(struct mdio_device *mdiodev)
+{
+       struct an8855_mfd_priv *priv;
+       struct regmap *regmap;
+
+       priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->bus = mdiodev->bus;
+       priv->dev = &mdiodev->dev;
+       priv->switch_addr = mdiodev->addr;
+       /* no DMA for mdiobus, mute warning for DMA mask not set */
+       priv->dev->dma_mask = &priv->dev->coherent_dma_mask;
+
+       regmap = devm_regmap_init(priv->dev, NULL, priv,
+                                 &an8855_regmap_config);
+       if (IS_ERR(regmap))
+               dev_err_probe(priv->dev, PTR_ERR(priv->dev),
+                             "regmap initialization failed\n");
+
+       dev_set_drvdata(&mdiodev->dev, priv);
+
+       return devm_mfd_add_devices(priv->dev, PLATFORM_DEVID_AUTO, an8855_mfd_devs,
+                                   ARRAY_SIZE(an8855_mfd_devs), NULL, 0,
+                                   NULL);
+}
+
+static const struct of_device_id an8855_mfd_of_match[] = {
+       { .compatible = "airoha,an8855-mfd" },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, an8855_mfd_of_match);
+
+static struct mdio_driver an8855_mfd_driver = {
+       .probe = an8855_mfd_probe,
+       .mdiodrv.driver = {
+               .name = "an8855",
+               .of_match_table = an8855_mfd_of_match,
+       },
+};
+mdio_module_driver(an8855_mfd_driver);
+
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_DESCRIPTION("Driver for Airoha AN8855 MFD");
+MODULE_LICENSE("GPL");
diff --git a/target/linux/mediatek/files-6.6/drivers/net/dsa/an8855.c b/target/linux/mediatek/files-6.6/drivers/net/dsa/an8855.c
new file mode 100644 (file)
index 0000000..7dd62e1
--- /dev/null
@@ -0,0 +1,2311 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Airoha AN8855 DSA Switch driver
+ * Copyright (C) 2023 Min Yao <min.yao@airoha.com>
+ * Copyright (C) 2024 Christian Marangi <ansuelsmth@gmail.com>
+ */
+#include <linux/bitfield.h>
+#include <linux/ethtool.h>
+#include <linux/etherdevice.h>
+#include <linux/gpio/consumer.h>
+#include <linux/if_bridge.h>
+#include <linux/iopoll.h>
+#include <linux/netdevice.h>
+#include <linux/of_net.h>
+#include <linux/of_platform.h>
+#include <linux/phylink.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <net/dsa.h>
+
+#include "an8855.h"
+
+static const struct an8855_mib_desc an8855_mib[] = {
+       MIB_DESC(1, AN8855_PORT_MIB_TX_DROP, "TxDrop"),
+       MIB_DESC(1, AN8855_PORT_MIB_TX_CRC_ERR, "TxCrcErr"),
+       MIB_DESC(1, AN8855_PORT_MIB_TX_COLLISION, "TxCollision"),
+       MIB_DESC(1, AN8855_PORT_MIB_TX_OVERSIZE_DROP, "TxOversizeDrop"),
+       MIB_DESC(2, AN8855_PORT_MIB_TX_BAD_PKT_BYTES, "TxBadPktBytes"),
+       MIB_DESC(1, AN8855_PORT_MIB_RX_DROP, "RxDrop"),
+       MIB_DESC(1, AN8855_PORT_MIB_RX_FILTERING, "RxFiltering"),
+       MIB_DESC(1, AN8855_PORT_MIB_RX_CRC_ERR, "RxCrcErr"),
+       MIB_DESC(1, AN8855_PORT_MIB_RX_CTRL_DROP, "RxCtrlDrop"),
+       MIB_DESC(1, AN8855_PORT_MIB_RX_INGRESS_DROP, "RxIngressDrop"),
+       MIB_DESC(1, AN8855_PORT_MIB_RX_ARL_DROP, "RxArlDrop"),
+       MIB_DESC(1, AN8855_PORT_MIB_FLOW_CONTROL_DROP, "FlowControlDrop"),
+       MIB_DESC(1, AN8855_PORT_MIB_WRED_DROP, "WredDrop"),
+       MIB_DESC(1, AN8855_PORT_MIB_MIRROR_DROP, "MirrorDrop"),
+       MIB_DESC(2, AN8855_PORT_MIB_RX_BAD_PKT_BYTES, "RxBadPktBytes"),
+       MIB_DESC(1, AN8855_PORT_MIB_RXS_FLOW_SAMPLING_PKT_DROP, "RxsFlowSamplingPktDrop"),
+       MIB_DESC(1, AN8855_PORT_MIB_RXS_FLOW_TOTAL_PKT_DROP, "RxsFlowTotalPktDrop"),
+       MIB_DESC(1, AN8855_PORT_MIB_PORT_CONTROL_DROP, "PortControlDrop"),
+};
+
+static int
+an8855_mib_init(struct an8855_priv *priv)
+{
+       int ret;
+
+       ret = regmap_write(priv->regmap, AN8855_MIB_CCR,
+                          AN8855_CCR_MIB_ENABLE);
+       if (ret)
+               return ret;
+
+       return regmap_write(priv->regmap, AN8855_MIB_CCR,
+                           AN8855_CCR_MIB_ACTIVATE);
+}
+
+static void an8855_fdb_write(struct an8855_priv *priv, u16 vid,
+                            u8 port_mask, const u8 *mac,
+                            bool add) __must_hold(&priv->reg_mutex)
+{
+       u32 mac_reg[2] = { };
+       u32 reg;
+
+       mac_reg[0] |= FIELD_PREP(AN8855_ATA1_MAC0, mac[0]);
+       mac_reg[0] |= FIELD_PREP(AN8855_ATA1_MAC1, mac[1]);
+       mac_reg[0] |= FIELD_PREP(AN8855_ATA1_MAC2, mac[2]);
+       mac_reg[0] |= FIELD_PREP(AN8855_ATA1_MAC3, mac[3]);
+       mac_reg[1] |= FIELD_PREP(AN8855_ATA2_MAC4, mac[4]);
+       mac_reg[1] |= FIELD_PREP(AN8855_ATA2_MAC5, mac[5]);
+
+       regmap_bulk_write(priv->regmap, AN8855_ATA1, mac_reg,
+                         ARRAY_SIZE(mac_reg));
+
+       reg = AN8855_ATWD_IVL;
+       if (add)
+               reg |= AN8855_ATWD_VLD;
+       reg |= FIELD_PREP(AN8855_ATWD_VID, vid);
+       reg |= FIELD_PREP(AN8855_ATWD_FID, AN8855_FID_BRIDGED);
+       regmap_write(priv->regmap, AN8855_ATWD, reg);
+       regmap_write(priv->regmap, AN8855_ATWD2,
+                    FIELD_PREP(AN8855_ATWD2_PORT, port_mask));
+}
+
+static void an8855_fdb_read(struct an8855_priv *priv, struct an8855_fdb *fdb)
+{
+       u32 reg[4];
+
+       regmap_bulk_read(priv->regmap, AN8855_ATRD0, reg,
+                        ARRAY_SIZE(reg));
+
+       fdb->live = FIELD_GET(AN8855_ATRD0_LIVE, reg[0]);
+       fdb->type = FIELD_GET(AN8855_ATRD0_TYPE, reg[0]);
+       fdb->ivl = FIELD_GET(AN8855_ATRD0_IVL, reg[0]);
+       fdb->vid = FIELD_GET(AN8855_ATRD0_VID, reg[0]);
+       fdb->fid = FIELD_GET(AN8855_ATRD0_FID, reg[0]);
+       fdb->aging = FIELD_GET(AN8855_ATRD1_AGING, reg[1]);
+       fdb->port_mask = FIELD_GET(AN8855_ATRD3_PORTMASK, reg[3]);
+       fdb->mac[0] = FIELD_GET(AN8855_ATRD2_MAC0, reg[2]);
+       fdb->mac[1] = FIELD_GET(AN8855_ATRD2_MAC1, reg[2]);
+       fdb->mac[2] = FIELD_GET(AN8855_ATRD2_MAC2, reg[2]);
+       fdb->mac[3] = FIELD_GET(AN8855_ATRD2_MAC3, reg[2]);
+       fdb->mac[4] = FIELD_GET(AN8855_ATRD1_MAC4, reg[1]);
+       fdb->mac[5] = FIELD_GET(AN8855_ATRD1_MAC5, reg[1]);
+       fdb->noarp = !!FIELD_GET(AN8855_ATRD0_ARP, reg[0]);
+}
+
+static int an8855_fdb_cmd(struct an8855_priv *priv, u32 cmd,
+                         u32 *rsp) __must_hold(&priv->reg_mutex)
+{
+       u32 val;
+       int ret;
+
+       /* Set the command operating upon the MAC address entries */
+       val = AN8855_ATC_BUSY | cmd;
+       ret = regmap_write(priv->regmap, AN8855_ATC, val);
+       if (ret)
+               return ret;
+
+       ret = regmap_read_poll_timeout(priv->regmap, AN8855_ATC, val,
+                                      !(val & AN8855_ATC_BUSY), 20, 200000);
+       if (ret)
+               return ret;
+
+       if (rsp)
+               *rsp = val;
+
+       return 0;
+}
+
+static void
+an8855_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
+{
+       struct dsa_port *dp = dsa_to_port(ds, port);
+       struct an8855_priv *priv = ds->priv;
+       bool learning = false;
+       u32 stp_state;
+
+       switch (state) {
+       case BR_STATE_DISABLED:
+               stp_state = AN8855_STP_DISABLED;
+               break;
+       case BR_STATE_BLOCKING:
+               stp_state = AN8855_STP_BLOCKING;
+               break;
+       case BR_STATE_LISTENING:
+               stp_state = AN8855_STP_LISTENING;
+               break;
+       case BR_STATE_LEARNING:
+               stp_state = AN8855_STP_LEARNING;
+               learning = dp->learning;
+               break;
+       case BR_STATE_FORWARDING:
+               learning = dp->learning;
+               fallthrough;
+       default:
+               stp_state = AN8855_STP_FORWARDING;
+               break;
+       }
+
+       regmap_update_bits(priv->regmap, AN8855_SSP_P(port),
+                          AN8855_FID_PST_MASK(AN8855_FID_BRIDGED),
+                          AN8855_FID_PST_VAL(AN8855_FID_BRIDGED, stp_state));
+
+       regmap_update_bits(priv->regmap, AN8855_PSC_P(port), AN8855_SA_DIS,
+                          learning ? 0 : AN8855_SA_DIS);
+}
+
+static void an8855_port_fast_age(struct dsa_switch *ds, int port)
+{
+       struct an8855_priv *priv = ds->priv;
+       int ret;
+
+       /* Set to clean Dynamic entry */
+       ret = regmap_write(priv->regmap, AN8855_ATA2, AN8855_ATA2_TYPE);
+       if (ret)
+               return;
+
+       /* Set Port */
+       ret = regmap_write(priv->regmap, AN8855_ATWD2,
+                          FIELD_PREP(AN8855_ATWD2_PORT, BIT(port)));
+       if (ret)
+               return;
+
+       /* Flush Dynamic entry at port */
+       an8855_fdb_cmd(priv, AN8855_ATC_MAT(AND8855_FDB_MAT_MAC_TYPE_PORT) |
+                      AN8855_FDB_FLUSH, NULL);
+}
+
+static int an8855_update_port_member(struct dsa_switch *ds, int port,
+                                    const struct net_device *bridge_dev,
+                                    bool join)
+{
+       struct an8855_priv *priv = ds->priv;
+       bool isolated, other_isolated;
+       struct dsa_port *dp;
+       u32 port_mask = 0;
+       int ret;
+
+       isolated = !!(priv->port_isolated_map & BIT(port));
+
+       dsa_switch_for_each_user_port(dp, ds) {
+               if (dp->index == port)
+                       continue;
+
+               if (!dsa_port_offloads_bridge_dev(dp, bridge_dev))
+                       continue;
+
+               other_isolated = !!(priv->port_isolated_map & BIT(dp->index));
+               port_mask |= BIT(dp->index);
+               /* Add/remove this port to the portvlan mask of the other
+                * ports in the bridge
+                */
+               if (join && !(isolated && other_isolated))
+                       ret = regmap_set_bits(priv->regmap,
+                                             AN8855_PORTMATRIX_P(dp->index),
+                                             FIELD_PREP(AN8855_USER_PORTMATRIX,
+                                                        BIT(port)));
+               else
+                       ret = regmap_clear_bits(priv->regmap,
+                                               AN8855_PORTMATRIX_P(dp->index),
+                                               FIELD_PREP(AN8855_USER_PORTMATRIX,
+                                                          BIT(port)));
+               if (ret)
+                       return ret;
+       }
+
+       /* Add/remove all other ports to this port's portvlan mask */
+       return regmap_update_bits(priv->regmap, AN8855_PORTMATRIX_P(port),
+                                 AN8855_USER_PORTMATRIX,
+                                 join ? port_mask : ~port_mask);
+}
+
+static int an8855_port_pre_bridge_flags(struct dsa_switch *ds, int port,
+                                       struct switchdev_brport_flags flags,
+                                       struct netlink_ext_ack *extack)
+{
+       if (flags.mask & ~(BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD |
+                          BR_BCAST_FLOOD | BR_ISOLATED))
+               return -EINVAL;
+
+       return 0;
+}
+
+static int an8855_port_bridge_flags(struct dsa_switch *ds, int port,
+                                   struct switchdev_brport_flags flags,
+                                   struct netlink_ext_ack *extack)
+{
+       struct an8855_priv *priv = ds->priv;
+       int ret;
+
+       if (flags.mask & BR_LEARNING) {
+               ret = regmap_update_bits(priv->regmap, AN8855_PSC_P(port), AN8855_SA_DIS,
+                                        flags.val & BR_LEARNING ? 0 : AN8855_SA_DIS);
+               if (ret)
+                       return ret;
+       }
+
+       if (flags.mask & BR_FLOOD) {
+               ret = regmap_update_bits(priv->regmap, AN8855_UNUF, BIT(port),
+                                        flags.val & BR_FLOOD ? BIT(port) : 0);
+               if (ret)
+                       return ret;
+       }
+
+       if (flags.mask & BR_MCAST_FLOOD) {
+               ret = regmap_update_bits(priv->regmap, AN8855_UNMF, BIT(port),
+                                        flags.val & BR_MCAST_FLOOD ? BIT(port) : 0);
+               if (ret)
+                       return ret;
+
+               ret = regmap_update_bits(priv->regmap, AN8855_UNIPMF, BIT(port),
+                                        flags.val & BR_MCAST_FLOOD ? BIT(port) : 0);
+               if (ret)
+                       return ret;
+       }
+
+       if (flags.mask & BR_BCAST_FLOOD) {
+               ret = regmap_update_bits(priv->regmap, AN8855_BCF, BIT(port),
+                                        flags.val & BR_BCAST_FLOOD ? BIT(port) : 0);
+               if (ret)
+                       return ret;
+       }
+
+       if (flags.mask & BR_ISOLATED) {
+               struct dsa_port *dp = dsa_to_port(ds, port);
+               struct net_device *bridge_dev = dsa_port_bridge_dev_get(dp);
+
+               if (flags.val & BR_ISOLATED)
+                       priv->port_isolated_map |= BIT(port);
+               else
+                       priv->port_isolated_map &= ~BIT(port);
+
+               ret = an8855_update_port_member(ds, port, bridge_dev, true);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int an8855_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+{
+       struct an8855_priv *priv = ds->priv;
+       u32 age_count, age_unit, val;
+
+       /* Convert msec in AN8855_L2_AGING_MS_CONSTANT counter */
+       val = msecs / AN8855_L2_AGING_MS_CONSTANT;
+       /* Derive the count unit */
+       age_unit = val / FIELD_MAX(AN8855_AGE_UNIT);
+       /* Get the count in unit, age_unit is always incremented by 1 internally */
+       age_count = val / (age_unit + 1);
+
+       return regmap_update_bits(priv->regmap, AN8855_AAC,
+                                 AN8855_AGE_CNT | AN8855_AGE_UNIT,
+                                 FIELD_PREP(AN8855_AGE_CNT, age_count) |
+                                 FIELD_PREP(AN8855_AGE_UNIT, age_unit));
+}
+
+static int an8855_port_bridge_join(struct dsa_switch *ds, int port,
+                                  struct dsa_bridge bridge,
+                                  bool *tx_fwd_offload,
+                                  struct netlink_ext_ack *extack)
+{
+       struct an8855_priv *priv = ds->priv;
+       int ret;
+
+       ret = an8855_update_port_member(ds, port, bridge.dev, true);
+       if (ret)
+               return ret;
+
+       /* Set to fallback mode for independent VLAN learning if in a bridge */
+       return regmap_update_bits(priv->regmap, AN8855_PCR_P(port),
+                                 AN8855_PORT_VLAN,
+                                 FIELD_PREP(AN8855_PORT_VLAN,
+                                            AN8855_PORT_FALLBACK_MODE));
+}
+
+static void an8855_port_bridge_leave(struct dsa_switch *ds, int port,
+                                    struct dsa_bridge bridge)
+{
+       struct an8855_priv *priv = ds->priv;
+
+       an8855_update_port_member(ds, port, bridge.dev, false);
+
+       /* When a port is removed from the bridge, the port would be set up
+        * back to the default as is at initial boot which is a VLAN-unaware
+        * port.
+        */
+       regmap_update_bits(priv->regmap, AN8855_PCR_P(port),
+                          AN8855_PORT_VLAN,
+                          FIELD_PREP(AN8855_PORT_VLAN,
+                                     AN8855_PORT_MATRIX_MODE));
+}
+
+static int an8855_port_fdb_add(struct dsa_switch *ds, int port,
+                              const unsigned char *addr, u16 vid,
+                              struct dsa_db db)
+{
+       struct an8855_priv *priv = ds->priv;
+       u8 port_mask = BIT(port);
+       int ret;
+
+       /* Set the vid to the port vlan id if no vid is set */
+       if (!vid)
+               vid = AN8855_PORT_VID_DEFAULT;
+
+       mutex_lock(&priv->reg_mutex);
+       an8855_fdb_write(priv, vid, port_mask, addr, true);
+       ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);
+       mutex_unlock(&priv->reg_mutex);
+
+       return ret;
+}
+
+static int an8855_port_fdb_del(struct dsa_switch *ds, int port,
+                              const unsigned char *addr, u16 vid,
+                              struct dsa_db db)
+{
+       struct an8855_priv *priv = ds->priv;
+       u8 port_mask = BIT(port);
+       int ret;
+
+       /* Set the vid to the port vlan id if no vid is set */
+       if (!vid)
+               vid = AN8855_PORT_VID_DEFAULT;
+
+       mutex_lock(&priv->reg_mutex);
+       an8855_fdb_write(priv, vid, port_mask, addr, false);
+       ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);
+       mutex_unlock(&priv->reg_mutex);
+
+       return ret;
+}
+
+static int an8855_port_fdb_dump(struct dsa_switch *ds, int port,
+                               dsa_fdb_dump_cb_t *cb, void *data)
+{
+       struct an8855_priv *priv = ds->priv;
+       int banks, count = 0;
+       u32 rsp;
+       int ret;
+       int i;
+
+       mutex_lock(&priv->reg_mutex);
+
+       /* Load search port */
+       ret = regmap_write(priv->regmap, AN8855_ATWD2,
+                          FIELD_PREP(AN8855_ATWD2_PORT, BIT(port)));
+       if (ret)
+               goto exit;
+       ret = an8855_fdb_cmd(priv, AN8855_ATC_MAT(AND8855_FDB_MAT_MAC_PORT) |
+                            AN8855_FDB_START, &rsp);
+       if (ret < 0)
+               goto exit;
+
+       do {
+               /* From response get the number of banks to read, exit if 0 */
+               banks = FIELD_GET(AN8855_ATC_HIT, rsp);
+               if (!banks)
+                       break;
+
+               /* Each banks have 4 entry */
+               for (i = 0; i < 4; i++) {
+                       struct an8855_fdb _fdb = {  };
+
+                       count++;
+
+                       /* Check if bank is present */
+                       if (!(banks & BIT(i)))
+                               continue;
+
+                       /* Select bank entry index */
+                       ret = regmap_write(priv->regmap, AN8855_ATRDS,
+                                          FIELD_PREP(AN8855_ATRD_SEL, i));
+                       if (ret)
+                               break;
+                       /* wait 1ms for the bank entry to be filled */
+                       usleep_range(1000, 1500);
+                       an8855_fdb_read(priv, &_fdb);
+
+                       if (!_fdb.live)
+                               continue;
+                       ret = cb(_fdb.mac, _fdb.vid, _fdb.noarp, data);
+                       if (ret < 0)
+                               break;
+               }
+
+               /* Stop if reached max FDB number */
+               if (count >= AN8855_NUM_FDB_RECORDS)
+                       break;
+
+               /* Read next bank */
+               ret = an8855_fdb_cmd(priv, AN8855_ATC_MAT(AND8855_FDB_MAT_MAC_PORT) |
+                                    AN8855_FDB_NEXT, &rsp);
+               if (ret < 0)
+                       break;
+       } while (true);
+
+exit:
+       mutex_unlock(&priv->reg_mutex);
+       return ret;
+}
+
+static int an8855_vlan_cmd(struct an8855_priv *priv, enum an8855_vlan_cmd cmd,
+                          u16 vid) __must_hold(&priv->reg_mutex)
+{
+       u32 val;
+       int ret;
+
+       val = AN8855_VTCR_BUSY | FIELD_PREP(AN8855_VTCR_FUNC, cmd) |
+             FIELD_PREP(AN8855_VTCR_VID, vid);
+       ret = regmap_write(priv->regmap, AN8855_VTCR, val);
+       if (ret)
+               return ret;
+
+       return regmap_read_poll_timeout(priv->regmap, AN8855_VTCR, val,
+                                       !(val & AN8855_VTCR_BUSY), 20, 200000);
+}
+
+static int an8855_vlan_add(struct an8855_priv *priv, u8 port, u16 vid,
+                          bool untagged) __must_hold(&priv->reg_mutex)
+{
+       u32 port_mask;
+       u32 val;
+       int ret;
+
+       /* Fetch entry */
+       ret = an8855_vlan_cmd(priv, AN8855_VTCR_RD_VID, vid);
+       if (ret)
+               return ret;
+
+       ret = regmap_read(priv->regmap, AN8855_VARD0, &val);
+       if (ret)
+               return ret;
+       port_mask = FIELD_GET(AN8855_VA0_PORT, val) | BIT(port);
+
+       /* Validate the entry with independent learning, create egress tag per
+        * VLAN and joining the port as one of the port members.
+        */
+       val = (val & AN8855_VA0_ETAG) | AN8855_VA0_IVL_MAC |
+             AN8855_VA0_VTAG_EN | AN8855_VA0_VLAN_VALID |
+             FIELD_PREP(AN8855_VA0_PORT, port_mask) |
+             FIELD_PREP(AN8855_VA0_FID, AN8855_FID_BRIDGED);
+       ret = regmap_write(priv->regmap, AN8855_VAWD0, val);
+       if (ret)
+               return ret;
+       ret = regmap_write(priv->regmap, AN8855_VAWD1, 0);
+       if (ret)
+               return ret;
+
+       /* CPU port is always taken as a tagged port for serving more than one
+        * VLANs across and also being applied with egress type stack mode for
+        * that VLAN tags would be appended after hardware special tag used as
+        * DSA tag.
+        */
+       if (port == AN8855_CPU_PORT)
+               val = AN8855_VLAN_EGRESS_STACK;
+       /* Decide whether adding tag or not for those outgoing packets from the
+        * port inside the VLAN.
+        */
+       else
+               val = untagged ? AN8855_VLAN_EGRESS_UNTAG : AN8855_VLAN_EGRESS_TAG;
+       ret = regmap_update_bits(priv->regmap, AN8855_VAWD0,
+                                AN8855_VA0_ETAG_PORT_MASK(port),
+                                AN8855_VA0_ETAG_PORT_VAL(port, val));
+       if (ret)
+               return ret;
+
+       /* Flush result to hardware */
+       return an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID, vid);
+}
+
+static int an8855_vlan_del(struct an8855_priv *priv, u8 port,
+                          u16 vid) __must_hold(&priv->reg_mutex)
+{
+       u32 port_mask;
+       u32 val;
+       int ret;
+
+       /* Fetch entry */
+       ret = an8855_vlan_cmd(priv, AN8855_VTCR_RD_VID, vid);
+       if (ret)
+               return ret;
+
+       ret = regmap_read(priv->regmap, AN8855_VARD0, &val);
+       if (ret)
+               return ret;
+       port_mask = FIELD_GET(AN8855_VA0_PORT, val) & ~BIT(port);
+
+       if (!(val & AN8855_VA0_VLAN_VALID)) {
+               dev_err(priv->dev, "Cannot be deleted due to invalid entry\n");
+               return -EINVAL;
+       }
+
+       if (port_mask) {
+               val = (val & AN8855_VA0_ETAG) | AN8855_VA0_IVL_MAC |
+                      AN8855_VA0_VTAG_EN | AN8855_VA0_VLAN_VALID |
+                      FIELD_PREP(AN8855_VA0_PORT, port_mask);
+               ret = regmap_write(priv->regmap, AN8855_VAWD0, val);
+               if (ret)
+                       return ret;
+       } else {
+               ret = regmap_write(priv->regmap, AN8855_VAWD0, 0);
+               if (ret)
+                       return ret;
+       }
+       ret = regmap_write(priv->regmap, AN8855_VAWD1, 0);
+       if (ret)
+               return ret;
+
+       /* Flush result to hardware */
+       return an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID, vid);
+}
+
+static int an8855_port_set_vlan_mode(struct an8855_priv *priv, int port,
+                                    enum an8855_port_mode port_mode,
+                                    enum an8855_vlan_port_eg_tag eg_tag,
+                                    enum an8855_vlan_port_attr vlan_attr,
+                                    enum an8855_vlan_port_acc_frm acc_frm)
+{
+       int ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_PCR_P(port),
+                                AN8855_PORT_VLAN,
+                                FIELD_PREP(AN8855_PORT_VLAN, port_mode));
+       if (ret)
+               return ret;
+
+       return regmap_update_bits(priv->regmap, AN8855_PVC_P(port),
+                                 AN8855_PVC_EG_TAG | AN8855_VLAN_ATTR | AN8855_ACC_FRM,
+                                 FIELD_PREP(AN8855_PVC_EG_TAG, eg_tag) |
+                                 FIELD_PREP(AN8855_VLAN_ATTR, vlan_attr) |
+                                 FIELD_PREP(AN8855_ACC_FRM, acc_frm));
+}
+
+static int an8855_port_set_pid(struct an8855_priv *priv, int port,
+                              u16 pid)
+{
+       int ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_PPBV1_P(port),
+                                AN8855_PPBV_G0_PORT_VID,
+                                FIELD_PREP(AN8855_PPBV_G0_PORT_VID, pid));
+       if (ret)
+               return ret;
+
+       return regmap_update_bits(priv->regmap, AN8855_PVID_P(port),
+                                 AN8855_G0_PORT_VID,
+                                 FIELD_PREP(AN8855_G0_PORT_VID, pid));
+}
+
+static int an8855_port_vlan_filtering(struct dsa_switch *ds, int port,
+                                     bool vlan_filtering,
+                                     struct netlink_ext_ack *extack)
+{
+       struct an8855_priv *priv = ds->priv;
+       u32 val;
+       int ret;
+
+       /* The port is being kept as VLAN-unaware port when bridge is
+        * set up with vlan_filtering not being set, Otherwise, the
+        * port and the corresponding CPU port is required the setup
+        * for becoming a VLAN-aware port.
+        */
+       if (vlan_filtering) {
+               u32 acc_frm;
+               /* CPU port is set to fallback mode to let untagged
+                * frames pass through.
+                */
+               ret = an8855_port_set_vlan_mode(priv, AN8855_CPU_PORT,
+                                               AN8855_PORT_FALLBACK_MODE,
+                                               AN8855_VLAN_EG_CONSISTENT,
+                                               AN8855_VLAN_USER,
+                                               AN8855_VLAN_ACC_ALL);
+               if (ret)
+                       return ret;
+
+               ret = regmap_read(priv->regmap, AN8855_PVID_P(port), &val);
+               if (ret)
+                       return ret;
+
+               /* Only accept tagged frames if PVID is not set */
+               if (FIELD_GET(AN8855_G0_PORT_VID, val) != AN8855_PORT_VID_DEFAULT)
+                       acc_frm = AN8855_VLAN_ACC_TAGGED;
+               else
+                       acc_frm = AN8855_VLAN_ACC_ALL;
+
+               /* Trapped into security mode allows packet forwarding through VLAN
+                * table lookup.
+                * Set the port as a user port which is to be able to recognize VID
+                * from incoming packets before fetching entry within the VLAN table.
+                */
+               ret = an8855_port_set_vlan_mode(priv, port,
+                                               AN8855_PORT_SECURITY_MODE,
+                                               AN8855_VLAN_EG_DISABLED,
+                                               AN8855_VLAN_USER,
+                                               acc_frm);
+               if (ret)
+                       return ret;
+       } else {
+               bool disable_cpu_vlan = true;
+               struct dsa_port *dp;
+               u32 port_mode;
+
+               /* This is called after .port_bridge_leave when leaving a VLAN-aware
+                * bridge. Don't set standalone ports to fallback mode.
+                */
+               if (dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
+                       port_mode = AN8855_PORT_FALLBACK_MODE;
+               else
+                       port_mode = AN8855_PORT_MATRIX_MODE;
+
+               /* When a port is removed from the bridge, the port would be set up
+                * back to the default as is at initial boot which is a VLAN-unaware
+                * port.
+                */
+               ret = an8855_port_set_vlan_mode(priv, port, port_mode,
+                                               AN8855_VLAN_EG_CONSISTENT,
+                                               AN8855_VLAN_TRANSPARENT,
+                                               AN8855_VLAN_ACC_ALL);
+               if (ret)
+                       return ret;
+
+               /* Restore default PVID */
+               ret = an8855_port_set_pid(priv, port, AN8855_PORT_VID_DEFAULT);
+               if (ret)
+                       return ret;
+
+               dsa_switch_for_each_user_port(dp, ds) {
+                       if (dsa_port_is_vlan_filtering(dp)) {
+                               disable_cpu_vlan = false;
+                               break;
+                       }
+               }
+
+               if (disable_cpu_vlan) {
+                       ret = an8855_port_set_vlan_mode(priv, AN8855_CPU_PORT,
+                                                       AN8855_PORT_MATRIX_MODE,
+                                                       AN8855_VLAN_EG_CONSISTENT,
+                                                       AN8855_VLAN_USER,
+                                                       AN8855_VLAN_ACC_ALL);
+                       if (ret)
+                               return ret;
+               }
+       }
+
+       return 0;
+}
+
+static int an8855_port_vlan_add(struct dsa_switch *ds, int port,
+                               const struct switchdev_obj_port_vlan *vlan,
+                               struct netlink_ext_ack *extack)
+{
+       bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
+       bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
+       struct an8855_priv *priv = ds->priv;
+       u32 val;
+       int ret;
+
+       mutex_lock(&priv->reg_mutex);
+       ret = an8855_vlan_add(priv, port, vlan->vid, untagged);
+       mutex_unlock(&priv->reg_mutex);
+       if (ret)
+               return ret;
+
+       if (pvid) {
+               /* Accept all frames if PVID is set */
+               regmap_update_bits(priv->regmap, AN8855_PVC_P(port), AN8855_ACC_FRM,
+                                  FIELD_PREP(AN8855_ACC_FRM, AN8855_VLAN_ACC_ALL));
+
+               /* Only configure PVID if VLAN filtering is enabled */
+               if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) {
+                       ret = an8855_port_set_pid(priv, port, vlan->vid);
+                       if (ret)
+                               return ret;
+               }
+       } else if (vlan->vid) {
+               ret = regmap_read(priv->regmap, AN8855_PVID_P(port), &val);
+               if (ret)
+                       return ret;
+
+               if (FIELD_GET(AN8855_G0_PORT_VID, val) != vlan->vid)
+                       return 0;
+
+               /* This VLAN is overwritten without PVID, so unset it */
+               if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) {
+                       ret = regmap_update_bits(priv->regmap, AN8855_PVC_P(port),
+                                                AN8855_ACC_FRM,
+                                                FIELD_PREP(AN8855_ACC_FRM,
+                                                           AN8855_VLAN_ACC_TAGGED));
+                       if (ret)
+                               return ret;
+               }
+
+               ret = an8855_port_set_pid(priv, port, AN8855_PORT_VID_DEFAULT);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int an8855_port_vlan_del(struct dsa_switch *ds, int port,
+                               const struct switchdev_obj_port_vlan *vlan)
+{
+       struct an8855_priv *priv = ds->priv;
+       u32 val;
+       int ret;
+
+       mutex_lock(&priv->reg_mutex);
+       ret = an8855_vlan_del(priv, port, vlan->vid);
+       mutex_unlock(&priv->reg_mutex);
+       if (ret)
+               return ret;
+
+       ret = regmap_read(priv->regmap, AN8855_PVID_P(port), &val);
+       if (ret)
+               return ret;
+
+       /* PVID is being restored to the default whenever the PVID port
+        * is being removed from the VLAN.
+        */
+       if (FIELD_GET(AN8855_G0_PORT_VID, val) == vlan->vid) {
+               /* Only accept tagged frames if the port is VLAN-aware */
+               if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) {
+                       ret = regmap_update_bits(priv->regmap, AN8855_PVC_P(port),
+                                                AN8855_ACC_FRM,
+                                                FIELD_PREP(AN8855_ACC_FRM,
+                                                           AN8855_VLAN_ACC_TAGGED));
+                       if (ret)
+                               return ret;
+               }
+
+               ret = an8855_port_set_pid(priv, port, AN8855_PORT_VID_DEFAULT);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static int
+an8855_port_mdb_add(struct dsa_switch *ds, int port,
+                   const struct switchdev_obj_port_mdb *mdb,
+                   struct dsa_db db)
+{
+       struct an8855_priv *priv = ds->priv;
+       const u8 *addr = mdb->addr;
+       u16 vid = mdb->vid;
+       u8 port_mask = 0;
+       u32 val;
+       int ret;
+
+       /* Set the vid to the port vlan id if no vid is set */
+       if (!vid)
+               vid = AN8855_PORT_VID_DEFAULT;
+
+       mutex_lock(&priv->reg_mutex);
+
+       an8855_fdb_write(priv, vid, 0, addr, false);
+       if (!an8855_fdb_cmd(priv, AN8855_FDB_READ, NULL)) {
+               ret = regmap_read(priv->regmap, AN8855_ATRD3, &val);
+               if (ret)
+                       goto exit;
+
+               port_mask = FIELD_GET(AN8855_ATRD3_PORTMASK, val);
+       }
+
+       port_mask |= BIT(port);
+       an8855_fdb_write(priv, vid, port_mask, addr, true);
+       ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);
+
+exit:
+       mutex_unlock(&priv->reg_mutex);
+
+       return ret;
+}
+
+static int
+an8855_port_mdb_del(struct dsa_switch *ds, int port,
+                   const struct switchdev_obj_port_mdb *mdb,
+                   struct dsa_db db)
+{
+       struct an8855_priv *priv = ds->priv;
+       const u8 *addr = mdb->addr;
+       u16 vid = mdb->vid;
+       u8 port_mask = 0;
+       u32 val;
+       int ret;
+
+       /* Set the vid to the port vlan id if no vid is set */
+       if (!vid)
+               vid = AN8855_PORT_VID_DEFAULT;
+
+       mutex_lock(&priv->reg_mutex);
+
+       an8855_fdb_write(priv, vid, 0, addr, 0);
+       if (!an8855_fdb_cmd(priv, AN8855_FDB_READ, NULL)) {
+               ret = regmap_read(priv->regmap, AN8855_ATRD3, &val);
+               if (ret)
+                       goto exit;
+
+               port_mask = FIELD_GET(AN8855_ATRD3_PORTMASK, val);
+       }
+
+       port_mask &= ~BIT(port);
+       an8855_fdb_write(priv, vid, port_mask, addr, port_mask ? true : false);
+       ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);
+
+exit:
+       mutex_unlock(&priv->reg_mutex);
+
+       return ret;
+}
+
+static int
+an8855_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
+{
+       struct an8855_priv *priv = ds->priv;
+       int length;
+       u32 val;
+
+       /* When a new MTU is set, DSA always set the CPU port's MTU to the
+        * largest MTU of the slave ports. Because the switch only has a global
+        * RX length register, only allowing CPU port here is enough.
+        */
+       if (!dsa_is_cpu_port(ds, port))
+               return 0;
+
+       /* RX length also includes Ethernet header, MTK tag, and FCS length */
+       length = new_mtu + ETH_HLEN + MTK_TAG_LEN + ETH_FCS_LEN;
+       if (length <= 1522)
+               val = AN8855_MAX_RX_PKT_1518_1522;
+       else if (length <= 1536)
+               val = AN8855_MAX_RX_PKT_1536;
+       else if (length <= 1552)
+               val = AN8855_MAX_RX_PKT_1552;
+       else if (length <= 3072)
+               val = AN8855_MAX_RX_JUMBO_3K;
+       else if (length <= 4096)
+               val = AN8855_MAX_RX_JUMBO_4K;
+       else if (length <= 5120)
+               val = AN8855_MAX_RX_JUMBO_5K;
+       else if (length <= 6144)
+               val = AN8855_MAX_RX_JUMBO_6K;
+       else if (length <= 7168)
+               val = AN8855_MAX_RX_JUMBO_7K;
+       else if (length <= 8192)
+               val = AN8855_MAX_RX_JUMBO_8K;
+       else if (length <= 9216)
+               val = AN8855_MAX_RX_JUMBO_9K;
+       else if (length <= 12288)
+               val = AN8855_MAX_RX_JUMBO_12K;
+       else if (length <= 15360)
+               val = AN8855_MAX_RX_JUMBO_15K;
+       else
+               val = AN8855_MAX_RX_JUMBO_16K;
+
+       /* Enable JUMBO packet */
+       if (length > 1552)
+               val |= AN8855_MAX_RX_PKT_JUMBO;
+
+       return regmap_update_bits(priv->regmap, AN8855_GMACCR,
+                                 AN8855_MAX_RX_JUMBO | AN8855_MAX_RX_PKT_LEN,
+                                 val);
+}
+
+static int
+an8855_port_max_mtu(struct dsa_switch *ds, int port)
+{
+       return AN8855_MAX_MTU;
+}
+
+static void
+an8855_get_strings(struct dsa_switch *ds, int port, u32 stringset,
+                  uint8_t *data)
+{
+       int i;
+
+       if (stringset != ETH_SS_STATS)
+               return;
+
+       for (i = 0; i < ARRAY_SIZE(an8855_mib); i++)
+               ethtool_puts(&data, an8855_mib[i].name);
+}
+
+static void
+an8855_read_port_stats(struct an8855_priv *priv, int port, u32 offset, u8 size,
+                      uint64_t *data)
+{
+       u32 val, reg = AN8855_PORT_MIB_COUNTER(port) + offset;
+
+       regmap_read(priv->regmap, reg, &val);
+       *data = val;
+
+       if (size == 2) {
+               regmap_read(priv->regmap, reg + 4, &val);
+               *data |= (u64)val << 32;
+       }
+}
+
+static void
+an8855_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
+{
+       struct an8855_priv *priv = ds->priv;
+       const struct an8855_mib_desc *mib;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(an8855_mib); i++) {
+               mib = &an8855_mib[i];
+
+               an8855_read_port_stats(priv, port, mib->offset, mib->size,
+                                      data + i);
+       }
+}
+
+static int
+an8855_get_sset_count(struct dsa_switch *ds, int port, int sset)
+{
+       if (sset != ETH_SS_STATS)
+               return 0;
+
+       return ARRAY_SIZE(an8855_mib);
+}
+
+static void
+an8855_get_eth_mac_stats(struct dsa_switch *ds, int port,
+                        struct ethtool_eth_mac_stats *mac_stats)
+{
+       struct an8855_priv *priv = ds->priv;
+
+       /* MIB counter doesn't provide a FramesTransmittedOK but instead
+        * provide stats for Unicast, Broadcast and Multicast frames separately.
+        * To simulate a global frame counter, read Unicast and addition Multicast
+        * and Broadcast later
+        */
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_UNICAST, 1,
+                              &mac_stats->FramesTransmittedOK);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_SINGLE_COLLISION, 1,
+                              &mac_stats->SingleCollisionFrames);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_MULTIPLE_COLLISION, 1,
+                              &mac_stats->MultipleCollisionFrames);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_UNICAST, 1,
+                              &mac_stats->FramesReceivedOK);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_BYTES, 2,
+                              &mac_stats->OctetsTransmittedOK);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_ALIGN_ERR, 1,
+                              &mac_stats->AlignmentErrors);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_DEFERRED, 1,
+                              &mac_stats->FramesWithDeferredXmissions);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_LATE_COLLISION, 1,
+                              &mac_stats->LateCollisions);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_EXCESSIVE_COLLISION, 1,
+                              &mac_stats->FramesAbortedDueToXSColls);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_BYTES, 2,
+                              &mac_stats->OctetsReceivedOK);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_MULTICAST, 1,
+                              &mac_stats->MulticastFramesXmittedOK);
+       mac_stats->FramesTransmittedOK += mac_stats->MulticastFramesXmittedOK;
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_BROADCAST, 1,
+                              &mac_stats->BroadcastFramesXmittedOK);
+       mac_stats->FramesTransmittedOK += mac_stats->BroadcastFramesXmittedOK;
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_MULTICAST, 1,
+                              &mac_stats->MulticastFramesReceivedOK);
+       mac_stats->FramesReceivedOK += mac_stats->MulticastFramesReceivedOK;
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_BROADCAST, 1,
+                              &mac_stats->BroadcastFramesReceivedOK);
+       mac_stats->FramesReceivedOK += mac_stats->BroadcastFramesReceivedOK;
+}
+
+static const struct ethtool_rmon_hist_range an8855_rmon_ranges[] = {
+       { 0, 64 },
+       { 65, 127 },
+       { 128, 255 },
+       { 256, 511 },
+       { 512, 1023 },
+       { 1024, 1518 },
+       { 1519, AN8855_MAX_MTU },
+       {}
+};
+
+static void an8855_get_rmon_stats(struct dsa_switch *ds, int port,
+                                 struct ethtool_rmon_stats *rmon_stats,
+                                 const struct ethtool_rmon_hist_range **ranges)
+{
+       struct an8855_priv *priv = ds->priv;
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_UNDER_SIZE_ERR, 1,
+                              &rmon_stats->undersize_pkts);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_OVER_SZ_ERR, 1,
+                              &rmon_stats->oversize_pkts);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_FRAG_ERR, 1,
+                              &rmon_stats->fragments);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_JABBER_ERR, 1,
+                              &rmon_stats->jabbers);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_64, 1,
+                              &rmon_stats->hist[0]);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_65_TO_127, 1,
+                              &rmon_stats->hist[1]);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_128_TO_255, 1,
+                              &rmon_stats->hist[2]);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_256_TO_511, 1,
+                              &rmon_stats->hist[3]);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_512_TO_1023, 1,
+                              &rmon_stats->hist[4]);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_1024_TO_1518, 1,
+                              &rmon_stats->hist[5]);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_1519_TO_MAX, 1,
+                              &rmon_stats->hist[6]);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_64, 1,
+                              &rmon_stats->hist_tx[0]);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_65_TO_127, 1,
+                              &rmon_stats->hist_tx[1]);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_128_TO_255, 1,
+                              &rmon_stats->hist_tx[2]);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_256_TO_511, 1,
+                              &rmon_stats->hist_tx[3]);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_512_TO_1023, 1,
+                              &rmon_stats->hist_tx[4]);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_1024_TO_1518, 1,
+                              &rmon_stats->hist_tx[5]);
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_1519_TO_MAX, 1,
+                              &rmon_stats->hist_tx[6]);
+
+       *ranges = an8855_rmon_ranges;
+}
+
+static void an8855_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
+                                     struct ethtool_eth_ctrl_stats *ctrl_stats)
+{
+       struct an8855_priv *priv = ds->priv;
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PAUSE, 1,
+                              &ctrl_stats->MACControlFramesTransmitted);
+
+       an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PAUSE, 1,
+                              &ctrl_stats->MACControlFramesReceived);
+}
+
+static int an8855_port_mirror_add(struct dsa_switch *ds, int port,
+                                 struct dsa_mall_mirror_tc_entry *mirror,
+                                 bool ingress,
+                                 struct netlink_ext_ack *extack)
+{
+       struct an8855_priv *priv = ds->priv;
+       int monitor_port;
+       u32 val;
+       int ret;
+
+       /* Check for existent entry */
+       if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
+               return -EEXIST;
+
+       ret = regmap_read(priv->regmap, AN8855_MIR, &val);
+       if (ret)
+               return ret;
+
+       /* AN8855 supports 4 monitor port, but only use first group */
+       monitor_port = FIELD_GET(AN8855_MIRROR_PORT, val);
+       if (val & AN8855_MIRROR_EN && monitor_port != mirror->to_local_port)
+               return -EEXIST;
+
+       val = AN8855_MIRROR_EN;
+       val |= FIELD_PREP(AN8855_MIRROR_PORT, mirror->to_local_port);
+       ret = regmap_update_bits(priv->regmap, AN8855_MIR,
+                                AN8855_MIRROR_EN | AN8855_MIRROR_PORT,
+                                val);
+       if (ret)
+               return ret;
+
+       ret = regmap_set_bits(priv->regmap, AN8855_PCR_P(port),
+                             ingress ? AN8855_PORT_RX_MIR : AN8855_PORT_TX_MIR);
+       if (ret)
+               return ret;
+
+       if (ingress)
+               priv->mirror_rx |= BIT(port);
+       else
+               priv->mirror_tx |= BIT(port);
+
+       return 0;
+}
+
+static void an8855_port_mirror_del(struct dsa_switch *ds, int port,
+                                  struct dsa_mall_mirror_tc_entry *mirror)
+{
+       struct an8855_priv *priv = ds->priv;
+
+       if (mirror->ingress)
+               priv->mirror_rx &= ~BIT(port);
+       else
+               priv->mirror_tx &= ~BIT(port);
+
+       regmap_clear_bits(priv->regmap, AN8855_PCR_P(port),
+                         mirror->ingress ? AN8855_PORT_RX_MIR :
+                                           AN8855_PORT_TX_MIR);
+
+       if (!priv->mirror_rx && !priv->mirror_tx)
+               regmap_clear_bits(priv->regmap, AN8855_MIR, AN8855_MIRROR_EN);
+}
+
+static int an8855_port_set_status(struct an8855_priv *priv, int port,
+                                 bool enable)
+{
+       if (enable)
+               return regmap_set_bits(priv->regmap, AN8855_PMCR_P(port),
+                                      AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN);
+       else
+               return regmap_clear_bits(priv->regmap, AN8855_PMCR_P(port),
+                                        AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN);
+}
+
+static int an8855_port_enable(struct dsa_switch *ds, int port,
+                             struct phy_device *phy)
+{
+       return an8855_port_set_status(ds->priv, port, true);
+}
+
+static void an8855_port_disable(struct dsa_switch *ds, int port)
+{
+       an8855_port_set_status(ds->priv, port, false);
+}
+
+static u32 en8855_get_phy_flags(struct dsa_switch *ds, int port)
+{
+       struct an8855_priv *priv = ds->priv;
+
+       /* PHY doesn't need calibration */
+       if (!priv->phy_require_calib)
+               return 0;
+
+       /* Use AN8855_PHY_FLAGS_EN_CALIBRATION to signal
+        * calibration needed.
+        */
+       return AN8855_PHY_FLAGS_EN_CALIBRATION;
+}
+
+static enum dsa_tag_protocol
+an8855_get_tag_protocol(struct dsa_switch *ds, int port,
+                       enum dsa_tag_protocol mp)
+{
+       return DSA_TAG_PROTO_MTK;
+}
+
+/* Similar to MT7530 also trap link local frame and special frame to CPU */
+static int an8855_trap_special_frames(struct an8855_priv *priv)
+{
+       int ret;
+
+       /* Trap BPDUs to the CPU port(s) and egress them
+        * VLAN-untagged.
+        */
+       ret = regmap_update_bits(priv->regmap, AN8855_BPC,
+                                AN8855_BPDU_BPDU_FR | AN8855_BPDU_EG_TAG |
+                                AN8855_BPDU_PORT_FW,
+                                AN8855_BPDU_BPDU_FR |
+                                FIELD_PREP(AN8855_BPDU_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
+                                FIELD_PREP(AN8855_BPDU_PORT_FW, AN8855_BPDU_CPU_ONLY));
+       if (ret)
+               return ret;
+
+       /* Trap 802.1X PAE frames to the CPU port(s) and egress them
+        * VLAN-untagged.
+        */
+       ret = regmap_update_bits(priv->regmap, AN8855_PAC,
+                                AN8855_PAE_BPDU_FR | AN8855_PAE_EG_TAG |
+                                AN8855_PAE_PORT_FW,
+                                AN8855_PAE_BPDU_FR |
+                                FIELD_PREP(AN8855_PAE_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
+                                FIELD_PREP(AN8855_PAE_PORT_FW, AN8855_BPDU_CPU_ONLY));
+       if (ret)
+               return ret;
+
+       /* Trap frames with :01 MAC DAs to the CPU port(s) and egress
+        * them VLAN-untagged.
+        */
+       ret = regmap_update_bits(priv->regmap, AN8855_RGAC1,
+                                AN8855_R01_BPDU_FR | AN8855_R01_EG_TAG |
+                                AN8855_R01_PORT_FW,
+                                AN8855_R01_BPDU_FR |
+                                FIELD_PREP(AN8855_R01_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
+                                FIELD_PREP(AN8855_R01_PORT_FW, AN8855_BPDU_CPU_ONLY));
+       if (ret)
+               return ret;
+
+       /* Trap frames with :02 MAC DAs to the CPU port(s) and egress
+        * them VLAN-untagged.
+        */
+       ret = regmap_update_bits(priv->regmap, AN8855_RGAC1,
+                                AN8855_R02_BPDU_FR | AN8855_R02_EG_TAG |
+                                AN8855_R02_PORT_FW,
+                                AN8855_R02_BPDU_FR |
+                                FIELD_PREP(AN8855_R02_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
+                                FIELD_PREP(AN8855_R02_PORT_FW, AN8855_BPDU_CPU_ONLY));
+       if (ret)
+               return ret;
+
+       /* Trap frames with :03 MAC DAs to the CPU port(s) and egress
+        * them VLAN-untagged.
+        */
+       ret = regmap_update_bits(priv->regmap, AN8855_RGAC1,
+                                AN8855_R03_BPDU_FR | AN8855_R03_EG_TAG |
+                                AN8855_R03_PORT_FW,
+                                AN8855_R03_BPDU_FR |
+                                FIELD_PREP(AN8855_R03_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
+                                FIELD_PREP(AN8855_R03_PORT_FW, AN8855_BPDU_CPU_ONLY));
+       if (ret)
+               return ret;
+
+       /* Trap frames with :0E MAC DAs to the CPU port(s) and egress
+        * them VLAN-untagged.
+        */
+       return regmap_update_bits(priv->regmap, AN8855_RGAC1,
+                                 AN8855_R0E_BPDU_FR | AN8855_R0E_EG_TAG |
+                                 AN8855_R0E_PORT_FW,
+                                 AN8855_R0E_BPDU_FR |
+                                 FIELD_PREP(AN8855_R0E_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
+                                 FIELD_PREP(AN8855_R0E_PORT_FW, AN8855_BPDU_CPU_ONLY));
+}
+
+static int
+an8855_setup_pvid_vlan(struct an8855_priv *priv)
+{
+       u32 val;
+       int ret;
+
+       /* Validate the entry with independent learning, keep the original
+        * ingress tag attribute.
+        */
+       val = AN8855_VA0_IVL_MAC | AN8855_VA0_EG_CON |
+             FIELD_PREP(AN8855_VA0_FID, AN8855_FID_BRIDGED) |
+             AN8855_VA0_PORT | AN8855_VA0_VLAN_VALID;
+       ret = regmap_write(priv->regmap, AN8855_VAWD0, val);
+       if (ret)
+               return ret;
+
+       return an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID,
+                              AN8855_PORT_VID_DEFAULT);
+}
+
+static int an8855_setup(struct dsa_switch *ds)
+{
+       struct an8855_priv *priv = ds->priv;
+       struct dsa_port *dp;
+       int ret;
+
+       /* Enable and reset MIB counters */
+       ret = an8855_mib_init(priv);
+       if (ret)
+               return ret;
+
+       dsa_switch_for_each_user_port(dp, ds) {
+               /* Disable MAC by default on all user ports */
+               ret = an8855_port_set_status(priv, dp->index, false);
+               if (ret)
+                       return ret;
+
+               /* Individual user ports get connected to CPU port only */
+               ret = regmap_write(priv->regmap, AN8855_PORTMATRIX_P(dp->index),
+                                  FIELD_PREP(AN8855_PORTMATRIX, BIT(AN8855_CPU_PORT)));
+               if (ret)
+                       return ret;
+
+               /* Disable Broadcast Forward on user ports */
+               ret = regmap_clear_bits(priv->regmap, AN8855_BCF, BIT(dp->index));
+               if (ret)
+                       return ret;
+
+               /* Disable Unknown Unicast Forward on user ports */
+               ret = regmap_clear_bits(priv->regmap, AN8855_UNUF, BIT(dp->index));
+               if (ret)
+                       return ret;
+
+               /* Disable Unknown Multicast Forward on user ports */
+               ret = regmap_clear_bits(priv->regmap, AN8855_UNMF, BIT(dp->index));
+               if (ret)
+                       return ret;
+
+               ret = regmap_clear_bits(priv->regmap, AN8855_UNIPMF, BIT(dp->index));
+               if (ret)
+                       return ret;
+
+               /* Set default PVID to on all user ports */
+               ret = an8855_port_set_pid(priv, dp->index, AN8855_PORT_VID_DEFAULT);
+               if (ret)
+                       return ret;
+       }
+
+       /* Enable Airoha header mode on the cpu port */
+       ret = regmap_write(priv->regmap, AN8855_PVC_P(AN8855_CPU_PORT),
+                          AN8855_PORT_SPEC_REPLACE_MODE | AN8855_PORT_SPEC_TAG);
+       if (ret)
+               return ret;
+
+       /* Unknown multicast frame forwarding to the cpu port */
+       ret = regmap_write(priv->regmap, AN8855_UNMF, BIT(AN8855_CPU_PORT));
+       if (ret)
+               return ret;
+
+       /* Set CPU port number */
+       ret = regmap_update_bits(priv->regmap, AN8855_MFC,
+                                AN8855_CPU_EN | AN8855_CPU_PORT_IDX,
+                                AN8855_CPU_EN |
+                                FIELD_PREP(AN8855_CPU_PORT_IDX, AN8855_CPU_PORT));
+       if (ret)
+               return ret;
+
+       /* CPU port gets connected to all user ports of
+        * the switch.
+        */
+       ret = regmap_write(priv->regmap, AN8855_PORTMATRIX_P(AN8855_CPU_PORT),
+                          FIELD_PREP(AN8855_PORTMATRIX, dsa_user_ports(ds)));
+       if (ret)
+               return ret;
+
+       /* CPU port is set to fallback mode to let untagged
+        * frames pass through.
+        */
+       ret = regmap_update_bits(priv->regmap, AN8855_PCR_P(AN8855_CPU_PORT),
+                                AN8855_PORT_VLAN,
+                                FIELD_PREP(AN8855_PORT_VLAN, AN8855_PORT_FALLBACK_MODE));
+       if (ret)
+               return ret;
+
+       /* Enable Broadcast Forward on CPU port */
+       ret = regmap_set_bits(priv->regmap, AN8855_BCF, BIT(AN8855_CPU_PORT));
+       if (ret)
+               return ret;
+
+       /* Enable Unknown Unicast Forward on CPU port */
+       ret = regmap_set_bits(priv->regmap, AN8855_UNUF, BIT(AN8855_CPU_PORT));
+       if (ret)
+               return ret;
+
+       /* Enable Unknown Multicast Forward on CPU port */
+       ret = regmap_set_bits(priv->regmap, AN8855_UNMF, BIT(AN8855_CPU_PORT));
+       if (ret)
+               return ret;
+
+       ret = regmap_set_bits(priv->regmap, AN8855_UNIPMF, BIT(AN8855_CPU_PORT));
+       if (ret)
+               return ret;
+
+       /* Setup Trap special frame to CPU rules */
+       ret = an8855_trap_special_frames(priv);
+       if (ret)
+               return ret;
+
+       dsa_switch_for_each_port(dp, ds) {
+               /* Disable Learning on all ports.
+                * Learning on CPU is disabled for fdb isolation and handled by
+                * assisted_learning_on_cpu_port.
+                */
+               ret = regmap_set_bits(priv->regmap, AN8855_PSC_P(dp->index),
+                                     AN8855_SA_DIS);
+               if (ret)
+                       return ret;
+
+               /* Enable consistent egress tag (for VLAN unware VLAN-passtrough) */
+               ret = regmap_update_bits(priv->regmap, AN8855_PVC_P(dp->index),
+                                        AN8855_PVC_EG_TAG,
+                                        FIELD_PREP(AN8855_PVC_EG_TAG, AN8855_VLAN_EG_CONSISTENT));
+               if (ret)
+                       return ret;
+       }
+
+       /* Setup VLAN for Default PVID */
+       ret = an8855_setup_pvid_vlan(priv);
+       if (ret)
+               return ret;
+
+       ret = regmap_clear_bits(priv->regmap, AN8855_CKGCR,
+                               AN8855_CKG_LNKDN_GLB_STOP | AN8855_CKG_LNKDN_PORT_STOP);
+       if (ret)
+               return ret;
+
+       /* Release global PHY power down */
+       ret = regmap_write(priv->regmap, AN8855_RG_GPHY_AFE_PWD, 0x0);
+       if (ret)
+               return ret;
+
+       ds->configure_vlan_while_not_filtering = true;
+
+       /* Flush the FDB table */
+       ret = an8855_fdb_cmd(priv, AN8855_FDB_FLUSH, NULL);
+       if (ret < 0)
+               return ret;
+
+       /* Set min a max ageing value supported */
+       ds->ageing_time_min = AN8855_L2_AGING_MS_CONSTANT;
+       ds->ageing_time_max = FIELD_MAX(AN8855_AGE_CNT) *
+                             FIELD_MAX(AN8855_AGE_UNIT) *
+                             AN8855_L2_AGING_MS_CONSTANT;
+
+       /* Enable assisted learning for fdb isolation */
+       ds->assisted_learning_on_cpu_port = true;
+
+       return 0;
+}
+
+static struct phylink_pcs *an8855_phylink_mac_select_pcs(struct phylink_config *config,
+                                                        phy_interface_t interface)
+{
+       struct dsa_port *dp = dsa_phylink_to_port(config);
+       struct an8855_priv *priv = dp->ds->priv;
+
+       switch (interface) {
+       case PHY_INTERFACE_MODE_SGMII:
+       case PHY_INTERFACE_MODE_2500BASEX:
+               return &priv->pcs;
+       default:
+               return NULL;
+       }
+}
+
+static void an8855_phylink_mac_config(struct phylink_config *config,
+                                     unsigned int mode,
+                                     const struct phylink_link_state *state)
+{
+       struct dsa_port *dp = dsa_phylink_to_port(config);
+       struct dsa_switch *ds = dp->ds;
+       struct an8855_priv *priv;
+       int port = dp->index;
+
+       priv = ds->priv;
+
+       /* Nothing to configure for internal ports */
+       if (port != 5)
+               return;
+
+       regmap_update_bits(priv->regmap, AN8855_PMCR_P(port),
+                          AN8855_PMCR_IFG_XMIT | AN8855_PMCR_MAC_MODE |
+                          AN8855_PMCR_BACKOFF_EN | AN8855_PMCR_BACKPR_EN,
+                          FIELD_PREP(AN8855_PMCR_IFG_XMIT, 0x1) |
+                          AN8855_PMCR_MAC_MODE | AN8855_PMCR_BACKOFF_EN |
+                          AN8855_PMCR_BACKPR_EN);
+}
+
+static void an8855_phylink_get_caps(struct dsa_switch *ds, int port,
+                                   struct phylink_config *config)
+{
+       switch (port) {
+       case 0:
+       case 1:
+       case 2:
+       case 3:
+       case 4:
+               __set_bit(PHY_INTERFACE_MODE_GMII,
+                         config->supported_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+                         config->supported_interfaces);
+               break;
+       case 5:
+               phy_interface_set_rgmii(config->supported_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_SGMII,
+                         config->supported_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+                         config->supported_interfaces);
+               break;
+       }
+
+       config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+                                  MAC_10 | MAC_100 | MAC_1000FD | MAC_2500FD;
+}
+
+static void an8855_phylink_mac_link_down(struct phylink_config *config,
+                                        unsigned int mode,
+                                        phy_interface_t interface)
+{
+       struct dsa_port *dp = dsa_phylink_to_port(config);
+       struct an8855_priv *priv = dp->ds->priv;
+
+       /* With autoneg just disable TX/RX else also force link down */
+       if (phylink_autoneg_inband(mode)) {
+               regmap_clear_bits(priv->regmap, AN8855_PMCR_P(dp->index),
+                                 AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN);
+       } else {
+               regmap_update_bits(priv->regmap, AN8855_PMCR_P(dp->index),
+                                  AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN |
+                                  AN8855_PMCR_FORCE_MODE | AN8855_PMCR_FORCE_LNK,
+                                  AN8855_PMCR_FORCE_MODE);
+       }
+}
+
+static void an8855_phylink_mac_link_up(struct phylink_config *config,
+                                      struct phy_device *phydev, unsigned int mode,
+                                      phy_interface_t interface, int speed,
+                                      int duplex, bool tx_pause, bool rx_pause)
+{
+       struct dsa_port *dp = dsa_phylink_to_port(config);
+       struct an8855_priv *priv = dp->ds->priv;
+       int port = dp->index;
+       u32 reg;
+
+       reg = regmap_read(priv->regmap, AN8855_PMCR_P(port), &reg);
+       if (phylink_autoneg_inband(mode)) {
+               reg &= ~AN8855_PMCR_FORCE_MODE;
+       } else {
+               reg |= AN8855_PMCR_FORCE_MODE | AN8855_PMCR_FORCE_LNK;
+
+               reg &= ~AN8855_PMCR_FORCE_SPEED;
+               switch (speed) {
+               case SPEED_10:
+                       reg |= AN8855_PMCR_FORCE_SPEED_10;
+                       break;
+               case SPEED_100:
+                       reg |= AN8855_PMCR_FORCE_SPEED_100;
+                       break;
+               case SPEED_1000:
+                       reg |= AN8855_PMCR_FORCE_SPEED_1000;
+                       break;
+               case SPEED_2500:
+                       reg |= AN8855_PMCR_FORCE_SPEED_2500;
+                       break;
+               case SPEED_5000:
+                       dev_err(priv->dev, "Missing support for 5G speed. Aborting...\n");
+                       return;
+               }
+
+               reg &= ~AN8855_PMCR_FORCE_FDX;
+               if (duplex == DUPLEX_FULL)
+                       reg |= AN8855_PMCR_FORCE_FDX;
+
+               reg &= ~AN8855_PMCR_RX_FC_EN;
+               if (rx_pause || dsa_port_is_cpu(dp))
+                       reg |= AN8855_PMCR_RX_FC_EN;
+
+               reg &= ~AN8855_PMCR_TX_FC_EN;
+               if (rx_pause || dsa_port_is_cpu(dp))
+                       reg |= AN8855_PMCR_TX_FC_EN;
+
+               /* Disable any EEE options */
+               reg &= ~(AN8855_PMCR_FORCE_EEE5G | AN8855_PMCR_FORCE_EEE2P5G |
+                        AN8855_PMCR_FORCE_EEE1G | AN8855_PMCR_FORCE_EEE100);
+       }
+
+       reg |= AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN;
+
+       regmap_write(priv->regmap, AN8855_PMCR_P(port), reg);
+}
+
+static unsigned int an8855_pcs_inband_caps(struct phylink_pcs *pcs,
+                                          phy_interface_t interface)
+{
+       /* SGMII can be configured to use inband with AN result */
+       if (interface == PHY_INTERFACE_MODE_SGMII)
+               return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+
+       /* inband is not supported in 2500-baseX and must be disabled */
+       return  LINK_INBAND_DISABLE;
+}
+
+static void an8855_pcs_get_state(struct phylink_pcs *pcs,
+                                struct phylink_link_state *state)
+{
+       struct an8855_priv *priv = container_of(pcs, struct an8855_priv, pcs);
+       u32 val;
+       int ret;
+
+       ret = regmap_read(priv->regmap, AN8855_PMSR_P(AN8855_CPU_PORT), &val);
+       if (ret < 0) {
+               state->link = false;
+               return;
+       }
+
+       state->link = !!(val & AN8855_PMSR_LNK);
+       state->an_complete = state->link;
+       state->duplex = (val & AN8855_PMSR_DPX) ? DUPLEX_FULL :
+                                                 DUPLEX_HALF;
+
+       switch (val & AN8855_PMSR_SPEED) {
+       case AN8855_PMSR_SPEED_10:
+               state->speed = SPEED_10;
+               break;
+       case AN8855_PMSR_SPEED_100:
+               state->speed = SPEED_100;
+               break;
+       case AN8855_PMSR_SPEED_1000:
+               state->speed = SPEED_1000;
+               break;
+       case AN8855_PMSR_SPEED_2500:
+               state->speed = SPEED_2500;
+               break;
+       case AN8855_PMSR_SPEED_5000:
+               dev_err(priv->dev, "Missing support for 5G speed. Setting Unknown.\n");
+               fallthrough;
+       default:
+               state->speed = SPEED_UNKNOWN;
+               break;
+       }
+
+       if (val & AN8855_PMSR_RX_FC)
+               state->pause |= MLO_PAUSE_RX;
+       if (val & AN8855_PMSR_TX_FC)
+               state->pause |= MLO_PAUSE_TX;
+}
+
+static int an8855_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+                            phy_interface_t interface,
+                            const unsigned long *advertising,
+                            bool permit_pause_to_mac)
+{
+       struct an8855_priv *priv = container_of(pcs, struct an8855_priv, pcs);
+       u32 val;
+       int ret;
+
+       /*                   !!! WELCOME TO HELL !!!                   */
+
+       /* TX FIR - improve TX EYE */
+       ret = regmap_update_bits(priv->regmap, AN8855_INTF_CTRL_10,
+                                AN8855_RG_DA_QP_TX_FIR_C2_SEL |
+                                AN8855_RG_DA_QP_TX_FIR_C2_FORCE |
+                                AN8855_RG_DA_QP_TX_FIR_C1_SEL |
+                                AN8855_RG_DA_QP_TX_FIR_C1_FORCE,
+                                AN8855_RG_DA_QP_TX_FIR_C2_SEL |
+                                FIELD_PREP(AN8855_RG_DA_QP_TX_FIR_C2_FORCE, 0x4) |
+                                AN8855_RG_DA_QP_TX_FIR_C1_SEL |
+                                FIELD_PREP(AN8855_RG_DA_QP_TX_FIR_C1_FORCE, 0x0));
+       if (ret)
+               return ret;
+
+       if (interface == PHY_INTERFACE_MODE_2500BASEX)
+               val = 0x0;
+       else
+               val = 0xd;
+       ret = regmap_update_bits(priv->regmap, AN8855_INTF_CTRL_11,
+                                AN8855_RG_DA_QP_TX_FIR_C0B_SEL |
+                                AN8855_RG_DA_QP_TX_FIR_C0B_FORCE,
+                                AN8855_RG_DA_QP_TX_FIR_C0B_SEL |
+                                FIELD_PREP(AN8855_RG_DA_QP_TX_FIR_C0B_FORCE, val));
+       if (ret)
+               return ret;
+
+       /* RX CDR - improve RX Jitter Tolerance */
+       if (interface == PHY_INTERFACE_MODE_2500BASEX)
+               val = 0x5;
+       else
+               val = 0x6;
+       ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_LPF_BOT_LIM,
+                                AN8855_RG_QP_CDR_LPF_KP_GAIN |
+                                AN8855_RG_QP_CDR_LPF_KI_GAIN,
+                                FIELD_PREP(AN8855_RG_QP_CDR_LPF_KP_GAIN, val) |
+                                FIELD_PREP(AN8855_RG_QP_CDR_LPF_KI_GAIN, val));
+       if (ret)
+               return ret;
+
+       /* PLL */
+       if (interface == PHY_INTERFACE_MODE_2500BASEX)
+               val = 0x1;
+       else
+               val = 0x0;
+       ret = regmap_update_bits(priv->regmap, AN8855_QP_DIG_MODE_CTRL_1,
+                                AN8855_RG_TPHY_SPEED,
+                                FIELD_PREP(AN8855_RG_TPHY_SPEED, val));
+       if (ret)
+               return ret;
+
+       /* PLL - LPF */
+       ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
+                                AN8855_RG_DA_QP_PLL_RICO_SEL_INTF |
+                                AN8855_RG_DA_QP_PLL_FBKSEL_INTF |
+                                AN8855_RG_DA_QP_PLL_BR_INTF |
+                                AN8855_RG_DA_QP_PLL_BPD_INTF |
+                                AN8855_RG_DA_QP_PLL_BPA_INTF |
+                                AN8855_RG_DA_QP_PLL_BC_INTF,
+                                AN8855_RG_DA_QP_PLL_RICO_SEL_INTF |
+                                FIELD_PREP(AN8855_RG_DA_QP_PLL_FBKSEL_INTF, 0x0) |
+                                FIELD_PREP(AN8855_RG_DA_QP_PLL_BR_INTF, 0x3) |
+                                FIELD_PREP(AN8855_RG_DA_QP_PLL_BPD_INTF, 0x0) |
+                                FIELD_PREP(AN8855_RG_DA_QP_PLL_BPA_INTF, 0x5) |
+                                FIELD_PREP(AN8855_RG_DA_QP_PLL_BC_INTF, 0x1));
+       if (ret)
+               return ret;
+
+       /* PLL - ICO */
+       ret = regmap_set_bits(priv->regmap, AN8855_PLL_CTRL_4,
+                             AN8855_RG_DA_QP_PLL_ICOLP_EN_INTF);
+       if (ret)
+               return ret;
+       ret = regmap_clear_bits(priv->regmap, AN8855_PLL_CTRL_2,
+                               AN8855_RG_DA_QP_PLL_ICOIQ_EN_INTF);
+       if (ret)
+               return ret;
+
+       /* PLL - CHP */
+       if (interface == PHY_INTERFACE_MODE_2500BASEX)
+               val = 0x6;
+       else
+               val = 0x4;
+       ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
+                                AN8855_RG_DA_QP_PLL_IR_INTF,
+                                FIELD_PREP(AN8855_RG_DA_QP_PLL_IR_INTF, val));
+       if (ret)
+               return ret;
+
+       /* PLL - PFD */
+       ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
+                                AN8855_RG_DA_QP_PLL_PFD_OFFSET_EN_INTRF |
+                                AN8855_RG_DA_QP_PLL_PFD_OFFSET_INTF |
+                                AN8855_RG_DA_QP_PLL_KBAND_PREDIV_INTF,
+                                FIELD_PREP(AN8855_RG_DA_QP_PLL_PFD_OFFSET_INTF, 0x1) |
+                                FIELD_PREP(AN8855_RG_DA_QP_PLL_KBAND_PREDIV_INTF, 0x1));
+       if (ret)
+               return ret;
+
+       /* PLL - POSTDIV */
+       ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
+                                AN8855_RG_DA_QP_PLL_POSTDIV_EN_INTF |
+                                AN8855_RG_DA_QP_PLL_PHY_CK_EN_INTF |
+                                AN8855_RG_DA_QP_PLL_PCK_SEL_INTF,
+                                AN8855_RG_DA_QP_PLL_PCK_SEL_INTF);
+       if (ret)
+               return ret;
+
+       /* PLL - SDM */
+       ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
+                                AN8855_RG_DA_QP_PLL_SDM_HREN_INTF,
+                                FIELD_PREP(AN8855_RG_DA_QP_PLL_SDM_HREN_INTF, 0x0));
+       if (ret)
+               return ret;
+       ret = regmap_clear_bits(priv->regmap, AN8855_PLL_CTRL_2,
+                               AN8855_RG_DA_QP_PLL_SDM_IFM_INTF);
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_SS_LCPLL_PWCTL_SETTING_2,
+                                AN8855_RG_NCPO_ANA_MSB,
+                                FIELD_PREP(AN8855_RG_NCPO_ANA_MSB, 0x1));
+       if (ret)
+               return ret;
+
+       if (interface == PHY_INTERFACE_MODE_2500BASEX)
+               val = 0x7a000000;
+       else
+               val = 0x48000000;
+       ret = regmap_write(priv->regmap, AN8855_SS_LCPLL_TDC_FLT_2,
+                          FIELD_PREP(AN8855_RG_LCPLL_NCPO_VALUE, val));
+       if (ret)
+               return ret;
+       ret = regmap_write(priv->regmap, AN8855_SS_LCPLL_TDC_PCW_1,
+                          FIELD_PREP(AN8855_RG_LCPLL_PON_HRDDS_PCW_NCPO_GPON, val));
+       if (ret)
+               return ret;
+
+       ret = regmap_clear_bits(priv->regmap, AN8855_SS_LCPLL_TDC_FLT_5,
+                               AN8855_RG_LCPLL_NCPO_CHG);
+       if (ret)
+               return ret;
+       ret = regmap_clear_bits(priv->regmap, AN8855_PLL_CK_CTRL_0,
+                               AN8855_RG_DA_QP_PLL_SDM_DI_EN_INTF);
+       if (ret)
+               return ret;
+
+       /* PLL - SS */
+       ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_3,
+                                AN8855_RG_DA_QP_PLL_SSC_DELTA_INTF,
+                                FIELD_PREP(AN8855_RG_DA_QP_PLL_SSC_DELTA_INTF, 0x0));
+       if (ret)
+               return ret;
+       ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_4,
+                                AN8855_RG_DA_QP_PLL_SSC_DIR_DLY_INTF,
+                                FIELD_PREP(AN8855_RG_DA_QP_PLL_SSC_DIR_DLY_INTF, 0x0));
+       if (ret)
+               return ret;
+       ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_3,
+                                AN8855_RG_DA_QP_PLL_SSC_PERIOD_INTF,
+                                FIELD_PREP(AN8855_RG_DA_QP_PLL_SSC_PERIOD_INTF, 0x0));
+       if (ret)
+               return ret;
+
+       /* PLL - TDC */
+       ret = regmap_clear_bits(priv->regmap, AN8855_PLL_CK_CTRL_0,
+                               AN8855_RG_DA_QP_PLL_TDC_TXCK_SEL_INTF);
+       if (ret)
+               return ret;
+
+       ret = regmap_set_bits(priv->regmap, AN8855_RG_QP_PLL_SDM_ORD,
+                             AN8855_RG_QP_PLL_SSC_TRI_EN);
+       if (ret)
+               return ret;
+       ret = regmap_set_bits(priv->regmap, AN8855_RG_QP_PLL_SDM_ORD,
+                             AN8855_RG_QP_PLL_SSC_PHASE_INI);
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_RX_DAC_EN,
+                                AN8855_RG_QP_SIGDET_HF,
+                                FIELD_PREP(AN8855_RG_QP_SIGDET_HF, 0x2));
+       if (ret)
+               return ret;
+
+       /* TCL Disable (only for Co-SIM) */
+       ret = regmap_clear_bits(priv->regmap, AN8855_PON_RXFEDIG_CTRL_0,
+                               AN8855_RG_QP_EQ_RX500M_CK_SEL);
+       if (ret)
+               return ret;
+
+       /* TX Init */
+       if (interface == PHY_INTERFACE_MODE_2500BASEX)
+               val = 0x4;
+       else
+               val = 0x0;
+       ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_TX_MODE,
+                                AN8855_RG_QP_TX_RESERVE |
+                                AN8855_RG_QP_TX_MODE_16B_EN,
+                                FIELD_PREP(AN8855_RG_QP_TX_RESERVE, val));
+       if (ret)
+               return ret;
+
+       /* RX Control/Init */
+       ret = regmap_set_bits(priv->regmap, AN8855_RG_QP_RXAFE_RESERVE,
+                             AN8855_RG_QP_CDR_PD_10B_EN);
+       if (ret)
+               return ret;
+
+       if (interface == PHY_INTERFACE_MODE_2500BASEX)
+               val = 0x1;
+       else
+               val = 0x2;
+       ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_LPF_MJV_LIM,
+                                AN8855_RG_QP_CDR_LPF_RATIO,
+                                FIELD_PREP(AN8855_RG_QP_CDR_LPF_RATIO, val));
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_LPF_SETVALUE,
+                                AN8855_RG_QP_CDR_PR_BUF_IN_SR |
+                                AN8855_RG_QP_CDR_PR_BETA_SEL,
+                                FIELD_PREP(AN8855_RG_QP_CDR_PR_BUF_IN_SR, 0x6) |
+                                FIELD_PREP(AN8855_RG_QP_CDR_PR_BETA_SEL, 0x1));
+       if (ret)
+               return ret;
+
+       if (interface == PHY_INTERFACE_MODE_2500BASEX)
+               val = 0xf;
+       else
+               val = 0xc;
+       ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_PR_CKREF_DIV1,
+                                AN8855_RG_QP_CDR_PR_DAC_BAND,
+                                FIELD_PREP(AN8855_RG_QP_CDR_PR_DAC_BAND, val));
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE,
+                                AN8855_RG_QP_CDR_PR_KBAND_PCIE_MODE |
+                                AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE_MASK,
+                                FIELD_PREP(AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE_MASK, 0x19));
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_FORCE_IBANDLPF_R_OFF,
+                                AN8855_RG_QP_CDR_PHYCK_SEL |
+                                AN8855_RG_QP_CDR_PHYCK_RSTB |
+                                AN8855_RG_QP_CDR_PHYCK_DIV,
+                                FIELD_PREP(AN8855_RG_QP_CDR_PHYCK_SEL, 0x2) |
+                                FIELD_PREP(AN8855_RG_QP_CDR_PHYCK_DIV, 0x21));
+       if (ret)
+               return ret;
+
+       ret = regmap_clear_bits(priv->regmap, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE,
+                               AN8855_RG_QP_CDR_PR_XFICK_EN);
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_PR_CKREF_DIV1,
+                                AN8855_RG_QP_CDR_PR_KBAND_DIV,
+                                FIELD_PREP(AN8855_RG_QP_CDR_PR_KBAND_DIV, 0x4));
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_26,
+                                AN8855_RG_QP_EQ_RETRAIN_ONLY_EN |
+                                AN8855_RG_LINK_NE_EN |
+                                AN8855_RG_LINK_ERRO_EN,
+                                AN8855_RG_QP_EQ_RETRAIN_ONLY_EN |
+                                AN8855_RG_LINK_ERRO_EN);
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_RX_DLY_0,
+                                AN8855_RG_QP_RX_SAOSC_EN_H_DLY |
+                                AN8855_RG_QP_RX_PI_CAL_EN_H_DLY,
+                                FIELD_PREP(AN8855_RG_QP_RX_SAOSC_EN_H_DLY, 0x3f) |
+                                FIELD_PREP(AN8855_RG_QP_RX_PI_CAL_EN_H_DLY, 0x6f));
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_42,
+                                AN8855_RG_QP_EQ_EN_DLY,
+                                FIELD_PREP(AN8855_RG_QP_EQ_EN_DLY, 0x150));
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_2,
+                                AN8855_RG_QP_RX_EQ_EN_H_DLY,
+                                FIELD_PREP(AN8855_RG_QP_RX_EQ_EN_H_DLY, 0x150));
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_PON_RXFEDIG_CTRL_9,
+                                AN8855_RG_QP_EQ_LEQOSC_DLYCNT,
+                                FIELD_PREP(AN8855_RG_QP_EQ_LEQOSC_DLYCNT, 0x1));
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_8,
+                                AN8855_RG_DA_QP_SAOSC_DONE_TIME |
+                                AN8855_RG_DA_QP_LEQOS_EN_TIME,
+                                FIELD_PREP(AN8855_RG_DA_QP_SAOSC_DONE_TIME, 0x200) |
+                                FIELD_PREP(AN8855_RG_DA_QP_LEQOS_EN_TIME, 0xfff));
+       if (ret)
+               return ret;
+
+       /* Frequency meter */
+       if (interface == PHY_INTERFACE_MODE_2500BASEX)
+               val = 0x10;
+       else
+               val = 0x28;
+       ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_5,
+                                AN8855_RG_FREDET_CHK_CYCLE,
+                                FIELD_PREP(AN8855_RG_FREDET_CHK_CYCLE, val));
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_6,
+                                AN8855_RG_FREDET_GOLDEN_CYCLE,
+                                FIELD_PREP(AN8855_RG_FREDET_GOLDEN_CYCLE, 0x64));
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_7,
+                                AN8855_RG_FREDET_TOLERATE_CYCLE,
+                                FIELD_PREP(AN8855_RG_FREDET_TOLERATE_CYCLE, 0x2710));
+       if (ret)
+               return ret;
+
+       ret = regmap_set_bits(priv->regmap, AN8855_PLL_CTRL_0,
+                             AN8855_RG_PHYA_AUTO_INIT);
+       if (ret)
+               return ret;
+
+       /* PCS Init */
+       if (interface == PHY_INTERFACE_MODE_SGMII &&
+           neg_mode == PHYLINK_PCS_NEG_INBAND_DISABLED) {
+               ret = regmap_clear_bits(priv->regmap, AN8855_QP_DIG_MODE_CTRL_0,
+                                       AN8855_RG_SGMII_MODE | AN8855_RG_SGMII_AN_EN);
+               if (ret)
+                       return ret;
+       }
+
+       ret = regmap_clear_bits(priv->regmap, AN8855_RG_HSGMII_PCS_CTROL_1,
+                               AN8855_RG_TBI_10B_MODE);
+       if (ret)
+               return ret;
+
+       if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
+               /* Set AN Ability - Interrupt */
+               ret = regmap_set_bits(priv->regmap, AN8855_SGMII_REG_AN_FORCE_CL37,
+                                     AN8855_RG_FORCE_AN_DONE);
+               if (ret)
+                       return ret;
+
+               ret = regmap_update_bits(priv->regmap, AN8855_SGMII_REG_AN_13,
+                                        AN8855_SGMII_REMOTE_FAULT_DIS |
+                                        AN8855_SGMII_IF_MODE,
+                                        AN8855_SGMII_REMOTE_FAULT_DIS |
+                                        FIELD_PREP(AN8855_SGMII_IF_MODE, 0xb));
+               if (ret)
+                       return ret;
+       }
+
+       /* Rate Adaption - GMII path config. */
+       if (interface == PHY_INTERFACE_MODE_2500BASEX) {
+               ret = regmap_clear_bits(priv->regmap, AN8855_RATE_ADP_P0_CTRL_0,
+                                       AN8855_RG_P0_DIS_MII_MODE);
+               if (ret)
+                       return ret;
+       } else {
+               if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
+                       ret = regmap_set_bits(priv->regmap, AN8855_MII_RA_AN_ENABLE,
+                                             AN8855_RG_P0_RA_AN_EN);
+                       if (ret)
+                               return ret;
+               } else {
+                       ret = regmap_update_bits(priv->regmap, AN8855_RG_AN_SGMII_MODE_FORCE,
+                                                AN8855_RG_FORCE_CUR_SGMII_MODE |
+                                                AN8855_RG_FORCE_CUR_SGMII_SEL,
+                                                AN8855_RG_FORCE_CUR_SGMII_SEL);
+                       if (ret)
+                               return ret;
+
+                       ret = regmap_clear_bits(priv->regmap, AN8855_RATE_ADP_P0_CTRL_0,
+                                               AN8855_RG_P0_MII_RA_RX_EN |
+                                               AN8855_RG_P0_MII_RA_TX_EN |
+                                               AN8855_RG_P0_MII_RA_RX_MODE |
+                                               AN8855_RG_P0_MII_RA_TX_MODE);
+                       if (ret)
+                               return ret;
+               }
+
+               ret = regmap_set_bits(priv->regmap, AN8855_RATE_ADP_P0_CTRL_0,
+                                     AN8855_RG_P0_MII_MODE);
+               if (ret)
+                       return ret;
+       }
+
+       ret = regmap_set_bits(priv->regmap, AN8855_RG_RATE_ADAPT_CTRL_0,
+                             AN8855_RG_RATE_ADAPT_RX_BYPASS |
+                             AN8855_RG_RATE_ADAPT_TX_BYPASS |
+                             AN8855_RG_RATE_ADAPT_RX_EN |
+                             AN8855_RG_RATE_ADAPT_TX_EN);
+       if (ret)
+               return ret;
+
+       /* Disable AN if not in autoneg */
+       ret = regmap_update_bits(priv->regmap, AN8855_SGMII_REG_AN0, BMCR_ANENABLE,
+                                neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED ? BMCR_ANENABLE :
+                                                                             0);
+       if (ret)
+               return ret;
+
+       if (interface == PHY_INTERFACE_MODE_SGMII) {
+               /* Follow SDK init flow with restarting AN after AN enable */
+               if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
+                       ret = regmap_set_bits(priv->regmap, AN8855_SGMII_REG_AN0,
+                                             BMCR_ANRESTART);
+                       if (ret)
+                               return ret;
+               } else {
+                       ret = regmap_set_bits(priv->regmap, AN8855_PHY_RX_FORCE_CTRL_0,
+                                             AN8855_RG_FORCE_TXC_SEL);
+                       if (ret)
+                               return ret;
+               }
+       }
+
+       /* Force Speed with fixed-link or 2500base-x as doesn't support aneg */
+       if (interface == PHY_INTERFACE_MODE_2500BASEX ||
+           neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) {
+               if (interface == PHY_INTERFACE_MODE_2500BASEX)
+                       val = AN8855_RG_LINK_MODE_P0_SPEED_2500;
+               else
+                       val = AN8855_RG_LINK_MODE_P0_SPEED_1000;
+               ret = regmap_update_bits(priv->regmap, AN8855_SGMII_STS_CTRL_0,
+                                        AN8855_RG_LINK_MODE_P0 |
+                                        AN8855_RG_FORCE_SPD_MODE_P0,
+                                        val | AN8855_RG_FORCE_SPD_MODE_P0);
+               if (ret)
+                       return ret;
+       }
+
+       /* bypass flow control to MAC */
+       ret = regmap_write(priv->regmap, AN8855_MSG_RX_LIK_STS_0,
+                          AN8855_RG_DPX_STS_P3 | AN8855_RG_DPX_STS_P2 |
+                          AN8855_RG_DPX_STS_P1 | AN8855_RG_TXFC_STS_P0 |
+                          AN8855_RG_RXFC_STS_P0 | AN8855_RG_DPX_STS_P0);
+       if (ret)
+               return ret;
+       ret = regmap_write(priv->regmap, AN8855_MSG_RX_LIK_STS_2,
+                          AN8855_RG_RXFC_AN_BYPASS_P3 |
+                          AN8855_RG_RXFC_AN_BYPASS_P2 |
+                          AN8855_RG_RXFC_AN_BYPASS_P1 |
+                          AN8855_RG_TXFC_AN_BYPASS_P3 |
+                          AN8855_RG_TXFC_AN_BYPASS_P2 |
+                          AN8855_RG_TXFC_AN_BYPASS_P1 |
+                          AN8855_RG_DPX_AN_BYPASS_P3 |
+                          AN8855_RG_DPX_AN_BYPASS_P2 |
+                          AN8855_RG_DPX_AN_BYPASS_P1 |
+                          AN8855_RG_DPX_AN_BYPASS_P0);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static void an8855_pcs_an_restart(struct phylink_pcs *pcs)
+{
+       return;
+}
+
+static const struct phylink_pcs_ops an8855_pcs_ops = {
+       .pcs_inband_caps = an8855_pcs_inband_caps,
+       .pcs_get_state = an8855_pcs_get_state,
+       .pcs_config = an8855_pcs_config,
+       .pcs_an_restart = an8855_pcs_an_restart,
+};
+
+static const struct phylink_mac_ops an8855_phylink_mac_ops = {
+       .mac_select_pcs = an8855_phylink_mac_select_pcs,
+       .mac_config     = an8855_phylink_mac_config,
+       .mac_link_down  = an8855_phylink_mac_link_down,
+       .mac_link_up    = an8855_phylink_mac_link_up,
+};
+
+static const struct dsa_switch_ops an8855_switch_ops = {
+       .get_tag_protocol = an8855_get_tag_protocol,
+       .setup = an8855_setup,
+       .get_phy_flags = en8855_get_phy_flags,
+       .phylink_get_caps = an8855_phylink_get_caps,
+       .get_strings = an8855_get_strings,
+       .get_ethtool_stats = an8855_get_ethtool_stats,
+       .get_sset_count = an8855_get_sset_count,
+       .get_eth_mac_stats = an8855_get_eth_mac_stats,
+       .get_eth_ctrl_stats = an8855_get_eth_ctrl_stats,
+       .get_rmon_stats = an8855_get_rmon_stats,
+       .port_enable = an8855_port_enable,
+       .port_disable = an8855_port_disable,
+       .set_ageing_time = an8855_set_ageing_time,
+       .port_bridge_join = an8855_port_bridge_join,
+       .port_bridge_leave = an8855_port_bridge_leave,
+       .port_fast_age = an8855_port_fast_age,
+       .port_stp_state_set = an8855_port_stp_state_set,
+       .port_pre_bridge_flags = an8855_port_pre_bridge_flags,
+       .port_bridge_flags = an8855_port_bridge_flags,
+       .port_vlan_filtering = an8855_port_vlan_filtering,
+       .port_vlan_add = an8855_port_vlan_add,
+       .port_vlan_del = an8855_port_vlan_del,
+       .port_fdb_add = an8855_port_fdb_add,
+       .port_fdb_del = an8855_port_fdb_del,
+       .port_fdb_dump = an8855_port_fdb_dump,
+       .port_mdb_add = an8855_port_mdb_add,
+       .port_mdb_del = an8855_port_mdb_del,
+       .port_change_mtu = an8855_port_change_mtu,
+       .port_max_mtu = an8855_port_max_mtu,
+       .port_mirror_add = an8855_port_mirror_add,
+       .port_mirror_del = an8855_port_mirror_del,
+};
+
+static int an8855_read_switch_id(struct an8855_priv *priv)
+{
+       u32 id;
+       int ret;
+
+       ret = regmap_read(priv->regmap, AN8855_CREV, &id);
+       if (ret)
+               return ret;
+
+       if (id != AN8855_ID) {
+               dev_err(priv->dev,
+                       "Switch id detected %x but expected %x\n",
+                       id, AN8855_ID);
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static int an8855_switch_probe(struct platform_device *pdev)
+{
+       struct an8855_priv *priv;
+       u32 val;
+       int ret;
+
+       priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->dev = &pdev->dev;
+       priv->phy_require_calib = of_property_read_bool(priv->dev->of_node,
+                                                       "airoha,ext-surge");
+
+       priv->reset_gpio = devm_gpiod_get_optional(priv->dev, "reset",
+                                                  GPIOD_OUT_LOW);
+       if (IS_ERR(priv->reset_gpio))
+               return PTR_ERR(priv->reset_gpio);
+
+       /* Get regmap from MFD */
+       priv->regmap = dev_get_regmap(priv->dev->parent, NULL);
+
+       if (priv->reset_gpio) {
+               usleep_range(100000, 150000);
+               gpiod_set_value_cansleep(priv->reset_gpio, 0);
+               usleep_range(100000, 150000);
+               gpiod_set_value_cansleep(priv->reset_gpio, 1);
+
+               /* Poll HWTRAP reg to wait for Switch to fully Init */
+               ret = regmap_read_poll_timeout(priv->regmap, AN8855_HWTRAP, val,
+                                              val, 20, 200000);
+               if (ret)
+                       return ret;
+       }
+
+       ret = an8855_read_switch_id(priv);
+       if (ret)
+               return ret;
+
+       priv->ds = devm_kzalloc(priv->dev, sizeof(*priv->ds), GFP_KERNEL);
+       if (!priv->ds)
+               return -ENOMEM;
+
+       priv->ds->dev = priv->dev;
+       priv->ds->num_ports = AN8855_NUM_PORTS;
+       priv->ds->priv = priv;
+       priv->ds->ops = &an8855_switch_ops;
+       devm_mutex_init(priv->dev, &priv->reg_mutex);
+       priv->ds->phylink_mac_ops = &an8855_phylink_mac_ops;
+
+       priv->pcs.ops = &an8855_pcs_ops;
+       priv->pcs.neg_mode = true;
+       priv->pcs.poll = true;
+
+       dev_set_drvdata(priv->dev, priv);
+
+       return dsa_register_switch(priv->ds);
+}
+
+static int an8855_switch_remove(struct platform_device *pdev)
+{
+       struct an8855_priv *priv = dev_get_drvdata(&pdev->dev);
+
+       if (!priv)
+               return 0;
+
+       dsa_unregister_switch(priv->ds);
+       return 0;
+}
+
+static const struct of_device_id an8855_switch_of_match[] = {
+       { .compatible = "airoha,an8855-switch" },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, an8855_switch_of_match);
+
+static struct platform_driver an8855_switch_driver = {
+       .probe = an8855_switch_probe,
+       .remove = an8855_switch_remove,
+       .driver = {
+               .name = "an8855-switch",
+               .of_match_table = an8855_switch_of_match,
+       },
+};
+module_platform_driver(an8855_switch_driver);
+
+MODULE_AUTHOR("Min Yao <min.yao@airoha.com>");
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_DESCRIPTION("Driver for Airoha AN8855 Switch");
+MODULE_LICENSE("GPL");
diff --git a/target/linux/mediatek/files-6.6/drivers/net/dsa/an8855.h b/target/linux/mediatek/files-6.6/drivers/net/dsa/an8855.h
new file mode 100644 (file)
index 0000000..2462b9d
--- /dev/null
@@ -0,0 +1,783 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2023 Min Yao <min.yao@airoha.com>
+ * Copyright (C) 2024 Christian Marangi <ansuelsmth@gmail.com>
+ */
+
+#ifndef __AN8855_H
+#define __AN8855_H
+
+#include <linux/bitfield.h>
+
+#define AN8855_NUM_PORTS               6
+#define AN8855_CPU_PORT                        5
+#define AN8855_NUM_FDB_RECORDS         2048
+#define AN8855_GPHY_SMI_ADDR_DEFAULT   1
+#define AN8855_PORT_VID_DEFAULT                0
+
+#define MTK_TAG_LEN                    4
+#define AN8855_MAX_MTU                 (15360 - ETH_HLEN - ETH_FCS_LEN - MTK_TAG_LEN)
+
+#define AN8855_L2_AGING_MS_CONSTANT    1024
+
+#define AN8855_PHY_FLAGS_EN_CALIBRATION        BIT(0)
+
+/*     AN8855_SCU                      0x10000000 */
+#define AN8855_RG_GPIO_LED_MODE                0x10000054
+#define AN8855_RG_GPIO_LED_SEL(i)      (0x10000000 + (0x0058 + ((i) * 4)))
+#define AN8855_RG_INTB_MODE            0x10000080
+#define AN8855_RG_RGMII_TXCK_C         0x100001d0
+
+#define AN8855_PKG_SEL                 0x10000094
+#define   AN8855_PAG_SEL_AN8855H       0x2
+
+/* Register for hw trap status */
+#define AN8855_HWTRAP                  0x1000009c
+
+#define AN8855_RG_GPIO_L_INV           0x10000010
+#define AN8855_RG_GPIO_CTRL            0x1000a300
+#define AN8855_RG_GPIO_DATA            0x1000a304
+#define AN8855_RG_GPIO_OE              0x1000a314
+
+#define AN8855_CREV                    0x10005000
+#define   AN8855_ID                    0x8855
+
+/* Register for system reset */
+#define AN8855_RST_CTRL                        0x100050c0
+#define   AN8855_SYS_CTRL_SYS_RST      BIT(31)
+
+#define AN8855_INT_MASK                        0x100050f0
+#define   AN8855_INT_SYS               BIT(15)
+
+#define AN8855_RG_CLK_CPU_ICG          0x10005034
+#define   AN8855_MCU_ENABLE            BIT(3)
+
+#define AN8855_RG_TIMER_CTL            0x1000a100
+#define   AN8855_WDOG_ENABLE           BIT(25)
+
+#define AN8855_RG_GDMP_RAM             0x10010000
+
+/* Registers to mac forward control for unknown frames */
+#define AN8855_MFC                     0x10200010
+#define   AN8855_CPU_EN                        BIT(15)
+#define   AN8855_CPU_PORT_IDX          GENMASK(12, 8)
+
+#define AN8855_PAC                     0x10200024
+#define   AN8855_TAG_PAE_MANG_FR       BIT(30)
+#define   AN8855_TAG_PAE_BPDU_FR       BIT(28)
+#define   AN8855_TAG_PAE_EG_TAG                GENMASK(27, 25)
+#define   AN8855_TAG_PAE_LKY_VLAN      BIT(24)
+#define   AN8855_TAG_PAE_PRI_HIGH      BIT(23)
+#define   AN8855_TAG_PAE_MIR           GENMASK(20, 19)
+#define   AN8855_TAG_PAE_PORT_FW       GENMASK(18, 16)
+#define   AN8855_PAE_MANG_FR           BIT(14)
+#define   AN8855_PAE_BPDU_FR           BIT(12)
+#define   AN8855_PAE_EG_TAG            GENMASK(11, 9)
+#define   AN8855_PAE_LKY_VLAN          BIT(8)
+#define   AN8855_PAE_PRI_HIGH          BIT(7)
+#define   AN8855_PAE_MIR               GENMASK(4, 3)
+#define   AN8855_PAE_PORT_FW           GENMASK(2, 0)
+
+#define AN8855_RGAC1                   0x10200028
+#define   AN8855_R02_MANG_FR           BIT(30)
+#define   AN8855_R02_BPDU_FR           BIT(28)
+#define   AN8855_R02_EG_TAG            GENMASK(27, 25)
+#define   AN8855_R02_LKY_VLAN          BIT(24)
+#define   AN8855_R02_PRI_HIGH          BIT(23)
+#define   AN8855_R02_MIR               GENMASK(20, 19)
+#define   AN8855_R02_PORT_FW           GENMASK(18, 16)
+#define   AN8855_R01_MANG_FR           BIT(14)
+#define   AN8855_R01_BPDU_FR           BIT(12)
+#define   AN8855_R01_EG_TAG            GENMASK(11, 9)
+#define   AN8855_R01_LKY_VLAN          BIT(8)
+#define   AN8855_R01_PRI_HIGH          BIT(7)
+#define   AN8855_R01_MIR               GENMASK(4, 3)
+#define   AN8855_R01_PORT_FW           GENMASK(2, 0)
+
+#define AN8855_RGAC2                   0x1020002c
+#define   AN8855_R0E_MANG_FR           BIT(30)
+#define   AN8855_R0E_BPDU_FR           BIT(28)
+#define   AN8855_R0E_EG_TAG            GENMASK(27, 25)
+#define   AN8855_R0E_LKY_VLAN          BIT(24)
+#define   AN8855_R0E_PRI_HIGH          BIT(23)
+#define   AN8855_R0E_MIR               GENMASK(20, 19)
+#define   AN8855_R0E_PORT_FW           GENMASK(18, 16)
+#define   AN8855_R03_MANG_FR           BIT(14)
+#define   AN8855_R03_BPDU_FR           BIT(12)
+#define   AN8855_R03_EG_TAG            GENMASK(11, 9)
+#define   AN8855_R03_LKY_VLAN          BIT(8)
+#define   AN8855_R03_PRI_HIGH          BIT(7)
+#define   AN8855_R03_MIR               GENMASK(4, 3)
+#define   AN8855_R03_PORT_FW           GENMASK(2, 0)
+
+#define AN8855_AAC                     0x102000a0
+#define   AN8855_MAC_AUTO_FLUSH                BIT(28)
+/* Control Address Table Age time.
+ * (AN8855_AGE_CNT + 1) * ( AN8855_AGE_UNIT + 1 ) * AN8855_L2_AGING_MS_CONSTANT
+ */
+#define   AN8855_AGE_CNT               GENMASK(20, 12)
+/* Value in seconds. Value is always incremented of 1 */
+#define   AN8855_AGE_UNIT              GENMASK(10, 0)
+
+/* Registers for ARL Unknown Unicast Forward control */
+#define AN8855_UNUF                    0x102000b4
+
+/* Registers for ARL Unknown Multicast Forward control */
+#define AN8855_UNMF                    0x102000b8
+
+/* Registers for ARL Broadcast forward control */
+#define AN8855_BCF                     0x102000bc
+
+/* Registers for port address age disable */
+#define AN8855_AGDIS                   0x102000c0
+
+/* Registers for mirror port control */
+#define AN8855_MIR                     0x102000cc
+#define   AN8855_MIRROR_EN             BIT(7)
+#define   AN8855_MIRROR_PORT           GENMASK(4, 0)
+
+/* Registers for BPDU and PAE frame control*/
+#define AN8855_BPC                     0x102000d0
+#define   AN8855_BPDU_MANG_FR          BIT(14)
+#define   AN8855_BPDU_BPDU_FR          BIT(12)
+#define   AN8855_BPDU_EG_TAG           GENMASK(11, 9)
+#define   AN8855_BPDU_LKY_VLAN         BIT(8)
+#define   AN8855_BPDU_PRI_HIGH         BIT(7)
+#define   AN8855_BPDU_MIR              GENMASK(4, 3)
+#define   AN8855_BPDU_PORT_FW          GENMASK(2, 0)
+
+/* Registers for IP Unknown Multicast Forward control */
+#define AN8855_UNIPMF                  0x102000dc
+
+enum an8855_bpdu_port_fw {
+       AN8855_BPDU_FOLLOW_MFC = 0,
+       AN8855_BPDU_CPU_EXCLUDE = 4,
+       AN8855_BPDU_CPU_INCLUDE = 5,
+       AN8855_BPDU_CPU_ONLY = 6,
+       AN8855_BPDU_DROP = 7,
+};
+
+/* Register for address table control */
+#define AN8855_ATC                     0x10200300
+#define   AN8855_ATC_BUSY              BIT(31)
+#define   AN8855_ATC_HASH              GENMASK(24, 16)
+#define   AN8855_ATC_HIT               GENMASK(15, 12)
+#define   AN8855_ATC_MAT_MASK          GENMASK(11, 7)
+#define   AN8855_ATC_MAT(x)            FIELD_PREP(AN8855_ATC_MAT_MASK, x)
+#define   AN8855_ATC_SAT               GENMASK(5, 4)
+#define   AN8855_ATC_CMD               GENMASK(2, 0)
+
+enum an8855_fdb_mat_cmds {
+       AND8855_FDB_MAT_ALL = 0,
+       AND8855_FDB_MAT_MAC, /* All MAC address */
+       AND8855_FDB_MAT_DYNAMIC_MAC, /* All Dynamic MAC address */
+       AND8855_FDB_MAT_STATIC_MAC, /* All Static Mac Address */
+       AND8855_FDB_MAT_DIP, /* All DIP/GA address */
+       AND8855_FDB_MAT_DIP_IPV4, /* All DIP/GA IPv4 address */
+       AND8855_FDB_MAT_DIP_IPV6, /* All DIP/GA IPv6 address */
+       AND8855_FDB_MAT_DIP_SIP, /* All DIP_SIP address */
+       AND8855_FDB_MAT_DIP_SIP_IPV4, /* All DIP_SIP IPv4 address */
+       AND8855_FDB_MAT_DIP_SIP_IPV6, /* All DIP_SIP IPv6 address */
+       AND8855_FDB_MAT_MAC_CVID, /* All MAC address with CVID */
+       AND8855_FDB_MAT_MAC_FID, /* All MAC address with Filter ID */
+       AND8855_FDB_MAT_MAC_PORT, /* All MAC address with port */
+       AND8855_FDB_MAT_DIP_SIP_DIP_IPV4, /* All DIP_SIP address with DIP_IPV4 */
+       AND8855_FDB_MAT_DIP_SIP_SIP_IPV4, /* All DIP_SIP address with SIP_IPV4 */
+       AND8855_FDB_MAT_DIP_SIP_DIP_IPV6, /* All DIP_SIP address with DIP_IPV6 */
+       AND8855_FDB_MAT_DIP_SIP_SIP_IPV6, /* All DIP_SIP address with SIP_IPV6 */
+       /* All MAC address with MAC type (dynamic or static) with CVID */
+       AND8855_FDB_MAT_MAC_TYPE_CVID,
+       /* All MAC address with MAC type (dynamic or static) with Filter ID */
+       AND8855_FDB_MAT_MAC_TYPE_FID,
+       /* All MAC address with MAC type (dynamic or static) with port */
+       AND8855_FDB_MAT_MAC_TYPE_PORT,
+};
+
+enum an8855_fdb_cmds {
+       AN8855_FDB_READ = 0,
+       AN8855_FDB_WRITE = 1,
+       AN8855_FDB_FLUSH = 2,
+       AN8855_FDB_START = 4,
+       AN8855_FDB_NEXT = 5,
+};
+
+/* Registers for address table access */
+#define AN8855_ATA1                    0x10200304
+#define   AN8855_ATA1_MAC0             GENMASK(31, 24)
+#define   AN8855_ATA1_MAC1             GENMASK(23, 16)
+#define   AN8855_ATA1_MAC2             GENMASK(15, 8)
+#define   AN8855_ATA1_MAC3             GENMASK(7, 0)
+#define AN8855_ATA2                    0x10200308
+#define   AN8855_ATA2_MAC4             GENMASK(31, 24)
+#define   AN8855_ATA2_MAC5             GENMASK(23, 16)
+#define   AN8855_ATA2_UNAUTH           BIT(10)
+#define   AN8855_ATA2_TYPE             BIT(9) /* 1: dynamic, 0: static */
+#define   AN8855_ATA2_AGE              GENMASK(8, 0)
+
+/* Register for address table write data */
+#define AN8855_ATWD                    0x10200324
+#define   AN8855_ATWD_FID              GENMASK(31, 28)
+#define   AN8855_ATWD_VID              GENMASK(27, 16)
+#define   AN8855_ATWD_IVL              BIT(15)
+#define   AN8855_ATWD_EG_TAG           GENMASK(14, 12)
+#define   AN8855_ATWD_SA_MIR           GENMASK(9, 8)
+#define   AN8855_ATWD_SA_FWD           GENMASK(7, 5)
+#define   AN8855_ATWD_UPRI             GENMASK(4, 2)
+#define   AN8855_ATWD_LEAKY            BIT(1)
+#define   AN8855_ATWD_VLD              BIT(0) /* vid LOAD */
+#define AN8855_ATWD2                   0x10200328
+#define   AN8855_ATWD2_PORT            GENMASK(7, 0)
+
+/* Registers for table search read address */
+#define AN8855_ATRDS                   0x10200330
+#define   AN8855_ATRD_SEL              GENMASK(1, 0)
+#define AN8855_ATRD0                   0x10200334
+#define   AN8855_ATRD0_FID             GENMASK(28, 25)
+#define   AN8855_ATRD0_VID             GENMASK(21, 10)
+#define   AN8855_ATRD0_IVL             BIT(9)
+#define   AN8855_ATRD0_TYPE            GENMASK(4, 3)
+#define   AN8855_ATRD0_ARP             GENMASK(2, 1)
+#define   AN8855_ATRD0_LIVE            BIT(0)
+#define AN8855_ATRD1                   0x10200338
+#define   AN8855_ATRD1_MAC4            GENMASK(31, 24)
+#define   AN8855_ATRD1_MAC5            GENMASK(23, 16)
+#define   AN8855_ATRD1_AGING           GENMASK(11, 3)
+#define AN8855_ATRD2                   0x1020033c
+#define   AN8855_ATRD2_MAC0            GENMASK(31, 24)
+#define   AN8855_ATRD2_MAC1            GENMASK(23, 16)
+#define   AN8855_ATRD2_MAC2            GENMASK(15, 8)
+#define   AN8855_ATRD2_MAC3            GENMASK(7, 0)
+#define AN8855_ATRD3                   0x10200340
+#define   AN8855_ATRD3_PORTMASK                GENMASK(7, 0)
+
+enum an8855_fdb_type {
+       AN8855_MAC_TB_TY_MAC = 0,
+       AN8855_MAC_TB_TY_DIP = 1,
+       AN8855_MAC_TB_TY_DIP_SIP = 2,
+};
+
+/* Register for vlan table control */
+#define AN8855_VTCR                    0x10200600
+#define   AN8855_VTCR_BUSY             BIT(31)
+#define   AN8855_VTCR_FUNC             GENMASK(15, 12)
+#define   AN8855_VTCR_VID              GENMASK(11, 0)
+
+enum an8855_vlan_cmd {
+       /* Read/Write the specified VID entry from VAWD register based
+        * on VID.
+        */
+       AN8855_VTCR_RD_VID = 0,
+       AN8855_VTCR_WR_VID = 1,
+};
+
+/* Register for setup vlan write data */
+#define AN8855_VAWD0                   0x10200604
+/* VLAN Member Control */
+#define   AN8855_VA0_PORT              GENMASK(31, 26)
+/* Egress Tag Control */
+#define   AN8855_VA0_ETAG              GENMASK(23, 12)
+#define   AN8855_VA0_ETAG_PORT         GENMASK(13, 12)
+#define   AN8855_VA0_ETAG_PORT_SHIFT(port) ((port) * 2)
+#define   AN8855_VA0_ETAG_PORT_MASK(port) (AN8855_VA0_ETAG_PORT << \
+                                               AN8855_VA0_ETAG_PORT_SHIFT(port))
+#define   AN8855_VA0_ETAG_PORT_VAL(port, val) (FIELD_PREP(AN8855_VA0_ETAG_PORT, (val)) << \
+                                               AN8855_VA0_ETAG_PORT_SHIFT(port))
+#define   AN8855_VA0_EG_CON            BIT(11)
+#define   AN8855_VA0_VTAG_EN           BIT(10) /* Per VLAN Egress Tag Control */
+#define   AN8855_VA0_IVL_MAC           BIT(5) /* Independent VLAN Learning */
+#define          AN8855_VA0_FID                GENMASK(4, 1)
+#define   AN8855_VA0_VLAN_VALID                BIT(0) /* VLAN Entry Valid */
+#define AN8855_VAWD1                   0x10200608
+#define   AN8855_VA1_PORT_STAG         BIT(1)
+
+enum an8855_fid {
+       AN8855_FID_STANDALONE = 0,
+       AN8855_FID_BRIDGED = 1,
+};
+
+/* Same register field of VAWD0 */
+#define AN8855_VARD0                   0x10200618
+
+enum an8855_vlan_egress_attr {
+       AN8855_VLAN_EGRESS_UNTAG = 0,
+       AN8855_VLAN_EGRESS_TAG = 2,
+       AN8855_VLAN_EGRESS_STACK = 3,
+};
+
+/* Register for port STP state control */
+#define AN8855_SSP_P(x)                        (0x10208000 + ((x) * 0x200))
+/* Up to 16 FID supported, each with the same mask */
+#define          AN8855_FID_PST                GENMASK(1, 0)
+#define   AN8855_FID_PST_SHIFT(fid)    (2 * (fid))
+#define   AN8855_FID_PST_MASK(fid)     (AN8855_FID_PST << \
+                                               AN8855_FID_PST_SHIFT(fid))
+#define   AN8855_FID_PST_VAL(fid, val) (FIELD_PREP(AN8855_FID_PST, (val)) << \
+                                               AN8855_FID_PST_SHIFT(fid))
+
+enum an8855_stp_state {
+       AN8855_STP_DISABLED = 0,
+       AN8855_STP_BLOCKING = 1,
+       AN8855_STP_LISTENING = AN8855_STP_BLOCKING,
+       AN8855_STP_LEARNING = 2,
+       AN8855_STP_FORWARDING = 3
+};
+
+/* Register for port control */
+#define AN8855_PCR_P(x)                        (0x10208004 + ((x) * 0x200))
+#define   AN8855_EG_TAG                        GENMASK(29, 28)
+#define   AN8855_PORT_PRI              GENMASK(26, 24)
+#define   AN8855_PORT_TX_MIR           BIT(20)
+#define   AN8855_PORT_RX_MIR           BIT(16)
+#define   AN8855_PORT_VLAN             GENMASK(1, 0)
+
+enum an8855_port_mode {
+       /* Port Matrix Mode: Frames are forwarded by the PCR_MATRIX members. */
+       AN8855_PORT_MATRIX_MODE = 0,
+
+       /* Fallback Mode: Forward received frames with ingress ports that do
+        * not belong to the VLAN member. Frames whose VID is not listed on
+        * the VLAN table are forwarded by the PCR_MATRIX members.
+        */
+       AN8855_PORT_FALLBACK_MODE = 1,
+
+       /* Check Mode: Forward received frames whose ingress do not
+        * belong to the VLAN member. Discard frames if VID ismiddes on the
+        * VLAN table.
+        */
+       AN8855_PORT_CHECK_MODE = 2,
+
+       /* Security Mode: Discard any frame due to ingress membership
+        * violation or VID missed on the VLAN table.
+        */
+       AN8855_PORT_SECURITY_MODE = 3,
+};
+
+/* Register for port security control */
+#define AN8855_PSC_P(x)                        (0x1020800c + ((x) * 0x200))
+#define   AN8855_SA_DIS                        BIT(4)
+
+/* Register for port vlan control */
+#define AN8855_PVC_P(x)                        (0x10208010 + ((x) * 0x200))
+#define   AN8855_PORT_SPEC_REPLACE_MODE        BIT(11)
+#define   AN8855_PVC_EG_TAG            GENMASK(10, 8)
+#define   AN8855_VLAN_ATTR             GENMASK(7, 6)
+#define   AN8855_PORT_SPEC_TAG         BIT(5)
+#define   AN8855_ACC_FRM               GENMASK(1, 0)
+
+enum an8855_vlan_port_eg_tag {
+       AN8855_VLAN_EG_DISABLED = 0,
+       AN8855_VLAN_EG_CONSISTENT = 1,
+       AN8855_VLAN_EG_UNTAGGED = 4,
+       AN8855_VLAN_EG_SWAP = 5,
+       AN8855_VLAN_EG_TAGGED = 6,
+       AN8855_VLAN_EG_STACK = 7,
+};
+
+enum an8855_vlan_port_attr {
+       AN8855_VLAN_USER = 0,
+       AN8855_VLAN_STACK = 1,
+       AN8855_VLAN_TRANSPARENT = 3,
+};
+
+enum an8855_vlan_port_acc_frm {
+       AN8855_VLAN_ACC_ALL = 0,
+       AN8855_VLAN_ACC_TAGGED = 1,
+       AN8855_VLAN_ACC_UNTAGGED = 2,
+};
+
+#define AN8855_PPBV1_P(x)              (0x10208014 + ((x) * 0x200))
+#define   AN8855_PPBV_G0_PORT_VID      GENMASK(11, 0)
+
+#define AN8855_PORTMATRIX_P(x)         (0x10208044 + ((x) * 0x200))
+#define   AN8855_PORTMATRIX            GENMASK(5, 0)
+/* Port matrix without the CPU port that should never be removed */
+#define   AN8855_USER_PORTMATRIX       GENMASK(4, 0)
+
+/* Register for port PVID */
+#define AN8855_PVID_P(x)               (0x10208048 + ((x) * 0x200))
+#define   AN8855_G0_PORT_VID           GENMASK(11, 0)
+
+/* Register for port MAC control register */
+#define AN8855_PMCR_P(x)               (0x10210000 + ((x) * 0x200))
+#define   AN8855_PMCR_FORCE_MODE       BIT(31)
+#define   AN8855_PMCR_FORCE_SPEED      GENMASK(30, 28)
+#define   AN8855_PMCR_FORCE_SPEED_5000 FIELD_PREP_CONST(AN8855_PMCR_FORCE_SPEED, 0x4)
+#define   AN8855_PMCR_FORCE_SPEED_2500 FIELD_PREP_CONST(AN8855_PMCR_FORCE_SPEED, 0x3)
+#define   AN8855_PMCR_FORCE_SPEED_1000 FIELD_PREP_CONST(AN8855_PMCR_FORCE_SPEED, 0x2)
+#define   AN8855_PMCR_FORCE_SPEED_100  FIELD_PREP_CONST(AN8855_PMCR_FORCE_SPEED, 0x1)
+#define   AN8855_PMCR_FORCE_SPEED_10   FIELD_PREP_CONST(AN8855_PMCR_FORCE_SPEED, 0x1)
+#define   AN8855_PMCR_FORCE_FDX                BIT(25)
+#define   AN8855_PMCR_FORCE_LNK                BIT(24)
+#define   AN8855_PMCR_IFG_XMIT         GENMASK(21, 20)
+#define   AN8855_PMCR_EXT_PHY          BIT(19)
+#define   AN8855_PMCR_MAC_MODE         BIT(18)
+#define   AN8855_PMCR_TX_EN            BIT(16)
+#define   AN8855_PMCR_RX_EN            BIT(15)
+#define   AN8855_PMCR_BACKOFF_EN       BIT(12)
+#define   AN8855_PMCR_BACKPR_EN                BIT(11)
+#define   AN8855_PMCR_FORCE_EEE5G      BIT(9)
+#define   AN8855_PMCR_FORCE_EEE2P5G    BIT(8)
+#define   AN8855_PMCR_FORCE_EEE1G      BIT(7)
+#define   AN8855_PMCR_FORCE_EEE100     BIT(6)
+#define   AN8855_PMCR_TX_FC_EN         BIT(5)
+#define   AN8855_PMCR_RX_FC_EN         BIT(4)
+
+#define AN8855_PMSR_P(x)               (0x10210010 + (x) * 0x200)
+#define   AN8855_PMSR_SPEED            GENMASK(30, 28)
+#define   AN8855_PMSR_SPEED_5000       FIELD_PREP_CONST(AN8855_PMSR_SPEED, 0x4)
+#define   AN8855_PMSR_SPEED_2500       FIELD_PREP_CONST(AN8855_PMSR_SPEED, 0x3)
+#define   AN8855_PMSR_SPEED_1000       FIELD_PREP_CONST(AN8855_PMSR_SPEED, 0x2)
+#define   AN8855_PMSR_SPEED_100                FIELD_PREP_CONST(AN8855_PMSR_SPEED, 0x1)
+#define   AN8855_PMSR_SPEED_10         FIELD_PREP_CONST(AN8855_PMSR_SPEED, 0x0)
+#define   AN8855_PMSR_DPX              BIT(25)
+#define   AN8855_PMSR_LNK              BIT(24)
+#define   AN8855_PMSR_EEE1G            BIT(7)
+#define   AN8855_PMSR_EEE100M          BIT(6)
+#define   AN8855_PMSR_RX_FC            BIT(5)
+#define   AN8855_PMSR_TX_FC            BIT(4)
+
+#define AN8855_PMEEECR_P(x)            (0x10210004 + (x) * 0x200)
+#define   AN8855_LPI_MODE_EN           BIT(31)
+#define   AN8855_WAKEUP_TIME_2500      GENMASK(23, 16)
+#define   AN8855_WAKEUP_TIME_1000      GENMASK(15, 8)
+#define   AN8855_WAKEUP_TIME_100       GENMASK(7, 0)
+#define AN8855_PMEEECR2_P(x)           (0x10210008 + (x) * 0x200)
+#define   AN8855_WAKEUP_TIME_5000      GENMASK(7, 0)
+
+#define AN8855_GMACCR                  0x10213e00
+#define   AN8855_MAX_RX_JUMBO          GENMASK(7, 4)
+/* 2K for 0x0, 0x1, 0x2 */
+#define   AN8855_MAX_RX_JUMBO_2K       FIELD_PREP_CONST(AN8855_MAX_RX_JUMBO, 0x0)
+#define   AN8855_MAX_RX_JUMBO_3K       FIELD_PREP_CONST(AN8855_MAX_RX_JUMBO, 0x3)
+#define   AN8855_MAX_RX_JUMBO_4K       FIELD_PREP_CONST(AN8855_MAX_RX_JUMBO, 0x4)
+#define   AN8855_MAX_RX_JUMBO_5K       FIELD_PREP_CONST(AN8855_MAX_RX_JUMBO, 0x5)
+#define   AN8855_MAX_RX_JUMBO_6K       FIELD_PREP_CONST(AN8855_MAX_RX_JUMBO, 0x6)
+#define   AN8855_MAX_RX_JUMBO_7K       FIELD_PREP_CONST(AN8855_MAX_RX_JUMBO, 0x7)
+#define   AN8855_MAX_RX_JUMBO_8K       FIELD_PREP_CONST(AN8855_MAX_RX_JUMBO, 0x8)
+#define   AN8855_MAX_RX_JUMBO_9K       FIELD_PREP_CONST(AN8855_MAX_RX_JUMBO, 0x9)
+#define   AN8855_MAX_RX_JUMBO_12K      FIELD_PREP_CONST(AN8855_MAX_RX_JUMBO, 0xa)
+#define   AN8855_MAX_RX_JUMBO_15K      FIELD_PREP_CONST(AN8855_MAX_RX_JUMBO, 0xb)
+#define   AN8855_MAX_RX_JUMBO_16K      FIELD_PREP_CONST(AN8855_MAX_RX_JUMBO, 0xc)
+#define   AN8855_MAX_RX_PKT_LEN                GENMASK(1, 0)
+#define   AN8855_MAX_RX_PKT_1518_1522  FIELD_PREP_CONST(AN8855_MAX_RX_PKT_LEN, 0x0)
+#define   AN8855_MAX_RX_PKT_1536       FIELD_PREP_CONST(AN8855_MAX_RX_PKT_LEN, 0x1)
+#define   AN8855_MAX_RX_PKT_1552       FIELD_PREP_CONST(AN8855_MAX_RX_PKT_LEN, 0x2)
+#define   AN8855_MAX_RX_PKT_JUMBO      FIELD_PREP_CONST(AN8855_MAX_RX_PKT_LEN, 0x3)
+
+#define AN8855_CKGCR                   0x10213e1c
+#define   AN8855_LPI_TXIDLE_THD_MASK   GENMASK(31, 14)
+#define   AN8855_CKG_LNKDN_PORT_STOP   BIT(1)
+#define   AN8855_CKG_LNKDN_GLB_STOP    BIT(0)
+
+/* Register for MIB */
+#define AN8855_PORT_MIB_COUNTER(x)     (0x10214000 + (x) * 0x200)
+/* Each define is an offset of AN8855_PORT_MIB_COUNTER */
+#define   AN8855_PORT_MIB_TX_DROP      0x00
+#define   AN8855_PORT_MIB_TX_CRC_ERR   0x04
+#define   AN8855_PORT_MIB_TX_UNICAST   0x08
+#define   AN8855_PORT_MIB_TX_MULTICAST 0x0c
+#define   AN8855_PORT_MIB_TX_BROADCAST 0x10
+#define   AN8855_PORT_MIB_TX_COLLISION 0x14
+#define   AN8855_PORT_MIB_TX_SINGLE_COLLISION 0x18
+#define   AN8855_PORT_MIB_TX_MULTIPLE_COLLISION 0x1c
+#define   AN8855_PORT_MIB_TX_DEFERRED  0x20
+#define   AN8855_PORT_MIB_TX_LATE_COLLISION 0x24
+#define   AN8855_PORT_MIB_TX_EXCESSIVE_COLLISION 0x28
+#define   AN8855_PORT_MIB_TX_PAUSE     0x2c
+#define   AN8855_PORT_MIB_TX_PKT_SZ_64 0x30
+#define   AN8855_PORT_MIB_TX_PKT_SZ_65_TO_127 0x34
+#define   AN8855_PORT_MIB_TX_PKT_SZ_128_TO_255 0x38
+#define   AN8855_PORT_MIB_TX_PKT_SZ_256_TO_511 0x3
+#define   AN8855_PORT_MIB_TX_PKT_SZ_512_TO_1023 0x40
+#define   AN8855_PORT_MIB_TX_PKT_SZ_1024_TO_1518 0x44
+#define   AN8855_PORT_MIB_TX_PKT_SZ_1519_TO_MAX 0x48
+#define   AN8855_PORT_MIB_TX_BYTES     0x4c /* 64 bytes */
+#define   AN8855_PORT_MIB_TX_OVERSIZE_DROP 0x54
+#define   AN8855_PORT_MIB_TX_BAD_PKT_BYTES 0x58 /* 64 bytes */
+#define   AN8855_PORT_MIB_RX_DROP      0x80
+#define   AN8855_PORT_MIB_RX_FILTERING 0x84
+#define   AN8855_PORT_MIB_RX_UNICAST   0x88
+#define   AN8855_PORT_MIB_RX_MULTICAST 0x8c
+#define   AN8855_PORT_MIB_RX_BROADCAST 0x90
+#define   AN8855_PORT_MIB_RX_ALIGN_ERR 0x94
+#define   AN8855_PORT_MIB_RX_CRC_ERR   0x98
+#define   AN8855_PORT_MIB_RX_UNDER_SIZE_ERR 0x9c
+#define   AN8855_PORT_MIB_RX_FRAG_ERR  0xa0
+#define   AN8855_PORT_MIB_RX_OVER_SZ_ERR 0xa4
+#define   AN8855_PORT_MIB_RX_JABBER_ERR        0xa8
+#define   AN8855_PORT_MIB_RX_PAUSE     0xac
+#define   AN8855_PORT_MIB_RX_PKT_SZ_64 0xb0
+#define   AN8855_PORT_MIB_RX_PKT_SZ_65_TO_127 0xb4
+#define   AN8855_PORT_MIB_RX_PKT_SZ_128_TO_255 0xb8
+#define   AN8855_PORT_MIB_RX_PKT_SZ_256_TO_511 0xbc
+#define   AN8855_PORT_MIB_RX_PKT_SZ_512_TO_1023 0xc0
+#define   AN8855_PORT_MIB_RX_PKT_SZ_1024_TO_1518 0xc4
+#define   AN8855_PORT_MIB_RX_PKT_SZ_1519_TO_MAX 0xc8
+#define   AN8855_PORT_MIB_RX_BYTES     0xcc /* 64 bytes */
+#define   AN8855_PORT_MIB_RX_CTRL_DROP 0xd4
+#define   AN8855_PORT_MIB_RX_INGRESS_DROP 0xd8
+#define   AN8855_PORT_MIB_RX_ARL_DROP  0xdc
+#define   AN8855_PORT_MIB_FLOW_CONTROL_DROP 0xe0
+#define   AN8855_PORT_MIB_WRED_DROP    0xe4
+#define   AN8855_PORT_MIB_MIRROR_DROP  0xe8
+#define   AN8855_PORT_MIB_RX_BAD_PKT_BYTES 0xec /* 64 bytes */
+#define   AN8855_PORT_MIB_RXS_FLOW_SAMPLING_PKT_DROP 0xf4
+#define   AN8855_PORT_MIB_RXS_FLOW_TOTAL_PKT_DROP 0xf8
+#define   AN8855_PORT_MIB_PORT_CONTROL_DROP 0xfc
+#define AN8855_MIB_CCR                 0x10213e30
+#define   AN8855_CCR_MIB_ENABLE                BIT(31)
+#define   AN8855_CCR_RX_OCT_CNT_GOOD   BIT(7)
+#define   AN8855_CCR_RX_OCT_CNT_BAD    BIT(6)
+#define   AN8855_CCR_TX_OCT_CNT_GOOD   BIT(5)
+#define   AN8855_CCR_TX_OCT_CNT_BAD    BIT(4)
+#define   AN8855_CCR_RX_OCT_CNT_GOOD_2 BIT(3)
+#define   AN8855_CCR_RX_OCT_CNT_BAD_2  BIT(2)
+#define   AN8855_CCR_TX_OCT_CNT_GOOD_2 BIT(1)
+#define   AN8855_CCR_TX_OCT_CNT_BAD_2  BIT(0)
+#define   AN8855_CCR_MIB_ACTIVATE      (AN8855_CCR_MIB_ENABLE | \
+                                        AN8855_CCR_RX_OCT_CNT_GOOD | \
+                                        AN8855_CCR_RX_OCT_CNT_BAD | \
+                                        AN8855_CCR_TX_OCT_CNT_GOOD | \
+                                        AN8855_CCR_TX_OCT_CNT_BAD | \
+                                        AN8855_CCR_RX_OCT_CNT_BAD_2 | \
+                                        AN8855_CCR_TX_OCT_CNT_BAD_2)
+#define AN8855_MIB_CLR                 0x10213e34
+#define   AN8855_MIB_PORT6_CLR         BIT(6)
+#define   AN8855_MIB_PORT5_CLR         BIT(5)
+#define   AN8855_MIB_PORT4_CLR         BIT(4)
+#define   AN8855_MIB_PORT3_CLR         BIT(3)
+#define   AN8855_MIB_PORT2_CLR         BIT(2)
+#define   AN8855_MIB_PORT1_CLR         BIT(1)
+#define   AN8855_MIB_PORT0_CLR         BIT(0)
+
+/* HSGMII/SGMII Configuration register */
+/*     AN8855_HSGMII_AN_CSR_BASE       0x10220000 */
+#define AN8855_SGMII_REG_AN0           0x10220000
+/*        AN8855_SGMII_AN_ENABLE       BMCR_ANENABLE */
+/*        AN8855_SGMII_AN_RESTART      BMCR_ANRESTART */
+#define AN8855_SGMII_REG_AN_13         0x10220034
+#define   AN8855_SGMII_REMOTE_FAULT_DIS        BIT(8)
+#define   AN8855_SGMII_IF_MODE         GENMASK(5, 0)
+#define AN8855_SGMII_REG_AN_FORCE_CL37 0x10220060
+#define   AN8855_RG_FORCE_AN_DONE      BIT(0)
+
+/*     AN8855_HSGMII_CSR_PCS_BASE      0x10220000 */
+#define AN8855_RG_HSGMII_PCS_CTROL_1   0x10220a00
+#define   AN8855_RG_TBI_10B_MODE       BIT(30)
+#define AN8855_RG_AN_SGMII_MODE_FORCE  0x10220a24
+#define   AN8855_RG_FORCE_CUR_SGMII_MODE GENMASK(5, 4)
+#define   AN8855_RG_FORCE_CUR_SGMII_SEL        BIT(0)
+
+/*     AN8855_MULTI_SGMII_CSR_BASE     0x10224000 */
+#define AN8855_SGMII_STS_CTRL_0                0x10224018
+#define   AN8855_RG_LINK_MODE_P0       GENMASK(5, 4)
+#define   AN8855_RG_LINK_MODE_P0_SPEED_2500 FIELD_PREP_CONST(AN8855_RG_LINK_MODE_P0, 0x3)
+#define   AN8855_RG_LINK_MODE_P0_SPEED_1000 FIELD_PREP_CONST(AN8855_RG_LINK_MODE_P0, 0x2)
+#define   AN8855_RG_LINK_MODE_P0_SPEED_100 FIELD_PREP_CONST(AN8855_RG_LINK_MODE_P0, 0x1)
+#define   AN8855_RG_LINK_MODE_P0_SPEED_10 FIELD_PREP_CONST(AN8855_RG_LINK_MODE_P0, 0x0)
+#define   AN8855_RG_FORCE_SPD_MODE_P0  BIT(2)
+#define AN8855_MSG_RX_CTRL_0           0x10224100
+#define AN8855_MSG_RX_LIK_STS_0                0x10224514
+#define   AN8855_RG_DPX_STS_P3         BIT(24)
+#define   AN8855_RG_DPX_STS_P2         BIT(16)
+#define   AN8855_RG_EEE1G_STS_P1       BIT(12)
+#define   AN8855_RG_DPX_STS_P1         BIT(8)
+#define   AN8855_RG_TXFC_STS_P0                BIT(2)
+#define   AN8855_RG_RXFC_STS_P0                BIT(1)
+#define   AN8855_RG_DPX_STS_P0         BIT(0)
+#define AN8855_MSG_RX_LIK_STS_2                0x1022451c
+#define   AN8855_RG_RXFC_AN_BYPASS_P3  BIT(11)
+#define   AN8855_RG_RXFC_AN_BYPASS_P2  BIT(10)
+#define   AN8855_RG_RXFC_AN_BYPASS_P1  BIT(9)
+#define   AN8855_RG_TXFC_AN_BYPASS_P3  BIT(7)
+#define   AN8855_RG_TXFC_AN_BYPASS_P2  BIT(6)
+#define   AN8855_RG_TXFC_AN_BYPASS_P1  BIT(5)
+#define   AN8855_RG_DPX_AN_BYPASS_P3   BIT(3)
+#define   AN8855_RG_DPX_AN_BYPASS_P2   BIT(2)
+#define   AN8855_RG_DPX_AN_BYPASS_P1   BIT(1)
+#define   AN8855_RG_DPX_AN_BYPASS_P0   BIT(0)
+#define AN8855_PHY_RX_FORCE_CTRL_0     0x10224520
+#define   AN8855_RG_FORCE_TXC_SEL      BIT(4)
+
+/*     AN8855_XFI_CSR_PCS_BASE         0x10225000 */
+#define AN8855_RG_USXGMII_AN_CONTROL_0 0x10225bf8
+
+/*     AN8855_MULTI_PHY_RA_CSR_BASE    0x10226000 */
+#define AN8855_RG_RATE_ADAPT_CTRL_0    0x10226000
+#define   AN8855_RG_RATE_ADAPT_RX_BYPASS BIT(27)
+#define   AN8855_RG_RATE_ADAPT_TX_BYPASS BIT(26)
+#define   AN8855_RG_RATE_ADAPT_RX_EN   BIT(4)
+#define   AN8855_RG_RATE_ADAPT_TX_EN   BIT(0)
+#define AN8855_RATE_ADP_P0_CTRL_0      0x10226100
+#define   AN8855_RG_P0_DIS_MII_MODE    BIT(31)
+#define   AN8855_RG_P0_MII_MODE                BIT(28)
+#define   AN8855_RG_P0_MII_RA_RX_EN    BIT(3)
+#define   AN8855_RG_P0_MII_RA_TX_EN    BIT(2)
+#define   AN8855_RG_P0_MII_RA_RX_MODE  BIT(1)
+#define   AN8855_RG_P0_MII_RA_TX_MODE  BIT(0)
+#define AN8855_MII_RA_AN_ENABLE                0x10226300
+#define   AN8855_RG_P0_RA_AN_EN                BIT(0)
+
+/*     AN8855_QP_DIG_CSR_BASE          0x1022a000 */
+#define AN8855_QP_CK_RST_CTRL_4                0x1022a310
+#define AN8855_QP_DIG_MODE_CTRL_0      0x1022a324
+#define   AN8855_RG_SGMII_MODE         GENMASK(5, 4)
+#define   AN8855_RG_SGMII_AN_EN                BIT(0)
+#define AN8855_QP_DIG_MODE_CTRL_1      0x1022a330
+#define   AN8855_RG_TPHY_SPEED         GENMASK(3, 2)
+
+/*     AN8855_SERDES_WRAPPER_BASE      0x1022c000 */
+#define AN8855_USGMII_CTRL_0           0x1022c000
+
+/*     AN8855_QP_PMA_TOP_BASE          0x1022e000 */
+#define AN8855_PON_RXFEDIG_CTRL_0      0x1022e100
+#define   AN8855_RG_QP_EQ_RX500M_CK_SEL        BIT(12)
+#define AN8855_PON_RXFEDIG_CTRL_9      0x1022e124
+#define   AN8855_RG_QP_EQ_LEQOSC_DLYCNT        GENMASK(2, 0)
+
+#define AN8855_SS_LCPLL_PWCTL_SETTING_2        0x1022e208
+#define   AN8855_RG_NCPO_ANA_MSB       GENMASK(17, 16)
+#define AN8855_SS_LCPLL_TDC_FLT_2      0x1022e230
+#define   AN8855_RG_LCPLL_NCPO_VALUE   GENMASK(30, 0)
+#define AN8855_SS_LCPLL_TDC_FLT_5      0x1022e23c
+#define   AN8855_RG_LCPLL_NCPO_CHG     BIT(24)
+#define AN8855_SS_LCPLL_TDC_PCW_1      0x1022e248
+#define  AN8855_RG_LCPLL_PON_HRDDS_PCW_NCPO_GPON GENMASK(30, 0)
+#define AN8855_INTF_CTRL_8             0x1022e320
+#define AN8855_INTF_CTRL_9             0x1022e324
+#define AN8855_INTF_CTRL_10            0x1022e328
+#define   AN8855_RG_DA_QP_TX_FIR_C2_SEL        BIT(29)
+#define   AN8855_RG_DA_QP_TX_FIR_C2_FORCE GENMASK(28, 24)
+#define   AN8855_RG_DA_QP_TX_FIR_C1_SEL        BIT(21)
+#define   AN8855_RG_DA_QP_TX_FIR_C1_FORCE GENMASK(20, 16)
+#define AN8855_INTF_CTRL_11            0x1022e32c
+#define   AN8855_RG_DA_QP_TX_FIR_C0B_SEL BIT(6)
+#define   AN8855_RG_DA_QP_TX_FIR_C0B_FORCE GENMASK(5, 0)
+#define AN8855_PLL_CTRL_0              0x1022e400
+#define   AN8855_RG_PHYA_AUTO_INIT     BIT(0)
+#define AN8855_PLL_CTRL_2              0x1022e408
+#define   AN8855_RG_DA_QP_PLL_SDM_IFM_INTF BIT(30)
+#define   AN8855_RG_DA_QP_PLL_RICO_SEL_INTF BIT(29)
+#define   AN8855_RG_DA_QP_PLL_POSTDIV_EN_INTF BIT(28)
+#define   AN8855_RG_DA_QP_PLL_PHY_CK_EN_INTF BIT(27)
+#define   AN8855_RG_DA_QP_PLL_PFD_OFFSET_EN_INTRF BIT(26)
+#define   AN8855_RG_DA_QP_PLL_PFD_OFFSET_INTF GENMASK(25, 24)
+#define   AN8855_RG_DA_QP_PLL_PCK_SEL_INTF BIT(22)
+#define   AN8855_RG_DA_QP_PLL_KBAND_PREDIV_INTF GENMASK(21, 20)
+#define   AN8855_RG_DA_QP_PLL_IR_INTF  GENMASK(19, 16)
+#define   AN8855_RG_DA_QP_PLL_ICOIQ_EN_INTF BIT(14)
+#define   AN8855_RG_DA_QP_PLL_FBKSEL_INTF GENMASK(13, 12)
+#define   AN8855_RG_DA_QP_PLL_BR_INTF  GENMASK(10, 8)
+#define   AN8855_RG_DA_QP_PLL_BPD_INTF GENMASK(7, 6)
+#define   AN8855_RG_DA_QP_PLL_BPA_INTF GENMASK(4, 2)
+#define   AN8855_RG_DA_QP_PLL_BC_INTF  GENMASK(1, 0)
+#define AN8855_PLL_CTRL_3              0x1022e40c
+#define   AN8855_RG_DA_QP_PLL_SSC_PERIOD_INTF GENMASK(31, 16)
+#define   AN8855_RG_DA_QP_PLL_SSC_DELTA_INTF GENMASK(15, 0)
+#define AN8855_PLL_CTRL_4              0x1022e410
+#define   AN8855_RG_DA_QP_PLL_SDM_HREN_INTF GENMASK(4, 3)
+#define   AN8855_RG_DA_QP_PLL_ICOLP_EN_INTF BIT(2)
+#define   AN8855_RG_DA_QP_PLL_SSC_DIR_DLY_INTF GENMASK(1, 0)
+#define AN8855_PLL_CK_CTRL_0           0x1022e414
+#define   AN8855_RG_DA_QP_PLL_TDC_TXCK_SEL_INTF BIT(9)
+#define   AN8855_RG_DA_QP_PLL_SDM_DI_EN_INTF BIT(8)
+#define AN8855_RX_DLY_0                        0x1022e614
+#define   AN8855_RG_QP_RX_SAOSC_EN_H_DLY GENMASK(13, 8)
+#define   AN8855_RG_QP_RX_PI_CAL_EN_H_DLY GENMASK(7, 0)
+#define AN8855_RX_CTRL_2               0x1022e630
+#define   AN8855_RG_QP_RX_EQ_EN_H_DLY  GENMASK(28, 16)
+#define AN8855_RX_CTRL_5               0x1022e63c
+#define   AN8855_RG_FREDET_CHK_CYCLE   GENMASK(29, 10)
+#define AN8855_RX_CTRL_6               0x1022e640
+#define   AN8855_RG_FREDET_GOLDEN_CYCLE        GENMASK(19, 0)
+#define AN8855_RX_CTRL_7               0x1022e644
+#define   AN8855_RG_FREDET_TOLERATE_CYCLE GENMASK(19, 0)
+#define AN8855_RX_CTRL_8               0x1022e648
+#define   AN8855_RG_DA_QP_SAOSC_DONE_TIME GENMASK(27, 16)
+#define   AN8855_RG_DA_QP_LEQOS_EN_TIME        GENMASK(14, 0)
+#define AN8855_RX_CTRL_26              0x1022e690
+#define   AN8855_RG_QP_EQ_RETRAIN_ONLY_EN BIT(26)
+#define   AN8855_RG_LINK_NE_EN         BIT(24)
+#define   AN8855_RG_LINK_ERRO_EN       BIT(23)
+#define AN8855_RX_CTRL_42              0x1022e6d0
+#define   AN8855_RG_QP_EQ_EN_DLY       GENMASK(12, 0)
+
+/*     AN8855_QP_ANA_CSR_BASE          0x1022f000 */
+#define AN8855_RG_QP_RX_DAC_EN         0x1022f000
+#define   AN8855_RG_QP_SIGDET_HF       GENMASK(17, 16)
+#define AN8855_RG_QP_RXAFE_RESERVE     0x1022f004
+#define   AN8855_RG_QP_CDR_PD_10B_EN   BIT(11)
+#define AN8855_RG_QP_CDR_LPF_BOT_LIM   0x1022f008
+#define   AN8855_RG_QP_CDR_LPF_KP_GAIN GENMASK(26, 24)
+#define   AN8855_RG_QP_CDR_LPF_KI_GAIN GENMASK(22, 20)
+#define AN8855_RG_QP_CDR_LPF_MJV_LIM   0x1022f00c
+#define   AN8855_RG_QP_CDR_LPF_RATIO   GENMASK(5, 4)
+#define AN8855_RG_QP_CDR_LPF_SETVALUE  0x1022f014
+#define   AN8855_RG_QP_CDR_PR_BUF_IN_SR        GENMASK(31, 29)
+#define   AN8855_RG_QP_CDR_PR_BETA_SEL GENMASK(28, 25)
+#define AN8855_RG_QP_CDR_PR_CKREF_DIV1 0x1022f018
+#define   AN8855_RG_QP_CDR_PR_KBAND_DIV        GENMASK(26, 24)
+#define   AN8855_RG_QP_CDR_PR_DAC_BAND GENMASK(12, 8)
+#define AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE 0x1022f01c
+#define   AN8855_RG_QP_CDR_PR_XFICK_EN BIT(30)
+#define   AN8855_RG_QP_CDR_PR_KBAND_PCIE_MODE BIT(6)
+#define   AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE_MASK GENMASK(5, 0)
+#define AN8855_RG_QP_CDR_FORCE_IBANDLPF_R_OFF 0x1022f020
+#define   AN8855_RG_QP_CDR_PHYCK_SEL   GENMASK(17, 16)
+#define   AN8855_RG_QP_CDR_PHYCK_RSTB  BIT(13)
+#define   AN8855_RG_QP_CDR_PHYCK_DIV   GENMASK(12, 6)
+#define AN8855_RG_QP_TX_MODE           0x1022f028
+#define   AN8855_RG_QP_TX_RESERVE      GENMASK(31, 16)
+#define   AN8855_RG_QP_TX_MODE_16B_EN  BIT(0)
+#define AN8855_RG_QP_PLL_IPLL_DIG_PWR_SEL 0x1022f03c
+#define AN8855_RG_QP_PLL_SDM_ORD       0x1022f040
+#define   AN8855_RG_QP_PLL_SSC_PHASE_INI BIT(4)
+#define   AN8855_RG_QP_PLL_SSC_TRI_EN  BIT(3)
+
+/*     AN8855_ETHER_SYS_BASE           0x1028c800 */
+#define AN8855_RG_GPHY_AFE_PWD         0x1028c840
+#define AN8855_RG_GPHY_SMI_ADDR                0x1028c848
+
+#define MIB_DESC(_s, _o, _n)   \
+       {                       \
+               .size = (_s),   \
+               .offset = (_o), \
+               .name = (_n),   \
+       }
+
+struct an8855_mib_desc {
+       unsigned int size;
+       unsigned int offset;
+       const char *name;
+};
+
+struct an8855_fdb {
+       u16 vid;
+       u8 port_mask;
+       u16 aging;
+       u8 mac[6];
+       bool noarp;
+       u8 live;
+       u8 type;
+       u8 fid;
+       u8 ivl;
+};
+
+struct an8855_priv {
+       struct device *dev;
+       struct dsa_switch *ds;
+       struct regmap *regmap;
+       struct gpio_desc *reset_gpio;
+       /* Protect ATU or VLAN table access */
+       struct mutex reg_mutex;
+
+       struct phylink_pcs pcs;
+
+       u8 mirror_rx;
+       u8 mirror_tx;
+       u8 port_isolated_map;
+
+       bool phy_require_calib;
+};
+
+#endif /* __AN8855_H */
diff --git a/target/linux/mediatek/files-6.6/drivers/net/mdio/mdio-an8855.c b/target/linux/mediatek/files-6.6/drivers/net/mdio/mdio-an8855.c
new file mode 100644 (file)
index 0000000..5feba72
--- /dev/null
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * MDIO passthrough driver for Airoha AN8855 Switch
+ */
+
+#include <linux/mfd/airoha-an8855-mfd.h>
+#include <linux/module.h>
+#include <linux/of_mdio.h>
+#include <linux/platform_device.h>
+
+static int an855_phy_restore_page(struct an8855_mfd_priv *priv,
+                                 int phy) __must_hold(&priv->bus->mdio_lock)
+{
+       /* Check PHY page only for addr shared with switch */
+       if (phy != priv->switch_addr)
+               return 0;
+
+       /* Don't restore page if it's not set to switch page */
+       if (priv->current_page != FIELD_GET(AN8855_PHY_PAGE,
+                                           AN8855_PHY_PAGE_EXTENDED_4))
+               return 0;
+
+       /* Restore page to 0, PHY might change page right after but that
+        * will be ignored as it won't be a switch page.
+        */
+       return an8855_mii_set_page(priv, phy, AN8855_PHY_PAGE_STANDARD);
+}
+
+static int an8855_phy_read(struct mii_bus *bus, int phy, int regnum)
+{
+       struct an8855_mfd_priv *priv = bus->priv;
+       struct mii_bus *real_bus = priv->bus;
+       int ret;
+
+       mutex_lock_nested(&real_bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+       ret = an855_phy_restore_page(priv, phy);
+       if (ret)
+               goto exit;
+
+       ret = __mdiobus_read(real_bus, phy, regnum);
+exit:
+       mutex_unlock(&real_bus->mdio_lock);
+
+       return ret;
+}
+
+static int an8855_phy_write(struct mii_bus *bus, int phy, int regnum, u16 val)
+{
+       struct an8855_mfd_priv *priv = bus->priv;
+       struct mii_bus *real_bus = priv->bus;
+       int ret;
+
+       mutex_lock_nested(&real_bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+       ret = an855_phy_restore_page(priv, phy);
+       if (ret)
+               goto exit;
+
+       ret = __mdiobus_write(real_bus, phy, regnum, val);
+exit:
+       mutex_unlock(&real_bus->mdio_lock);
+
+       return ret;
+}
+
+static int an8855_mdio_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct an8855_mfd_priv *priv;
+       struct mii_bus *bus;
+       int ret;
+
+       /* Get priv of MFD */
+       priv = dev_get_drvdata(dev->parent);
+
+       bus = devm_mdiobus_alloc(dev);
+       if (!bus)
+               return -ENOMEM;
+
+       bus->priv = priv;
+       bus->name = KBUILD_MODNAME "-mii";
+       snprintf(bus->id, MII_BUS_ID_SIZE, KBUILD_MODNAME "-%d",
+                priv->switch_addr);
+       bus->parent = dev;
+       bus->read = an8855_phy_read;
+       bus->write = an8855_phy_write;
+
+       ret = devm_of_mdiobus_register(dev, bus, dev->of_node);
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to register MDIO bus\n");
+
+       return ret;
+}
+
+static const struct of_device_id an8855_mdio_of_match[] = {
+       { .compatible = "airoha,an8855-mdio", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, an8855_mdio_of_match);
+
+static struct platform_driver an8855_mdio_driver = {
+       .probe  = an8855_mdio_probe,
+       .driver = {
+               .name = "an8855-mdio",
+               .of_match_table = an8855_mdio_of_match,
+       },
+};
+module_platform_driver(an8855_mdio_driver);
+
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_DESCRIPTION("Driver for AN8855 MDIO passthrough");
+MODULE_LICENSE("GPL");
diff --git a/target/linux/mediatek/files-6.6/drivers/net/phy/air_an8855.c b/target/linux/mediatek/files-6.6/drivers/net/phy/air_an8855.c
new file mode 100644 (file)
index 0000000..7fab085
--- /dev/null
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2024 Christian Marangi <ansuelsmth@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/phy.h>
+
+#define AN8855_PHY_SELECT_PAGE                 0x1f
+#define   AN8855_PHY_PAGE                      GENMASK(2, 0)
+#define   AN8855_PHY_PAGE_STANDARD             FIELD_PREP_CONST(AN8855_PHY_PAGE, 0x0)
+#define   AN8855_PHY_PAGE_EXTENDED_1           FIELD_PREP_CONST(AN8855_PHY_PAGE, 0x1)
+
+/* MII Registers Page 1 */
+#define AN8855_PHY_EXT_REG_14                  0x14
+#define   AN8855_PHY_EN_DOWN_SHIFT             BIT(4)
+
+/* R50 Calibration regs in MDIO_MMD_VEND1 */
+#define AN8855_PHY_R500HM_RSEL_TX_AB           0x174
+#define AN8855_PHY_R50OHM_RSEL_TX_A_EN         BIT(15)
+#define AN8855_PHY_R50OHM_RSEL_TX_A            GENMASK(14, 8)
+#define AN8855_PHY_R50OHM_RSEL_TX_B_EN         BIT(7)
+#define AN8855_PHY_R50OHM_RSEL_TX_B            GENMASK(6, 0)
+#define AN8855_PHY_R500HM_RSEL_TX_CD           0x175
+#define AN8855_PHY_R50OHM_RSEL_TX_C_EN         BIT(15)
+#define AN8855_PHY_R50OHM_RSEL_TX_C            GENMASK(14, 8)
+#define AN8855_PHY_R50OHM_RSEL_TX_D_EN         BIT(7)
+#define AN8855_PHY_R50OHM_RSEL_TX_D            GENMASK(6, 0)
+
+#define AN8855_SWITCH_EFUSE_R50O               GENMASK(30, 24)
+
+/* PHY TX PAIR DELAY SELECT Register */
+#define AN8855_PHY_TX_PAIR_DLY_SEL_GBE         0x013
+#define   AN8855_PHY_CR_DA_TX_PAIR_DELKAY_SEL_A_GBE GENMASK(14, 12)
+#define   AN8855_PHY_CR_DA_TX_PAIR_DELKAY_SEL_B_GBE GENMASK(10, 8)
+#define   AN8855_PHY_CR_DA_TX_PAIR_DELKAY_SEL_C_GBE GENMASK(6, 4)
+#define   AN8855_PHY_CR_DA_TX_PAIR_DELKAY_SEL_D_GBE GENMASK(2, 0)
+/* PHY ADC Register */
+#define AN8855_PHY_RXADC_CTRL                  0x0d8
+#define   AN8855_PHY_RG_AD_SAMNPLE_PHSEL_A     BIT(12)
+#define   AN8855_PHY_RG_AD_SAMNPLE_PHSEL_B     BIT(8)
+#define   AN8855_PHY_RG_AD_SAMNPLE_PHSEL_C     BIT(4)
+#define   AN8855_PHY_RG_AD_SAMNPLE_PHSEL_D     BIT(0)
+#define AN8855_PHY_RXADC_REV_0                 0x0d9
+#define   AN8855_PHY_RG_AD_RESERVE0_A          GENMASK(15, 8)
+#define   AN8855_PHY_RG_AD_RESERVE0_B          GENMASK(7, 0)
+#define AN8855_PHY_RXADC_REV_1                 0x0da
+#define   AN8855_PHY_RG_AD_RESERVE0_C          GENMASK(15, 8)
+#define   AN8855_PHY_RG_AD_RESERVE0_D          GENMASK(7, 0)
+
+#define AN8855_PHY_ID                          0xc0ff0410
+
+#define AN8855_PHY_FLAGS_EN_CALIBRATION                BIT(0)
+
+struct air_an8855_priv {
+       u8 calibration_data[4];
+};
+
+static const u8 dsa_r50ohm_table[] = {
+       127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+       127, 127, 127, 127, 127, 127, 127, 126, 122, 117,
+       112, 109, 104, 101,  97,  94,  90,  88,  84,  80,
+       78,  74,  72,  68,  66,  64,  61,  58,  56,  53,
+       51,  48,  47,  44,  42,  40,  38,  36,  34,  32,
+       31,  28,  27,  24,  24,  22,  20,  18,  16,  16,
+       14,  12,  11,   9
+};
+
+static int en8855_get_r50ohm_val(struct device *dev, const char *calib_name,
+                                u8 *dest)
+{
+       u32 shift_sel, val;
+       int ret;
+       int i;
+
+       ret = nvmem_cell_read_u32(dev, calib_name, &val);
+       if (ret)
+               return ret;
+
+       shift_sel = FIELD_GET(AN8855_SWITCH_EFUSE_R50O, val);
+       for (i = 0; i < ARRAY_SIZE(dsa_r50ohm_table); i++)
+               if (dsa_r50ohm_table[i] == shift_sel)
+                       break;
+
+       if (i < 8 || i >= ARRAY_SIZE(dsa_r50ohm_table))
+               *dest = dsa_r50ohm_table[25];
+       else
+               *dest = dsa_r50ohm_table[i - 8];
+
+       return 0;
+}
+
+static int an8855_probe(struct phy_device *phydev)
+{
+       struct device *dev = &phydev->mdio.dev;
+       struct device_node *node = dev->of_node;
+       struct air_an8855_priv *priv;
+
+       /* If we don't have a node, skip calib */
+       if (!node)
+               return 0;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       phydev->priv = priv;
+
+       return 0;
+}
+
+static int an8855_get_downshift(struct phy_device *phydev, u8 *data)
+{
+       int val;
+
+       val = phy_read_paged(phydev, AN8855_PHY_PAGE_EXTENDED_1, AN8855_PHY_EXT_REG_14);
+       if (val < 0)
+               return val;
+
+       *data = val & AN8855_PHY_EN_DOWN_SHIFT ? DOWNSHIFT_DEV_DEFAULT_COUNT :
+                                                DOWNSHIFT_DEV_DISABLE;
+
+       return 0;
+}
+
+static int an8855_set_downshift(struct phy_device *phydev, u8 cnt)
+{
+       u16 ds = cnt != DOWNSHIFT_DEV_DISABLE ? AN8855_PHY_EN_DOWN_SHIFT : 0;
+
+       return phy_modify_paged(phydev, AN8855_PHY_PAGE_EXTENDED_1,
+                               AN8855_PHY_EXT_REG_14, AN8855_PHY_EN_DOWN_SHIFT,
+                               ds);
+}
+
+static int an8855_config_init(struct phy_device *phydev)
+{
+       struct air_an8855_priv *priv = phydev->priv;
+       struct device *dev = &phydev->mdio.dev;
+       int ret;
+
+       /* Enable HW auto downshift */
+       ret = an8855_set_downshift(phydev, DOWNSHIFT_DEV_DEFAULT_COUNT);
+       if (ret)
+               return ret;
+
+       /* Apply calibration values, if needed.
+        * AN8855_PHY_FLAGS_EN_CALIBRATION signal this.
+        */
+       if (priv && phydev->dev_flags & AN8855_PHY_FLAGS_EN_CALIBRATION) {
+               u8 *calibration_data = priv->calibration_data;
+
+               ret = en8855_get_r50ohm_val(dev, "tx_a", &calibration_data[0]);
+               if (ret)
+                       return ret;
+
+               ret = en8855_get_r50ohm_val(dev, "tx_b", &calibration_data[1]);
+               if (ret)
+                       return ret;
+
+               ret = en8855_get_r50ohm_val(dev, "tx_c", &calibration_data[2]);
+               if (ret)
+                       return ret;
+
+               ret = en8855_get_r50ohm_val(dev, "tx_d", &calibration_data[3]);
+               if (ret)
+                       return ret;
+
+               ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_R500HM_RSEL_TX_AB,
+                                    AN8855_PHY_R50OHM_RSEL_TX_A | AN8855_PHY_R50OHM_RSEL_TX_B,
+                                    FIELD_PREP(AN8855_PHY_R50OHM_RSEL_TX_A, calibration_data[0]) |
+                                    FIELD_PREP(AN8855_PHY_R50OHM_RSEL_TX_B, calibration_data[1]));
+               if (ret)
+                       return ret;
+               ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_R500HM_RSEL_TX_CD,
+                                    AN8855_PHY_R50OHM_RSEL_TX_C | AN8855_PHY_R50OHM_RSEL_TX_D,
+                                    FIELD_PREP(AN8855_PHY_R50OHM_RSEL_TX_C, calibration_data[2]) |
+                                    FIELD_PREP(AN8855_PHY_R50OHM_RSEL_TX_D, calibration_data[3]));
+               if (ret)
+                       return ret;
+       }
+
+       /* Apply values to reduce signal noise */
+       ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_TX_PAIR_DLY_SEL_GBE,
+                           FIELD_PREP(AN8855_PHY_CR_DA_TX_PAIR_DELKAY_SEL_A_GBE, 0x4) |
+                           FIELD_PREP(AN8855_PHY_CR_DA_TX_PAIR_DELKAY_SEL_C_GBE, 0x4));
+       if (ret)
+               return ret;
+       ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_RXADC_CTRL,
+                           AN8855_PHY_RG_AD_SAMNPLE_PHSEL_A |
+                           AN8855_PHY_RG_AD_SAMNPLE_PHSEL_C);
+       if (ret)
+               return ret;
+       ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_RXADC_REV_0,
+                           FIELD_PREP(AN8855_PHY_RG_AD_RESERVE0_A, 0x1));
+       if (ret)
+               return ret;
+       ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_RXADC_REV_1,
+                           FIELD_PREP(AN8855_PHY_RG_AD_RESERVE0_C, 0x1));
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int an8855_get_tunable(struct phy_device *phydev,
+                             struct ethtool_tunable *tuna, void *data)
+{
+       switch (tuna->id) {
+       case ETHTOOL_PHY_DOWNSHIFT:
+               return an8855_get_downshift(phydev, data);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static int an8855_set_tunable(struct phy_device *phydev,
+                             struct ethtool_tunable *tuna, const void *data)
+{
+       switch (tuna->id) {
+       case ETHTOOL_PHY_DOWNSHIFT:
+               return an8855_set_downshift(phydev, *(const u8 *)data);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static int an8855_read_page(struct phy_device *phydev)
+{
+       return __phy_read(phydev, AN8855_PHY_SELECT_PAGE);
+}
+
+static int an8855_write_page(struct phy_device *phydev, int page)
+{
+       return __phy_write(phydev, AN8855_PHY_SELECT_PAGE, page);
+}
+
+static struct phy_driver an8855_driver[] = {
+{
+       PHY_ID_MATCH_EXACT(AN8855_PHY_ID),
+       .name                   = "Airoha AN8855 internal PHY",
+       /* PHY_GBIT_FEATURES */
+       .flags                  = PHY_IS_INTERNAL,
+       .probe                  = an8855_probe,
+       .config_init            = an8855_config_init,
+       .soft_reset             = genphy_soft_reset,
+       .get_tunable            = an8855_get_tunable,
+       .set_tunable            = an8855_set_tunable,
+       .suspend                = genphy_suspend,
+       .resume                 = genphy_resume,
+       .read_page              = an8855_read_page,
+       .write_page             = an8855_write_page,
+}, };
+
+module_phy_driver(an8855_driver);
+
+static struct mdio_device_id __maybe_unused an8855_tbl[] = {
+       { PHY_ID_MATCH_EXACT(AN8855_PHY_ID) },
+       { }
+};
+
+MODULE_DEVICE_TABLE(mdio, an8855_tbl);
+
+MODULE_DESCRIPTION("Airoha AN8855 PHY driver");
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/target/linux/mediatek/files-6.6/drivers/nvmem/an8855-efuse.c b/target/linux/mediatek/files-6.6/drivers/nvmem/an8855-efuse.c
new file mode 100644 (file)
index 0000000..7940453
--- /dev/null
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  Airoha AN8855 Switch EFUSE Driver
+ */
+
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define AN8855_EFUSE_CELL              50
+
+#define AN8855_EFUSE_DATA0             0x1000a500
+#define   AN8855_EFUSE_R50O            GENMASK(30, 24)
+
+static int an8855_efuse_read(void *context, unsigned int offset,
+                            void *val, size_t bytes)
+{
+       struct regmap *regmap = context;
+
+       return regmap_bulk_read(regmap, AN8855_EFUSE_DATA0 + offset,
+                               val, bytes / sizeof(u32));
+}
+
+static int an8855_efuse_probe(struct platform_device *pdev)
+{
+       struct nvmem_config an8855_nvmem_config = {
+               .name = "an8855-efuse",
+               .size = AN8855_EFUSE_CELL * sizeof(u32),
+               .stride = sizeof(u32),
+               .word_size = sizeof(u32),
+               .reg_read = an8855_efuse_read,
+       };
+       struct device *dev = &pdev->dev;
+       struct nvmem_device *nvmem;
+
+       /* Assign NVMEM priv to MFD regmap */
+       an8855_nvmem_config.priv = dev_get_regmap(dev->parent, NULL);
+       an8855_nvmem_config.dev = dev;
+       nvmem = devm_nvmem_register(dev, &an8855_nvmem_config);
+
+       return PTR_ERR_OR_ZERO(nvmem);
+}
+
+static const struct of_device_id an8855_efuse_of_match[] = {
+       { .compatible = "airoha,an8855-efuse", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, an8855_efuse_of_match);
+
+static struct platform_driver an8855_efuse_driver = {
+       .probe = an8855_efuse_probe,
+       .driver = {
+               .name = "an8855-efuse",
+               .of_match_table = an8855_efuse_of_match,
+       },
+};
+module_platform_driver(an8855_efuse_driver);
+
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_DESCRIPTION("Driver for AN8855 Switch EFUSE");
+MODULE_LICENSE("GPL");
diff --git a/target/linux/mediatek/files-6.6/include/linux/mfd/airoha-an8855-mfd.h b/target/linux/mediatek/files-6.6/include/linux/mfd/airoha-an8855-mfd.h
new file mode 100644 (file)
index 0000000..5606156
--- /dev/null
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * MFD driver for Airoha AN8855 Switch
+ */
+#ifndef _LINUX_INCLUDE_MFD_AIROHA_AN8855_MFD_H
+#define _LINUX_INCLUDE_MFD_AIROHA_AN8855_MFD_H
+
+#include <linux/bitfield.h>
+
+/* MII Registers */
+#define AN8855_PHY_SELECT_PAGE         0x1f
+#define   AN8855_PHY_PAGE              GENMASK(2, 0)
+#define   AN8855_PHY_PAGE_STANDARD     FIELD_PREP_CONST(AN8855_PHY_PAGE, 0x0)
+#define   AN8855_PHY_PAGE_EXTENDED_1   FIELD_PREP_CONST(AN8855_PHY_PAGE, 0x1)
+#define   AN8855_PHY_PAGE_EXTENDED_4   FIELD_PREP_CONST(AN8855_PHY_PAGE, 0x4)
+
+/* MII Registers Page 4 */
+#define AN8855_PBUS_MODE               0x10
+#define   AN8855_PBUS_MODE_ADDR_FIXED  0x0
+#define AN8855_PBUS_MODE_ADDR_INCR     BIT(15)
+#define AN8855_PBUS_WR_ADDR_HIGH       0x11
+#define AN8855_PBUS_WR_ADDR_LOW                0x12
+#define AN8855_PBUS_WR_DATA_HIGH       0x13
+#define AN8855_PBUS_WR_DATA_LOW                0x14
+#define AN8855_PBUS_RD_ADDR_HIGH       0x15
+#define AN8855_PBUS_RD_ADDR_LOW                0x16
+#define AN8855_PBUS_RD_DATA_HIGH       0x17
+#define AN8855_PBUS_RD_DATA_LOW                0x18
+
+struct an8855_mfd_priv {
+       struct device *dev;
+       struct mii_bus *bus;
+
+       unsigned int switch_addr;
+       u16 current_page;
+};
+
+int an8855_mii_set_page(struct an8855_mfd_priv *priv, u8 phy_id,
+                       u8 page);
+
+#endif
index e0a715a56b187d4813f99354018da74641d5805e..fe85b9d3f14130bbad6609d5760cce39af7680fb 100644 (file)
@@ -1,5 +1,6 @@
 CONFIG_64BIT=y
 # CONFIG_AHCI_MTK is not set
+CONFIG_AIR_AN8855_PHY=y
 CONFIG_AIROHA_EN8801SC_PHY=y
 CONFIG_ARCH_BINFMT_ELF_EXTRA_PHDRS=y
 CONFIG_ARCH_CORRECT_STACKTRACE_ON_KRETPROBE=y
@@ -237,12 +238,14 @@ CONFIG_MAXLINEAR_GPHY=y
 CONFIG_MDIO_BUS=y
 CONFIG_MDIO_DEVICE=y
 CONFIG_MDIO_DEVRES=y
+CONFIG_MDIO_AN8855=y
 CONFIG_MEDIATEK_2P5GE_PHY=y
 CONFIG_MEDIATEK_GE_PHY=y
 CONFIG_MEDIATEK_GE_SOC_PHY=y
 CONFIG_MEDIATEK_WATCHDOG=y
 CONFIG_MESSAGE_LOGLEVEL_DEFAULT=7
 CONFIG_MFD_SYSCON=y
+CONFIG_MFD_AIROHA_AN8855=y
 CONFIG_MIGRATION=y
 # CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY is not set
 CONFIG_MMC=y
@@ -292,6 +295,7 @@ CONFIG_NEED_DMA_MAP_STATE=y
 CONFIG_NEED_SG_DMA_LENGTH=y
 CONFIG_NET_DEVLINK=y
 CONFIG_NET_DSA=y
+CONFIG_NET_DSA_AN8855=y
 CONFIG_NET_DSA_MT7530=y
 CONFIG_NET_DSA_MT7530_MDIO=y
 CONFIG_NET_DSA_MT7530_MMIO=y
@@ -310,6 +314,7 @@ CONFIG_NO_HZ_COMMON=y
 CONFIG_NO_HZ_IDLE=y
 CONFIG_NR_CPUS=4
 CONFIG_NVMEM=y
+CONFIG_NVMEM_AN8855_EFUSE=y
 CONFIG_NVMEM_BLOCK=y
 CONFIG_NVMEM_LAYOUTS=y
 CONFIG_NVMEM_LAYOUT_ADTRAN=y
index ec3be8df9aa57454dd39656304389f83866d531f..c3c8b60684dceb8dfc0ec3be8114e2b57dbc1a18 100644 (file)
@@ -1,5 +1,6 @@
 CONFIG_64BIT=y
 # CONFIG_AHCI_MTK is not set
+# CONFIG_AIR_AN8855_PHY is not set
 # CONFIG_AIROHA_EN8801SC_PHY is not set
 CONFIG_AQUANTIA_PHY=y
 CONFIG_ARCH_BINFMT_ELF_EXTRA_PHDRS=y
@@ -240,12 +241,14 @@ CONFIG_MAXLINEAR_GPHY=y
 CONFIG_MDIO_BUS=y
 CONFIG_MDIO_DEVICE=y
 CONFIG_MDIO_DEVRES=y
+# CONFIG_MDIO_AN8855 is not set
 # CONFIG_MEDIATEK_2P5GE_PHY is not set
 CONFIG_MEDIATEK_GE_PHY=y
 # CONFIG_MEDIATEK_GE_SOC_PHY is not set
 CONFIG_MEDIATEK_WATCHDOG=y
 CONFIG_MESSAGE_LOGLEVEL_DEFAULT=7
 CONFIG_MFD_SYSCON=y
+# CONFIG_MFD_AIROHA_AN8855 is not set
 CONFIG_MIGRATION=y
 # CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY is not set
 CONFIG_MMC=y
@@ -294,6 +297,7 @@ CONFIG_NEED_DMA_MAP_STATE=y
 CONFIG_NEED_SG_DMA_LENGTH=y
 CONFIG_NET_DEVLINK=y
 CONFIG_NET_DSA=y
+# CONFIG_NET_DSA_AN8855 is not set
 CONFIG_NET_DSA_MT7530=y
 CONFIG_NET_DSA_MT7530_MDIO=y
 # CONFIG_NET_DSA_MT7530_MMIO is not set
@@ -312,6 +316,7 @@ CONFIG_NO_HZ_COMMON=y
 CONFIG_NO_HZ_IDLE=y
 CONFIG_NR_CPUS=2
 CONFIG_NVMEM=y
+# CONFIG_NVMEM_AN8855_EFUSE is not set
 CONFIG_NVMEM_BLOCK=y
 CONFIG_NVMEM_LAYOUTS=y
 CONFIG_NVMEM_LAYOUT_ADTRAN=y
index 6bc92a09dce30e8b5cfef256a51472b8d4acbd3b..9d12c48eeeea1e9283efa8d7c538e7039cdca661 100644 (file)
@@ -1,4 +1,5 @@
 # CONFIG_AIO is not set
+# CONFIG_AIR_AN8855_PHY is not set
 # CONFIG_AIROHA_EN8801SC_PHY is not set
 CONFIG_ALIGNMENT_TRAP=y
 CONFIG_ARCH_32BIT_OFF_T=y
@@ -352,6 +353,7 @@ CONFIG_MDIO_BITBANG=y
 CONFIG_MDIO_BUS=y
 CONFIG_MDIO_DEVICE=y
 CONFIG_MDIO_DEVRES=y
+# CONFIG_MDIO_AN8855 is not set
 CONFIG_MDIO_GPIO=y
 CONFIG_MEDIATEK_GE_PHY=y
 CONFIG_MEDIATEK_MT6577_AUXADC=y
@@ -361,6 +363,7 @@ CONFIG_MFD_CORE=y
 # CONFIG_MFD_HI6421_SPMI is not set
 CONFIG_MFD_MT6397=y
 CONFIG_MFD_SYSCON=y
+# CONFIG_MFD_AIROHA_AN8855 is not set
 CONFIG_MIGHT_HAVE_CACHE_L2X0=y
 CONFIG_MIGRATION=y
 CONFIG_MMC=y
@@ -410,6 +413,7 @@ CONFIG_NEED_SRCU_NMI_SAFE=y
 CONFIG_NEON=y
 CONFIG_NET_DEVLINK=y
 CONFIG_NET_DSA=y
+# CONFIG_NET_DSA_AN8855 is not set
 CONFIG_NET_DSA_MT7530=y
 CONFIG_NET_DSA_MT7530_MDIO=y
 # CONFIG_NET_DSA_MT7530_MMIO is not set
@@ -431,6 +435,7 @@ CONFIG_NO_HZ_COMMON=y
 CONFIG_NO_HZ_IDLE=y
 CONFIG_NR_CPUS=4
 CONFIG_NVMEM=y
+# CONFIG_NVMEM_AN8855_EFUSE is not set
 CONFIG_NVMEM_LAYOUTS=y
 # CONFIG_NVMEM_LAYOUT_ADTRAN is not set
 CONFIG_NVMEM_MTK_EFUSE=y
index 9f57bda3e9e7a7f1908f1a5ba45b9d3c95451728..d66a514f63e6261c3c190d2e9fd827d7f4065e5f 100644 (file)
@@ -1,3 +1,4 @@
+# CONFIG_AIR_AN8855_PHY is not set
 # CONFIG_AIROHA_EN8801SC_PHY is not set
 CONFIG_ALIGNMENT_TRAP=y
 CONFIG_ARCH_32BIT_OFF_T=y
@@ -181,9 +182,11 @@ CONFIG_MACH_MT7629=y
 CONFIG_MDIO_BUS=y
 CONFIG_MDIO_DEVICE=y
 CONFIG_MDIO_DEVRES=y
+# CONFIG_MDIO_AN8855 is not set
 CONFIG_MEDIATEK_GE_PHY=y
 CONFIG_MEDIATEK_WATCHDOG=y
 CONFIG_MFD_SYSCON=y
+# CONFIG_MFD_AIROHA_AN8855 is not set
 CONFIG_MIGHT_HAVE_CACHE_L2X0=y
 CONFIG_MIGRATION=y
 CONFIG_MMU_LAZY_TLB_REFCOUNT=y
@@ -216,6 +219,7 @@ CONFIG_NETFILTER=y
 CONFIG_NETFILTER_BPF_LINK=y
 CONFIG_NET_DEVLINK=y
 CONFIG_NET_DSA=y
+# CONFIG_NET_DSA_AN8855 is not set
 CONFIG_NET_DSA_MT7530=y
 CONFIG_NET_DSA_MT7530_MDIO=y
 # CONFIG_NET_DSA_MT7530_MMIO is not set
@@ -234,6 +238,7 @@ CONFIG_NO_HZ_COMMON=y
 CONFIG_NO_HZ_IDLE=y
 CONFIG_NR_CPUS=2
 CONFIG_NVMEM=y
+# CONFIG_NVMEM_AN8855_EFUSE is not set
 CONFIG_NVMEM_LAYOUTS=y
 # CONFIG_NVMEM_LAYOUT_ADTRAN is not set
 # CONFIG_NVMEM_MTK_EFUSE is not set
diff --git a/target/linux/mediatek/patches-6.6/737-net-dsa-add-Airoha-AN8855.patch b/target/linux/mediatek/patches-6.6/737-net-dsa-add-Airoha-AN8855.patch
new file mode 100644 (file)
index 0000000..bd70bec
--- /dev/null
@@ -0,0 +1,309 @@
+From: Christian Marangi <ansuelsmth@gmail.com>
+To: Christian Marangi <ansuelsmth@gmail.com>,
+       Lee Jones <lee@kernel.org>, Rob Herring <robh@kernel.org>,
+       Krzysztof Kozlowski <krzk+dt@kernel.org>,
+       Conor Dooley <conor+dt@kernel.org>,
+       Andrew Lunn <andrew+netdev@lunn.ch>,
+       "David S. Miller" <davem@davemloft.net>,
+       Eric Dumazet <edumazet@google.com>,
+       Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
+       Vladimir Oltean <olteanv@gmail.com>,
+       Srinivas Kandagatla <srinivas.kandagatla@linaro.org>,
+       Heiner Kallweit <hkallweit1@gmail.com>,
+       Russell King <linux@armlinux.org.uk>,
+       Matthias Brugger <matthias.bgg@gmail.com>,
+       AngeloGioacchino Del Regno
+       <angelogioacchino.delregno@collabora.com>,
+       linux-arm-kernel@lists.infradead.org,
+       linux-mediatek@lists.infradead.org, netdev@vger.kernel.org,
+       devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
+       upstream@airoha.com
+Subject: [net-next PATCH v11 0/9] net: dsa: Add Airoha AN8855 support
+Date: Mon,  9 Dec 2024 14:44:17 +0100  [thread overview]
+Message-ID: <20241209134459.27110-1-ansuelsmth@gmail.com> (raw)
+
+This small series add the initial support for the Airoha AN8855 Switch.
+
+It's a 5 port Gigabit Switch with SGMII/HSGMII upstream port.
+
+This is starting to get in the wild and there are already some router
+having this switch chip.
+
+It's conceptually similar to mediatek switch but register and bits
+are different. And there is that massive Hell that is the PCS
+configuration.
+Saddly for that part we have absolutely NO documentation currently.
+
+There is this special thing where PHY needs to be calibrated with values
+from the switch efuse. (the thing have a whole cpu timer and MCU)
+
+Changes v11:
+- Address reviews from Christophe (spell mistake + dev_err_probe)
+- Fix kconfig dependency for MFD driver (depends on MDIO_DEVICE instead of MDIO)
+  (indirectly fix link error for mdio APIs)
+- Fix copy-paste error for MFD driver of_table
+- Fix compilation error for PHY (move NVMEM to .config)
+- Drop unneeded NVMEM node from MDIO example schema (from Andrew)
+- Adapt MFD example schema to MDIO reg property restrictions
+Changes v10:
+- Entire rework to MFD + split to MDIO, EFUSE, SWITCH separate drivers
+- Drop EEE OPs (while Russell finish RFC for EEE changes)
+- Use new pcs_inpand OPs
+- Drop AN restart function and move to pcs_config
+- Enable assisted_learning and disable CPU learn (preparation for fdb_isolation)
+- Move EFUSE read in Internal PHY driver to .config to handle EPROBE_DEFER
+  (needed now that NVMEM driver is register externally instead of internally to switch
+   node)
+Changes v9:
+- Error out on using 5G speed as currently not supported
+- Add missing MAC_2500FD in phylink mac_capabilities
+- Add comment and improve if condition for an8855_phylink_mac_config
+Changes v8:
+- Add port Fast Age support
+- Add support for Port Isolation
+- Use correct register for Learning Disable
+- Add support for Ageing Time OP
+- Set default PVID to 0 by default
+- Add mdb OPs
+- Add port change MTU
+- Fix support for Upper VLAN
+Changes v7:
+- Fix devm_dsa_register_switch wrong export symbol
+Changes v6:
+- Drop standard MIB and handle with ethtool OPs (as requested by Jakub)
+- Cosmetic: use bool instead of 0 or 1
+Changes v5:
+- Add devm_dsa_register_switch() patch
+- Add Reviewed-by tag for DT patch
+Changes v4:
+- Set regmap readable_table static (mute compilation warning)
+- Add support for port_bridge flags (LEARNING, FLOOD)
+- Reset fdb struct in fdb_dump
+- Drop support_asym_pause in port_enable
+- Add define for get_phy_flags
+- Fix bug for port not inititially part of a bridge
+  (in an8855_setup the port matrix was always cleared but
+   the CPU port was never initially added)
+- Disable learning and flood for user port by default
+- Set CPU port to flood and learning by default
+- Correctly AND force duplex and flow control in an8855_phylink_mac_link_up
+- Drop RGMII from pcs_config
+- Check ret in "Disable AN if not in autoneg"
+- Use devm_mutex_init
+- Fix typo for AN8855_PORT_CHECK_MODE
+- Better define AN8855_STP_LISTENING = AN8855_STP_BLOCKING
+- Fix typo in AN8855_PHY_EN_DOWN_SHIFT
+- Use paged helper for PHY
+- Skip calibration in config_init if priv not defined
+Changes v3:
+- Out of RFC
+- Switch PHY code to select_page API
+- Better describe masks and bits in PHY driver for ADC register
+- Drop raw values and use define for mii read/write
+- Switch to absolute PHY address
+- Replace raw values with mask and bits for pcs_config
+- Fix typo for ext-surge property name
+- Drop support for relocating Switch base PHY address on the bus
+Changes v2:
+- Drop mutex guard patch
+- Drop guard usage in DSA driver
+- Use __mdiobus_write/read
+- Check return condition and return errors for mii read/write
+- Fix wrong logic for EEE
+- Fix link_down (don't force link down with autoneg)
+- Fix forcing speed on sgmii autoneg
+- Better document link speed for sgmii reg
+- Use standard define for sgmii reg
+- Imlement nvmem support to expose switch EFUSE
+- Rework PHY calibration with the use of NVMEM producer/consumer
+- Update DT with new NVMEM property
+- Move aneg validation for 2500-basex in pcs_config
+- Move r50Ohm table and function to PHY driver
+
+Christian Marangi (9):
+  dt-bindings: nvmem: Document support for Airoha AN8855 Switch EFUSE
+  dt-bindings: net: Document support for Airoha AN8855 Switch Virtual
+    MDIO
+  dt-bindings: net: dsa: Document support for Airoha AN8855 DSA Switch
+  dt-bindings: mfd: Document support for Airoha AN8855 Switch SoC
+  mfd: an8855: Add support for Airoha AN8855 Switch MFD
+  net: mdio: Add Airoha AN8855 Switch MDIO Passtrough
+  nvmem: an8855: Add support for Airoha AN8855 Switch EFUSE
+  net: dsa: Add Airoha AN8855 5-Port Gigabit DSA Switch driver
+  net: phy: Add Airoha AN8855 Internal Switch Gigabit PHY
+
+ .../bindings/mfd/airoha,an8855-mfd.yaml       |  178 ++
+ .../bindings/net/airoha,an8855-mdio.yaml      |   56 +
+ .../net/dsa/airoha,an8855-switch.yaml         |  105 +
+ .../bindings/nvmem/airoha,an8855-efuse.yaml   |  123 +
+ MAINTAINERS                                   |   17 +
+ drivers/mfd/Kconfig                           |   10 +
+ drivers/mfd/Makefile                          |    1 +
+ drivers/mfd/airoha-an8855.c                   |  278 ++
+ drivers/net/dsa/Kconfig                       |    9 +
+ drivers/net/dsa/Makefile                      |    1 +
+ drivers/net/dsa/an8855.c                      | 2310 +++++++++++++++++
+ drivers/net/dsa/an8855.h                      |  783 ++++++
+ drivers/net/mdio/Kconfig                      |    9 +
+ drivers/net/mdio/Makefile                     |    1 +
+ drivers/net/mdio/mdio-an8855.c                |  113 +
+ drivers/net/phy/Kconfig                       |    5 +
+ drivers/net/phy/Makefile                      |    1 +
+ drivers/net/phy/air_an8855.c                  |  267 ++
+ drivers/nvmem/Kconfig                         |   11 +
+ drivers/nvmem/Makefile                        |    2 +
+ drivers/nvmem/an8855-efuse.c                  |   63 +
+ include/linux/mfd/airoha-an8855-mfd.h         |   41 +
+ 22 files changed, 4384 insertions(+)
+ create mode 100644 Documentation/devicetree/bindings/mfd/airoha,an8855-mfd.yaml
+ create mode 100644 Documentation/devicetree/bindings/net/airoha,an8855-mdio.yaml
+ create mode 100644 Documentation/devicetree/bindings/net/dsa/airoha,an8855-switch.yaml
+ create mode 100644 Documentation/devicetree/bindings/nvmem/airoha,an8855-efuse.yaml
+ create mode 100644 drivers/mfd/airoha-an8855.c
+ create mode 100644 drivers/net/dsa/an8855.c
+ create mode 100644 drivers/net/dsa/an8855.h
+ create mode 100644 drivers/net/mdio/mdio-an8855.c
+ create mode 100644 drivers/net/phy/air_an8855.c
+ create mode 100644 drivers/nvmem/an8855-efuse.c
+ create mode 100644 include/linux/mfd/airoha-an8855-mfd.h
+
+--- a/drivers/mfd/Kconfig
++++ b/drivers/mfd/Kconfig
+@@ -41,6 +41,16 @@ config MFD_ALTERA_SYSMGR
+         using regmap_mmio accesses for ARM32 parts and SMC calls to
+         EL3 for ARM64 parts.
++config MFD_AIROHA_AN8855
++      tristate "Airoha AN8855 Switch MFD"
++      select MFD_CORE
++      select MDIO_DEVICE
++      depends on NETDEVICES && OF
++      help
++        Support for the Airoha AN8855 Switch MFD. This is a SoC Switch
++        that provides various peripherals. Currently it provides a
++        DSA switch and a NVMEM provider.
++
+ config MFD_ACT8945A
+       tristate "Active-semi ACT8945A"
+       select MFD_CORE
+--- a/drivers/mfd/Makefile
++++ b/drivers/mfd/Makefile
+@@ -7,6 +7,7 @@
+ obj-$(CONFIG_MFD_88PM860X)    += 88pm860x.o
+ obj-$(CONFIG_MFD_88PM800)     += 88pm800.o 88pm80x.o
+ obj-$(CONFIG_MFD_88PM805)     += 88pm805.o 88pm80x.o
++obj-$(CONFIG_MFD_AIROHA_AN8855)       += airoha-an8855.o
+ obj-$(CONFIG_MFD_ACT8945A)    += act8945a.o
+ obj-$(CONFIG_MFD_SM501)               += sm501.o
+ obj-$(CONFIG_ARCH_BCM2835)    += bcm2835-pm.o
+--- a/drivers/net/dsa/Kconfig
++++ b/drivers/net/dsa/Kconfig
+@@ -24,6 +24,15 @@ config NET_DSA_LOOP
+         This enables support for a fake mock-up switch chip which
+         exercises the DSA APIs.
++config NET_DSA_AN8855
++      tristate "Airoha AN8855 Ethernet switch support"
++      depends on MFD_AIROHA_AN8855
++      depends on NET_DSA
++      select NET_DSA_TAG_MTK
++      help
++        This enables support for the Airoha AN8855 Ethernet switch
++        chip.
++
+ source "drivers/net/dsa/hirschmann/Kconfig"
+ config NET_DSA_LANTIQ_GSWIP
+--- a/drivers/net/dsa/Makefile
++++ b/drivers/net/dsa/Makefile
+@@ -5,6 +5,7 @@ obj-$(CONFIG_NET_DSA_LOOP)     += dsa_loop.o
+ ifdef CONFIG_NET_DSA_LOOP
+ obj-$(CONFIG_FIXED_PHY)               += dsa_loop_bdinfo.o
+ endif
++obj-$(CONFIG_NET_DSA_AN8855)  += an8855.o
+ obj-$(CONFIG_NET_DSA_LANTIQ_GSWIP) += lantiq_gswip.o
+ obj-$(CONFIG_NET_DSA_MT7530)  += mt7530.o
+ obj-$(CONFIG_NET_DSA_MT7530_MDIO) += mt7530-mdio.o
+--- a/drivers/net/mdio/Kconfig
++++ b/drivers/net/mdio/Kconfig
+@@ -61,6 +61,15 @@ config MDIO_XGENE
+         This module provides a driver for the MDIO busses found in the
+         APM X-Gene SoC's.
++config MDIO_AN8855
++      tristate "Airoha AN8855 Switch MDIO bus controller"
++      depends on MFD_AIROHA_AN8855
++      depends on OF_MDIO
++      help
++        This module provides a driver for the Airoha AN8855 Switch
++        that requires a MDIO passtrough as switch address is shared
++        with the internal PHYs and requires additional page handling.
++
+ config MDIO_ASPEED
+       tristate "ASPEED MDIO bus controller"
+       depends on ARCH_ASPEED || COMPILE_TEST
+--- a/drivers/net/mdio/Makefile
++++ b/drivers/net/mdio/Makefile
+@@ -5,6 +5,7 @@ obj-$(CONFIG_ACPI_MDIO)                += acpi_mdio.o
+ obj-$(CONFIG_FWNODE_MDIO)     += fwnode_mdio.o
+ obj-$(CONFIG_OF_MDIO)         += of_mdio.o
++obj-$(CONFIG_MDIO_AN8855)             += mdio-an8855.o
+ obj-$(CONFIG_MDIO_ASPEED)             += mdio-aspeed.o
+ obj-$(CONFIG_MDIO_BCM_IPROC)          += mdio-bcm-iproc.o
+ obj-$(CONFIG_MDIO_BCM_UNIMAC)         += mdio-bcm-unimac.o
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -147,6 +147,11 @@ config AIROHA_EN8801SC_PHY
+       help
+         Currently supports the Airoha EN8801SC PHY.
++config AIR_AN8855_PHY
++      tristate "Airoha AN8855 Internal Gigabit PHY"
++      help
++        Currently supports the internal Airoha AN8855 Switch PHY.
++
+ config AIR_EN8811H_PHY
+       tristate "Airoha EN8811H 2.5 Gigabit PHY"
+       help
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -50,6 +50,7 @@ obj-y                                += $(sfp-obj-y) $(sfp-obj-m)
+ obj-$(CONFIG_ADIN_PHY)                += adin.o
+ obj-$(CONFIG_ADIN1100_PHY)    += adin1100.o
+ obj-$(CONFIG_AIROHA_EN8801SC_PHY)   += en8801sc.o
++obj-$(CONFIG_AIR_AN8855_PHY)          += air_an8855.o
+ obj-$(CONFIG_AIR_EN8811H_PHY)   += air_en8811h.o
+ obj-$(CONFIG_AMD_PHY)         += amd.o
+ obj-$(CONFIG_AQUANTIA_PHY)    += aquantia/
+--- a/drivers/nvmem/Kconfig
++++ b/drivers/nvmem/Kconfig
+@@ -29,6 +29,17 @@ source "drivers/nvmem/layouts/Kconfig"
+ # Devices
++config NVMEM_AN8855_EFUSE
++      tristate "Airoha AN8855 eFuse support"
++      depends on MFD_AIROHA_AN8855 || COMPILE_TEST
++      help
++        Say y here to enable support for reading eFuses on Airoha AN8855
++        Switch. These are e.g. used to store factory programmed
++        calibration data required for the PHY.
++
++        This driver can also be built as a module. If so, the module will
++        be called nvmem-an8855-efuse.
++
+ config NVMEM_APPLE_EFUSES
+       tristate "Apple eFuse support"
+       depends on ARCH_APPLE || COMPILE_TEST
+--- a/drivers/nvmem/Makefile
++++ b/drivers/nvmem/Makefile
+@@ -10,6 +10,8 @@ nvmem_layouts-y                      := layouts.o
+ obj-y                         += layouts/
+ # Devices
++obj-$(CONFIG_NVMEM_AN8855_EFUSE)      += nvmem-an8855-efuse.o
++nvmem-an8855-efuse-y                  := an8855-efuse.o
+ obj-$(CONFIG_NVMEM_APPLE_EFUSES)      += nvmem-apple-efuses.o
+ nvmem-apple-efuses-y                  := apple-efuses.o
+ obj-$(CONFIG_NVMEM_BCM_OCOTP)         += nvmem-bcm-ocotp.o
diff --git a/target/linux/mediatek/patches-6.6/738-net-phylink-move-phylink_pcs_neg_mode.patch b/target/linux/mediatek/patches-6.6/738-net-phylink-move-phylink_pcs_neg_mode.patch
new file mode 100644 (file)
index 0000000..2860b78
--- /dev/null
@@ -0,0 +1,166 @@
+From 5e5401d6612ef599ad45785b941eebda7effc90f Mon Sep 17 00:00:00 2001\r
+From: "Russell King (Oracle)" <rmk+kernel@armlinux.org.uk>\r
+Date: Thu, 4 Jan 2024 09:47:36 +0000\r
+Subject: [PATCH] net: phylink: move phylink_pcs_neg_mode() into phylink.c\r
+\r
+Move phylink_pcs_neg_mode() from the header file into the .c file since\r
+nothing should be using it.\r
+\r
+Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>\r
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>\r
+Signed-off-by: David S. Miller <davem@davemloft.net>\r
+---\r
+ drivers/net/phy/phylink.c | 66 +++++++++++++++++++++++++++++++++++++++\r
+ include/linux/phylink.h   | 66 ---------------------------------------\r
+ 2 files changed, 66 insertions(+), 66 deletions(-)\r
+\r
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -1150,6 +1150,72 @@ static void phylink_pcs_an_restart(struc
+               pl->pcs->ops->pcs_an_restart(pl->pcs);
+ }
++/**
++ * phylink_pcs_neg_mode() - helper to determine PCS inband mode
++ * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
++ * @interface: interface mode to be used
++ * @advertising: adertisement ethtool link mode mask
++ *
++ * Determines the negotiation mode to be used by the PCS, and returns
++ * one of:
++ *
++ * - %PHYLINK_PCS_NEG_NONE: interface mode does not support inband
++ * - %PHYLINK_PCS_NEG_OUTBAND: an out of band mode (e.g. reading the PHY)
++ *   will be used.
++ * - %PHYLINK_PCS_NEG_INBAND_DISABLED: inband mode selected but autoneg
++ *   disabled
++ * - %PHYLINK_PCS_NEG_INBAND_ENABLED: inband mode selected and autoneg enabled
++ *
++ * Note: this is for cases where the PCS itself is involved in negotiation
++ * (e.g. Clause 37, SGMII and similar) not Clause 73.
++ */
++static unsigned int phylink_pcs_neg_mode(unsigned int mode,
++                                       phy_interface_t interface,
++                                       const unsigned long *advertising)
++{
++      unsigned int neg_mode;
++
++      switch (interface) {
++      case PHY_INTERFACE_MODE_SGMII:
++      case PHY_INTERFACE_MODE_QSGMII:
++      case PHY_INTERFACE_MODE_QUSGMII:
++      case PHY_INTERFACE_MODE_USXGMII:
++              /* These protocols are designed for use with a PHY which
++               * communicates its negotiation result back to the MAC via
++               * inband communication. Note: there exist PHYs that run
++               * with SGMII but do not send the inband data.
++               */
++              if (!phylink_autoneg_inband(mode))
++                      neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++              else
++                      neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++              break;
++
++      case PHY_INTERFACE_MODE_1000BASEX:
++      case PHY_INTERFACE_MODE_2500BASEX:
++              /* 1000base-X is designed for use media-side for Fibre
++               * connections, and thus the Autoneg bit needs to be
++               * taken into account. We also do this for 2500base-X
++               * as well, but drivers may not support this, so may
++               * need to override this.
++               */
++              if (!phylink_autoneg_inband(mode))
++                      neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++              else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
++                                         advertising))
++                      neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++              else
++                      neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
++              break;
++
++      default:
++              neg_mode = PHYLINK_PCS_NEG_NONE;
++              break;
++      }
++
++      return neg_mode;
++}
++
+ static void phylink_major_config(struct phylink *pl, bool restart,
+                                 const struct phylink_link_state *state)
+ {
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -99,72 +99,6 @@ static inline bool phylink_autoneg_inban
+ }
+ /**
+- * phylink_pcs_neg_mode() - helper to determine PCS inband mode
+- * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
+- * @interface: interface mode to be used
+- * @advertising: adertisement ethtool link mode mask
+- *
+- * Determines the negotiation mode to be used by the PCS, and returns
+- * one of:
+- *
+- * - %PHYLINK_PCS_NEG_NONE: interface mode does not support inband
+- * - %PHYLINK_PCS_NEG_OUTBAND: an out of band mode (e.g. reading the PHY)
+- *   will be used.
+- * - %PHYLINK_PCS_NEG_INBAND_DISABLED: inband mode selected but autoneg
+- *   disabled
+- * - %PHYLINK_PCS_NEG_INBAND_ENABLED: inband mode selected and autoneg enabled
+- *
+- * Note: this is for cases where the PCS itself is involved in negotiation
+- * (e.g. Clause 37, SGMII and similar) not Clause 73.
+- */
+-static inline unsigned int phylink_pcs_neg_mode(unsigned int mode,
+-                                              phy_interface_t interface,
+-                                              const unsigned long *advertising)
+-{
+-      unsigned int neg_mode;
+-
+-      switch (interface) {
+-      case PHY_INTERFACE_MODE_SGMII:
+-      case PHY_INTERFACE_MODE_QSGMII:
+-      case PHY_INTERFACE_MODE_QUSGMII:
+-      case PHY_INTERFACE_MODE_USXGMII:
+-              /* These protocols are designed for use with a PHY which
+-               * communicates its negotiation result back to the MAC via
+-               * inband communication. Note: there exist PHYs that run
+-               * with SGMII but do not send the inband data.
+-               */
+-              if (!phylink_autoneg_inband(mode))
+-                      neg_mode = PHYLINK_PCS_NEG_OUTBAND;
+-              else
+-                      neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
+-              break;
+-
+-      case PHY_INTERFACE_MODE_1000BASEX:
+-      case PHY_INTERFACE_MODE_2500BASEX:
+-              /* 1000base-X is designed for use media-side for Fibre
+-               * connections, and thus the Autoneg bit needs to be
+-               * taken into account. We also do this for 2500base-X
+-               * as well, but drivers may not support this, so may
+-               * need to override this.
+-               */
+-              if (!phylink_autoneg_inband(mode))
+-                      neg_mode = PHYLINK_PCS_NEG_OUTBAND;
+-              else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+-                                         advertising))
+-                      neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
+-              else
+-                      neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
+-              break;
+-
+-      default:
+-              neg_mode = PHYLINK_PCS_NEG_NONE;
+-              break;
+-      }
+-
+-      return neg_mode;
+-}
+-
+-/**
+  * struct phylink_link_state - link state structure
+  * @advertising: ethtool bitmask containing advertised link modes
+  * @lp_advertising: ethtool bitmask containing link partner advertised link
diff --git a/target/linux/mediatek/patches-6.6/739-net-add-negotiation-of-in-band-capabilities.patch b/target/linux/mediatek/patches-6.6/739-net-add-negotiation-of-in-band-capabilities.patch
new file mode 100644 (file)
index 0000000..44a6aad
--- /dev/null
@@ -0,0 +1,1233 @@
+From: "Russell King (Oracle)" <linux@armlinux.org.uk>
+To: Andrew Lunn <andrew@lunn.ch>, Heiner Kallweit <hkallweit1@gmail.com>
+Cc: Alexander Couzens <lynxis@fe80.eu>,
+       Andrew Lunn <andrew+netdev@lunn.ch>,
+       AngeloGioacchino Del Regno
+       <angelogioacchino.delregno@collabora.com>,
+       Broadcom internal kernel review list
+       <bcm-kernel-feedback-list@broadcom.com>,
+       Daniel Golle <daniel@makrotopia.org>,
+       "David S. Miller" <davem@davemloft.net>,
+       Eric Dumazet <edumazet@google.com>,
+       Florian Fainelli <florian.fainelli@broadcom.com>,
+       Ioana Ciornei <ioana.ciornei@nxp.com>,
+       Jakub Kicinski <kuba@kernel.org>,
+       Jose Abreu <Jose.Abreu@synopsys.com>,
+       linux-arm-kernel@lists.infradead.org,
+       linux-mediatek@lists.infradead.org,
+       Marcin Wojtas <marcin.s.wojtas@gmail.com>,
+       Matthias Brugger <matthias.bgg@gmail.com>,
+       netdev@vger.kernel.org, Paolo Abeni <pabeni@redhat.com>
+Subject: [PATCH RFC net-next 00/16] net: add negotiation of in-band capabilities
+Date: Tue, 26 Nov 2024 09:23:48 +0000  [thread overview]
+Message-ID: <Z0WTpE8wkpjMiv_J@shell.armlinux.org.uk> (raw)
+
+Hi,
+
+Yes, this is one patch over the limit of 15 for netdev - but I think it's
+important to include the last patch to head off review comments like "why
+don't you remove phylink_phy_no_inband() in this series?"
+
+Phylink's handling of in-band has been deficient for a long time, and
+people keep hitting problems with it. Notably, situations with the way-
+to-late standardized 2500Base-X and whether that should or should not
+have in-band enabled. We have also been carrying a hack in the form of
+phylink_phy_no_inband() for a PHY that has been used on a SFP module,
+but has no in-band capabilities, not even for SGMII.
+
+When phylink is trying to operate in in-band mode, this series will look
+at the capabilities of the MAC-side PCS and PHY, and work out whether
+in-band can or should be used, programming the PHY as appropriate. This
+includes in-band bypass mode at the PHY.
+
+We don't... yet... support that on the MAC side PCS, because that
+requires yet more complexity.
+
+Patch 1 passes struct phylink and struct phylink_pcs into
+phylink_pcs_neg_mode() so we can look at more state in this function in
+a future patch.
+
+Patch 2 splits "cur_link_an_mode" (the MLO_AN_* mode) into two separate
+purposes - a requested and an active mode. The active mode is the one
+we will be using for the MAC, which becomes dependent on the result of
+in-band negotiation.
+
+Patch 3 adds debug to phylink_major_config() so we can see what is going
+on with the requested and active AN modes.
+
+Patch 4 adds to phylib a method to get the in-band capabilities of the
+PHY from phylib. Patches 5 and 6 add implementations for BCM84881 and
+some Marvell PHYs found on SFPs.
+
+Patch 7 adds to phylib a method to configure the PHY in-band signalling,
+and patch 8 implements it for those Marvell PHYs that support the method
+in patch 4.
+
+Patch 9 does the same as patch 4 but for the MAC-side PCS, with patches
+10 through 14 adding support to several PCS.
+
+Patch 15 adds the code to phylink_pcs_neg_mode() which looks at the
+capabilities, and works out whether to use in-band or out-band mode for
+driving the link between the MAC PCS and PHY.
+
+Patch 16 removes the phylink_phy_no_inband() hack now that we are
+publishing the in-band capabilities from the BCM84881 PHY driver.
+
+ drivers/net/ethernet/marvell/mvneta.c           |  27 +-
+ drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c |  25 +-
+ drivers/net/pcs/pcs-lynx.c                      |  22 ++
+ drivers/net/pcs/pcs-mtk-lynxi.c                 |  16 ++
+ drivers/net/pcs/pcs-xpcs.c                      |  28 ++
+ drivers/net/phy/bcm84881.c                      |  10 +
+ drivers/net/phy/marvell.c                       |  48 ++++
+ drivers/net/phy/phy.c                           |  52 ++++
+ drivers/net/phy/phylink.c                       | 352 +++++++++++++++++++-----
+ include/linux/phy.h                             |  34 +++
+ include/linux/phylink.h                         |  17 ++
+ 11 files changed, 539 insertions(+), 92 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -56,7 +56,8 @@ struct phylink {
+       struct phy_device *phydev;
+       phy_interface_t link_interface; /* PHY_INTERFACE_xxx */
+       u8 cfg_link_an_mode;            /* MLO_AN_xxx */
+-      u8 cur_link_an_mode;
++      u8 req_link_an_mode;            /* Requested MLO_AN_xxx mode */
++      u8 act_link_an_mode;            /* Active MLO_AN_xxx mode */
+       u8 link_port;                   /* The current non-phy ethtool port */
+       __ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+@@ -74,6 +75,7 @@ struct phylink {
+       struct mutex state_mutex;
+       struct phylink_link_state phy_state;
++      unsigned int phy_ib_mode;
+       struct work_struct resolve;
+       unsigned int pcs_neg_mode;
+       unsigned int pcs_state;
+@@ -175,6 +177,24 @@ static const char *phylink_an_mode_str(u
+       return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
+ }
++static const char *phylink_pcs_mode_str(unsigned int mode)
++{
++      if (!mode)
++              return "none";
++
++      if (mode & PHYLINK_PCS_NEG_OUTBAND)
++              return "outband";
++
++      if (mode & PHYLINK_PCS_NEG_INBAND) {
++              if (mode & PHYLINK_PCS_NEG_ENABLED)
++                      return "inband,an-enabled";
++              else
++                      return "inband,an-disabled";
++      }
++
++      return "unknown";
++}
++
+ static unsigned int phylink_interface_signal_rate(phy_interface_t interface)
+ {
+       switch (interface) {
+@@ -1053,6 +1073,15 @@ static void phylink_resolve_an_pause(str
+       }
+ }
++static unsigned int phylink_pcs_inband_caps(struct phylink_pcs *pcs,
++                                  phy_interface_t interface)
++{
++      if (pcs && pcs->ops->pcs_inband_caps)
++              return pcs->ops->pcs_inband_caps(pcs, interface);
++
++      return 0;
++}
++
+ static void phylink_pcs_pre_config(struct phylink_pcs *pcs,
+                                  phy_interface_t interface)
+ {
+@@ -1106,6 +1135,24 @@ static void phylink_pcs_link_up(struct p
+               pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
+ }
++/* Query inband for a specific interface mode, asking the MAC for the
++ * PCS which will be used to handle the interface mode.
++ */
++static unsigned int phylink_inband_caps(struct phylink *pl,
++                                       phy_interface_t interface)
++{
++      struct phylink_pcs *pcs;
++
++      if (!pl->mac_ops->mac_select_pcs)
++              return 0;
++
++      pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
++      if (!pcs)
++              return 0;
++
++      return phylink_pcs_inband_caps(pcs, interface);
++}
++
+ static void phylink_pcs_poll_stop(struct phylink *pl)
+ {
+       if (pl->cfg_link_an_mode == MLO_AN_INBAND)
+@@ -1132,13 +1179,13 @@ static void phylink_mac_config(struct ph
+       phylink_dbg(pl,
+                   "%s: mode=%s/%s/%s adv=%*pb pause=%02x\n",
+-                  __func__, phylink_an_mode_str(pl->cur_link_an_mode),
++                  __func__, phylink_an_mode_str(pl->act_link_an_mode),
+                   phy_modes(st.interface),
+                   phy_rate_matching_to_str(st.rate_matching),
+                   __ETHTOOL_LINK_MODE_MASK_NBITS, st.advertising,
+                   st.pause);
+-      pl->mac_ops->mac_config(pl->config, pl->cur_link_an_mode, &st);
++      pl->mac_ops->mac_config(pl->config, pl->act_link_an_mode, &st);
+ }
+ static void phylink_pcs_an_restart(struct phylink *pl)
+@@ -1146,13 +1193,14 @@ static void phylink_pcs_an_restart(struc
+       if (pl->pcs && linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+                                        pl->link_config.advertising) &&
+           phy_interface_mode_is_8023z(pl->link_config.interface) &&
+-          phylink_autoneg_inband(pl->cur_link_an_mode))
++          phylink_autoneg_inband(pl->act_link_an_mode))
+               pl->pcs->ops->pcs_an_restart(pl->pcs);
+ }
+ /**
+  * phylink_pcs_neg_mode() - helper to determine PCS inband mode
+- * @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
++ * @pl: a pointer to a &struct phylink returned from phylink_create()
++ * @pcs: a pointer to &struct phylink_pcs
+  * @interface: interface mode to be used
+  * @advertising: adertisement ethtool link mode mask
+  *
+@@ -1169,11 +1217,21 @@ static void phylink_pcs_an_restart(struc
+  * Note: this is for cases where the PCS itself is involved in negotiation
+  * (e.g. Clause 37, SGMII and similar) not Clause 73.
+  */
+-static unsigned int phylink_pcs_neg_mode(unsigned int mode,
+-                                       phy_interface_t interface,
+-                                       const unsigned long *advertising)
++static void phylink_pcs_neg_mode(struct phylink *pl, struct phylink_pcs *pcs,
++                               phy_interface_t interface,
++                               const unsigned long *advertising)
+ {
+-      unsigned int neg_mode;
++      unsigned int pcs_ib_caps = 0;
++      unsigned int phy_ib_caps = 0;
++      unsigned int neg_mode, mode;
++      enum {
++              INBAND_CISCO_SGMII,
++              INBAND_BASEX,
++      } type;
++
++      mode = pl->req_link_an_mode;
++
++      pl->phy_ib_mode = 0;
+       switch (interface) {
+       case PHY_INTERFACE_MODE_SGMII:
+@@ -1185,10 +1243,7 @@ static unsigned int phylink_pcs_neg_mode
+                * inband communication. Note: there exist PHYs that run
+                * with SGMII but do not send the inband data.
+                */
+-              if (!phylink_autoneg_inband(mode))
+-                      neg_mode = PHYLINK_PCS_NEG_OUTBAND;
+-              else
+-                      neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++              type = INBAND_CISCO_SGMII;
+               break;
+       case PHY_INTERFACE_MODE_1000BASEX:
+@@ -1199,21 +1254,143 @@ static unsigned int phylink_pcs_neg_mode
+                * as well, but drivers may not support this, so may
+                * need to override this.
+                */
+-              if (!phylink_autoneg_inband(mode))
++              type = INBAND_BASEX;
++              break;
++
++      default:
++              pl->pcs_neg_mode = PHYLINK_PCS_NEG_NONE;
++              pl->act_link_an_mode = mode;
++              return;
++      }
++
++      if (pcs)
++              pcs_ib_caps = phylink_pcs_inband_caps(pcs, interface);
++
++      if (pl->phydev)
++              phy_ib_caps = phy_inband_caps(pl->phydev, interface);
++
++      phylink_dbg(pl, "interface %s inband modes: pcs=%02x phy=%02x\n",
++                  phy_modes(interface), pcs_ib_caps, phy_ib_caps);
++
++      if (!phylink_autoneg_inband(mode)) {
++              bool pcs_ib_only = false;
++              bool phy_ib_only = false;
++
++              if (pcs_ib_caps && pcs_ib_caps != LINK_INBAND_DISABLE) {
++                      /* PCS supports reporting in-band capabilities, and
++                       * supports more than disable mode.
++                       */
++                      if (pcs_ib_caps & LINK_INBAND_DISABLE)
++                              neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++                      else if (pcs_ib_caps & LINK_INBAND_ENABLE)
++                              pcs_ib_only = true;
++              }
++
++              if (phy_ib_caps && phy_ib_caps != LINK_INBAND_DISABLE) {
++                      /* PHY supports in-band capabilities, and supports
++                       * more than disable mode.
++                       */
++                      if (phy_ib_caps & LINK_INBAND_DISABLE)
++                              pl->phy_ib_mode = LINK_INBAND_DISABLE;
++                      else if (phy_ib_caps & LINK_INBAND_BYPASS)
++                              pl->phy_ib_mode = LINK_INBAND_BYPASS;
++                      else if (phy_ib_caps & LINK_INBAND_ENABLE)
++                              phy_ib_only = true;
++              }
++
++              /* If either the PCS or PHY requires inband to be enabled,
++               * this is an invalid configuration. Provide a diagnostic
++               * message for this case, but don't try to force the issue.
++               */
++              if (pcs_ib_only || phy_ib_only)
++                      phylink_warn(pl,
++                                   "firmware wants %s mode, but %s%s%s requires inband\n",
++                                   phylink_an_mode_str(mode),
++                                   pcs_ib_only ? "PCS" : "",
++                                   pcs_ib_only && phy_ib_only ? " and " : "",
++                                   phy_ib_only ? "PHY" : "");
++
++              neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++      } else if (type == INBAND_CISCO_SGMII || pl->phydev) {
++              /* For SGMII modes which are designed to be used with PHYs, or
++               * Base-X with a PHY, we try to use in-band mode where-ever
++               * possible. However, there are some PHYs e.g. BCM84881 which
++               * do not support in-band.
++               */
++              const unsigned int inband_ok = LINK_INBAND_ENABLE |
++                                             LINK_INBAND_BYPASS;
++              const unsigned int outband_ok = LINK_INBAND_DISABLE |
++                                              LINK_INBAND_BYPASS;
++              /* PCS  PHY
++               * D E  D E
++               * 0 0  0 0     no information                  inband enabled
++               * 1 0  0 0     pcs doesn't support             outband
++               * 0 1  0 0     pcs required                    inband enabled
++               * 1 1  0 0     pcs optional                    inband enabled
++               * 0 0  1 0     phy doesn't support             outband
++               * 1 0  1 0     pcs+phy doesn't support         outband
++               * 0 1  1 0     pcs required, phy doesn't support, invalid
++               * 1 1  1 0     pcs optional, phy doesn't support, outband
++               * 0 0  0 1     phy required                    inband enabled
++               * 1 0  0 1     pcs doesn't support, phy required, invalid
++               * 0 1  0 1     pcs+phy required                inband enabled
++               * 1 1  0 1     pcs optional, phy required      inband enabled
++               * 0 0  1 1     phy optional                    inband enabled
++               * 1 0  1 1     pcs doesn't support, phy optional, outband
++               * 0 1  1 1     pcs required, phy optional      inband enabled
++               * 1 1  1 1     pcs+phy optional                inband enabled
++               */
++              if ((!pcs_ib_caps || pcs_ib_caps & inband_ok) &&
++                  (!phy_ib_caps || phy_ib_caps & inband_ok)) {
++                      /* In-band supported or unknown at both ends. Enable
++                       * in-band mode with or without bypass at the PHY.
++                       */
++                      if (phy_ib_caps & LINK_INBAND_ENABLE)
++                              pl->phy_ib_mode = LINK_INBAND_ENABLE;
++                      else if (phy_ib_caps & LINK_INBAND_BYPASS)
++                              pl->phy_ib_mode = LINK_INBAND_BYPASS;
++
++                      neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++              } else if ((!pcs_ib_caps || pcs_ib_caps & outband_ok) &&
++                         (!phy_ib_caps || phy_ib_caps & outband_ok)) {
++                      /* Either in-band not supported at at least one end.
++                       * In-band bypass at the other end is possible.
++                       */
++                      if (phy_ib_caps & LINK_INBAND_DISABLE)
++                              pl->phy_ib_mode = LINK_INBAND_DISABLE;
++                      else if (phy_ib_caps & LINK_INBAND_BYPASS)
++                              pl->phy_ib_mode = LINK_INBAND_BYPASS;
++
+                       neg_mode = PHYLINK_PCS_NEG_OUTBAND;
++                      if (pl->phydev)
++                              mode = MLO_AN_PHY;
++              } else {
++                      /* invalid */
++                      phylink_warn(pl, "%s: incompatible in-band capabilities, trying in-band",
++                                   phy_modes(interface));
++                      neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
++              }
++      } else {
++              /* For Base-X without a PHY */
++              if (pcs_ib_caps == LINK_INBAND_DISABLE)
++                      /* If the PCS doesn't support inband, then inband must
++                       * be disabled.
++                       */
++                      neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
++              else if (pcs_ib_caps == LINK_INBAND_ENABLE)
++                      /* If the PCS requires inband, then inband must always
++                       * be enabled.
++                       */
++                      neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
+               else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+                                          advertising))
+                       neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
+               else
+                       neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
+-              break;
+-
+-      default:
+-              neg_mode = PHYLINK_PCS_NEG_NONE;
+-              break;
+       }
+-      return neg_mode;
++      pl->pcs_neg_mode = neg_mode;
++      pl->act_link_an_mode = mode;
+ }
+ static void phylink_major_config(struct phylink *pl, bool restart,
+@@ -1225,11 +1402,9 @@ static void phylink_major_config(struct
+       unsigned int neg_mode;
+       int err;
+-      phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
+-
+-      pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
+-                                              state->interface,
+-                                              state->advertising);
++      phylink_dbg(pl, "major config, requested %s/%s\n",
++                  phylink_an_mode_str(pl->req_link_an_mode),
++                  phy_modes(state->interface));
+       if (pl->using_mac_select_pcs) {
+               pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
+@@ -1243,10 +1418,17 @@ static void phylink_major_config(struct
+               pcs_changed = pcs && pl->pcs != pcs;
+       }
++      phylink_pcs_neg_mode(pl, pcs, state->interface, state->advertising);
++
++      phylink_dbg(pl, "major config, active %s/%s/%s\n",
++                  phylink_an_mode_str(pl->act_link_an_mode),
++                  phylink_pcs_mode_str(pl->pcs_neg_mode),
++                  phy_modes(state->interface));
++
+       phylink_pcs_poll_stop(pl);
+       if (pl->mac_ops->mac_prepare) {
+-              err = pl->mac_ops->mac_prepare(pl->config, pl->cur_link_an_mode,
++              err = pl->mac_ops->mac_prepare(pl->config, pl->act_link_an_mode,
+                                              state->interface);
+               if (err < 0) {
+                       phylink_err(pl, "mac_prepare failed: %pe\n",
+@@ -1280,7 +1462,7 @@ static void phylink_major_config(struct
+       if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed)
+               phylink_pcs_enable(pl->pcs);
+-      neg_mode = pl->cur_link_an_mode;
++      neg_mode = pl->act_link_an_mode;
+       if (pl->pcs && pl->pcs->neg_mode)
+               neg_mode = pl->pcs_neg_mode;
+@@ -1296,13 +1478,20 @@ static void phylink_major_config(struct
+               phylink_pcs_an_restart(pl);
+       if (pl->mac_ops->mac_finish) {
+-              err = pl->mac_ops->mac_finish(pl->config, pl->cur_link_an_mode,
++              err = pl->mac_ops->mac_finish(pl->config, pl->act_link_an_mode,
+                                             state->interface);
+               if (err < 0)
+                       phylink_err(pl, "mac_finish failed: %pe\n",
+                                   ERR_PTR(err));
+       }
++      if (pl->phydev && pl->phy_ib_mode) {
++              err = phy_config_inband(pl->phydev, pl->phy_ib_mode);
++              if (err < 0)
++                      phylink_err(pl, "phy_config_inband: %pe\n",
++                                  ERR_PTR(err));
++      }
++
+       if (pl->sfp_bus) {
+               rate_kbd = phylink_interface_signal_rate(state->interface);
+               if (rate_kbd)
+@@ -1327,17 +1516,16 @@ static int phylink_change_inband_advert(
+               return 0;
+       phylink_dbg(pl, "%s: mode=%s/%s adv=%*pb pause=%02x\n", __func__,
+-                  phylink_an_mode_str(pl->cur_link_an_mode),
++                  phylink_an_mode_str(pl->req_link_an_mode),
+                   phy_modes(pl->link_config.interface),
+                   __ETHTOOL_LINK_MODE_MASK_NBITS, pl->link_config.advertising,
+                   pl->link_config.pause);
+       /* Recompute the PCS neg mode */
+-      pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
+-                                      pl->link_config.interface,
+-                                      pl->link_config.advertising);
++      phylink_pcs_neg_mode(pl, pl->pcs, pl->link_config.interface,
++                           pl->link_config.advertising);
+-      neg_mode = pl->cur_link_an_mode;
++      neg_mode = pl->act_link_an_mode;
+       if (pl->pcs->neg_mode)
+               neg_mode = pl->pcs_neg_mode;
+@@ -1402,7 +1590,7 @@ static void phylink_mac_initial_config(s
+ {
+       struct phylink_link_state link_state;
+-      switch (pl->cur_link_an_mode) {
++      switch (pl->req_link_an_mode) {
+       case MLO_AN_PHY:
+               link_state = pl->phy_state;
+               break;
+@@ -1476,14 +1664,14 @@ static void phylink_link_up(struct phyli
+       pl->cur_interface = link_state.interface;
+-      neg_mode = pl->cur_link_an_mode;
++      neg_mode = pl->act_link_an_mode;
+       if (pl->pcs && pl->pcs->neg_mode)
+               neg_mode = pl->pcs_neg_mode;
+       phylink_pcs_link_up(pl->pcs, neg_mode, pl->cur_interface, speed,
+                           duplex);
+-      pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode,
++      pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->act_link_an_mode,
+                                pl->cur_interface, speed, duplex,
+                                !!(link_state.pause & MLO_PAUSE_TX), rx_pause);
+@@ -1503,7 +1691,7 @@ static void phylink_link_down(struct phy
+       if (ndev)
+               netif_carrier_off(ndev);
+-      pl->mac_ops->mac_link_down(pl->config, pl->cur_link_an_mode,
++      pl->mac_ops->mac_link_down(pl->config, pl->act_link_an_mode,
+                                  pl->cur_interface);
+       phylink_info(pl, "Link is Down\n");
+ }
+@@ -1530,7 +1718,7 @@ static void phylink_resolve(struct work_
+               link_state.link = false;
+               retrigger = true;
+       } else {
+-              switch (pl->cur_link_an_mode) {
++              switch (pl->act_link_an_mode) {
+               case MLO_AN_PHY:
+                       link_state = pl->phy_state;
+                       phylink_apply_manual_flow(pl, &link_state);
+@@ -1773,7 +1961,7 @@ struct phylink *phylink_create(struct ph
+               }
+       }
+-      pl->cur_link_an_mode = pl->cfg_link_an_mode;
++      pl->req_link_an_mode = pl->cfg_link_an_mode;
+       ret = phylink_register_sfp(pl, fwnode);
+       if (ret < 0) {
+@@ -2236,7 +2424,7 @@ void phylink_start(struct phylink *pl)
+       ASSERT_RTNL();
+       phylink_info(pl, "configuring for %s/%s link mode\n",
+-                   phylink_an_mode_str(pl->cur_link_an_mode),
++                   phylink_an_mode_str(pl->req_link_an_mode),
+                    phy_modes(pl->link_config.interface));
+       /* Always set the carrier off */
+@@ -2495,7 +2683,7 @@ int phylink_ethtool_ksettings_get(struct
+       linkmode_copy(kset->link_modes.supported, pl->supported);
+-      switch (pl->cur_link_an_mode) {
++      switch (pl->act_link_an_mode) {
+       case MLO_AN_FIXED:
+               /* We are using fixed settings. Report these as the
+                * current link settings - and note that these also
+@@ -2526,6 +2714,26 @@ int phylink_ethtool_ksettings_get(struct
+ }
+ EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get);
++static bool phylink_validate_pcs_inband_autoneg(struct phylink *pl,
++                                              phy_interface_t interface,
++                                              unsigned long *adv)
++{
++      unsigned int inband = phylink_inband_caps(pl, interface);
++      unsigned int mask;
++
++      /* If the PCS doesn't implement inband support, be permissive. */
++      if (!inband)
++              return true;
++
++      if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, adv))
++              mask = LINK_INBAND_ENABLE;
++      else
++              mask = LINK_INBAND_DISABLE;
++
++      /* Check whether the PCS implements the required mode */
++      return !!(inband & mask);
++}
++
+ /**
+  * phylink_ethtool_ksettings_set() - set the link settings
+  * @pl: a pointer to a &struct phylink returned from phylink_create()
+@@ -2587,7 +2795,7 @@ int phylink_ethtool_ksettings_set(struct
+               /* If we have a fixed link, refuse to change link parameters.
+                * If the link parameters match, accept them but do nothing.
+                */
+-              if (pl->cur_link_an_mode == MLO_AN_FIXED) {
++              if (pl->req_link_an_mode == MLO_AN_FIXED) {
+                       if (s->speed != pl->link_config.speed ||
+                           s->duplex != pl->link_config.duplex)
+                               return -EINVAL;
+@@ -2603,7 +2811,7 @@ int phylink_ethtool_ksettings_set(struct
+                * is our default case) but do not allow the advertisement to
+                * be changed. If the advertisement matches, simply return.
+                */
+-              if (pl->cur_link_an_mode == MLO_AN_FIXED) {
++              if (pl->req_link_an_mode == MLO_AN_FIXED) {
+                       if (!linkmode_equal(config.advertising,
+                                           pl->link_config.advertising))
+                               return -EINVAL;
+@@ -2643,7 +2851,7 @@ int phylink_ethtool_ksettings_set(struct
+               linkmode_copy(support, pl->supported);
+               if (phylink_validate(pl, support, &config)) {
+                       phylink_err(pl, "validation of %s/%s with support %*pb failed\n",
+-                                  phylink_an_mode_str(pl->cur_link_an_mode),
++                                  phylink_an_mode_str(pl->req_link_an_mode),
+                                   phy_modes(config.interface),
+                                   __ETHTOOL_LINK_MODE_MASK_NBITS, support);
+                       return -EINVAL;
+@@ -2661,6 +2869,13 @@ int phylink_ethtool_ksettings_set(struct
+           phylink_is_empty_linkmode(config.advertising))
+               return -EINVAL;
++      /* Validate the autonegotiation state. We don't have a PHY in this
++       * situation, so the PCS is the media-facing entity.
++       */
++      if (!phylink_validate_pcs_inband_autoneg(pl, config.interface,
++                                               config.advertising))
++              return -EINVAL;
++
+       mutex_lock(&pl->state_mutex);
+       pl->link_config.speed = config.speed;
+       pl->link_config.duplex = config.duplex;
+@@ -2743,7 +2958,7 @@ int phylink_ethtool_set_pauseparam(struc
+       ASSERT_RTNL();
+-      if (pl->cur_link_an_mode == MLO_AN_FIXED)
++      if (pl->req_link_an_mode == MLO_AN_FIXED)
+               return -EOPNOTSUPP;
+       if (!phylink_test(pl->supported, Pause) &&
+@@ -3007,7 +3222,7 @@ static int phylink_mii_read(struct phyli
+       struct phylink_link_state state;
+       int val = 0xffff;
+-      switch (pl->cur_link_an_mode) {
++      switch (pl->act_link_an_mode) {
+       case MLO_AN_FIXED:
+               if (phy_id == 0) {
+                       phylink_get_fixed_state(pl, &state);
+@@ -3032,7 +3247,7 @@ static int phylink_mii_read(struct phyli
+ static int phylink_mii_write(struct phylink *pl, unsigned int phy_id,
+                            unsigned int reg, unsigned int val)
+ {
+-      switch (pl->cur_link_an_mode) {
++      switch (pl->act_link_an_mode) {
+       case MLO_AN_FIXED:
+               break;
+@@ -3202,10 +3417,11 @@ static phy_interface_t phylink_choose_sf
+       return interface;
+ }
+-static void phylink_sfp_set_config(struct phylink *pl, u8 mode,
++static void phylink_sfp_set_config(struct phylink *pl,
+                                  unsigned long *supported,
+                                  struct phylink_link_state *state)
+ {
++      u8 mode = MLO_AN_INBAND;
+       bool changed = false;
+       phylink_dbg(pl, "requesting link mode %s/%s with support %*pb\n",
+@@ -3222,9 +3438,9 @@ static void phylink_sfp_set_config(struc
+               changed = true;
+       }
+-      if (pl->cur_link_an_mode != mode ||
++      if (pl->req_link_an_mode != mode ||
+           pl->link_config.interface != state->interface) {
+-              pl->cur_link_an_mode = mode;
++              pl->req_link_an_mode = mode;
+               pl->link_config.interface = state->interface;
+               changed = true;
+@@ -3239,8 +3455,7 @@ static void phylink_sfp_set_config(struc
+               phylink_mac_initial_config(pl, false);
+ }
+-static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
+-                                struct phy_device *phy)
++static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy)
+ {
+       __ETHTOOL_DECLARE_LINK_MODE_MASK(support1);
+       __ETHTOOL_DECLARE_LINK_MODE_MASK(support);
+@@ -3279,8 +3494,7 @@ static int phylink_sfp_config_phy(struct
+       ret = phylink_validate(pl, support1, &config);
+       if (ret) {
+               phylink_err(pl,
+-                          "validation of %s/%s with support %*pb failed: %pe\n",
+-                          phylink_an_mode_str(mode),
++                          "validation of %s with support %*pb failed: %pe\n",
+                           phy_modes(config.interface),
+                           __ETHTOOL_LINK_MODE_MASK_NBITS, support,
+                           ERR_PTR(ret));
+@@ -3289,7 +3503,7 @@ static int phylink_sfp_config_phy(struct
+       pl->link_port = pl->sfp_port;
+-      phylink_sfp_set_config(pl, mode, support, &config);
++      phylink_sfp_set_config(pl, support, &config);
+       return 0;
+ }
+@@ -3345,6 +3559,12 @@ static int phylink_sfp_config_optical(st
+       phylink_dbg(pl, "optical SFP: chosen %s interface\n",
+                   phy_modes(interface));
++      if (!phylink_validate_pcs_inband_autoneg(pl, interface,
++                                               config.advertising)) {
++              phylink_err(pl, "autoneg setting not compatible with PCS");
++              return -EINVAL;
++      }
++
+       config.interface = interface;
+       /* Ignore errors if we're expecting a PHY to attach later */
+@@ -3358,7 +3578,7 @@ static int phylink_sfp_config_optical(st
+       pl->link_port = pl->sfp_port;
+-      phylink_sfp_set_config(pl, MLO_AN_INBAND, pl->sfp_support, &config);
++      phylink_sfp_set_config(pl, pl->sfp_support, &config);
+       return 0;
+ }
+@@ -3429,20 +3649,10 @@ static void phylink_sfp_link_up(void *up
+       phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_LINK);
+ }
+-/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII
+- * or 802.3z control word, so inband will not work.
+- */
+-static bool phylink_phy_no_inband(struct phy_device *phy)
+-{
+-      return phy->is_c45 && phy_id_compare(phy->c45_ids.device_ids[1],
+-                                           0xae025150, 0xfffffff0);
+-}
+-
+ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
+ {
+       struct phylink *pl = upstream;
+       phy_interface_t interface;
+-      u8 mode;
+       int ret;
+       /*
+@@ -3454,17 +3664,12 @@ static int phylink_sfp_connect_phy(void
+        */
+       phy_support_asym_pause(phy);
+-      if (phylink_phy_no_inband(phy))
+-              mode = MLO_AN_PHY;
+-      else
+-              mode = MLO_AN_INBAND;
+-
+       /* Set the PHY's host supported interfaces */
+       phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces,
+                         pl->config->supported_interfaces);
+       /* Do the initial configuration */
+-      ret = phylink_sfp_config_phy(pl, mode, phy);
++      ret = phylink_sfp_config_phy(pl, phy);
+       if (ret < 0)
+               return ret;
+--- a/drivers/net/phy/phy.c
++++ b/drivers/net/phy/phy.c
+@@ -973,6 +973,58 @@ static int phy_check_link_status(struct
+ }
+ /**
++ * phy_inband_caps - query which in-band signalling modes are supported
++ * @phydev: a pointer to a &struct phy_device
++ * @interface: the interface mode for the PHY
++ *
++ * Returns zero if it is unknown what in-band signalling is supported by the
++ * PHY (e.g. because the PHY driver doesn't implement the method.) Otherwise,
++ * returns a bit mask of the LINK_INBAND_* values from
++ * &enum link_inband_signalling to describe which inband modes are supported
++ * by the PHY for this interface mode.
++ */
++unsigned int phy_inband_caps(struct phy_device *phydev,
++                           phy_interface_t interface)
++{
++      if (phydev->drv && phydev->drv->inband_caps)
++              return phydev->drv->inband_caps(phydev, interface);
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(phy_inband_caps);
++
++/**
++ * phy_config_inband - configure the desired PHY in-band mode
++ * @phydev: the phy_device struct
++ * @modes: in-band modes to configure
++ *
++ * Description: disables, enables or enables-with-bypass in-band signalling
++ *   between the PHY and host system.
++ *
++ * Returns: zero on success, or negative errno value.
++ */
++int phy_config_inband(struct phy_device *phydev, unsigned int modes)
++{
++      int err;
++
++      if (!!(modes & LINK_INBAND_DISABLE) +
++          !!(modes & LINK_INBAND_ENABLE) +
++          !!(modes & LINK_INBAND_BYPASS) != 1)
++              return -EINVAL;
++
++      mutex_lock(&phydev->lock);
++      if (!phydev->drv)
++              err = -EIO;
++      else if (!phydev->drv->config_inband)
++              err = -EOPNOTSUPP;
++      else
++              err = phydev->drv->config_inband(phydev, modes);
++      mutex_unlock(&phydev->lock);
++
++      return err;
++}
++
++/**
+  * _phy_start_aneg - start auto-negotiation for this PHY device
+  * @phydev: the phy_device struct
+  *
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -800,6 +800,24 @@ struct phy_tdr_config {
+ #define PHY_PAIR_ALL -1
+ /**
++ * enum link_inband_signalling - in-band signalling modes that are supported
++ *
++ * @LINK_INBAND_DISABLE: in-band signalling can be disabled
++ * @LINK_INBAND_ENABLE: in-band signalling can be enabled without bypass
++ * @LINK_INBAND_BYPASS: in-band signalling can be enabled with bypass
++ *
++ * The possible and required bits can only be used if the valid bit is set.
++ * If possible is clear, that means inband signalling can not be used.
++ * Required is only valid when possible is set, and means that inband
++ * signalling must be used.
++ */
++enum link_inband_signalling {
++      LINK_INBAND_DISABLE             = BIT(0),
++      LINK_INBAND_ENABLE              = BIT(1),
++      LINK_INBAND_BYPASS              = BIT(2),
++};
++
++/**
+  * struct phy_plca_cfg - Configuration of the PLCA (Physical Layer Collision
+  * Avoidance) Reconciliation Sublayer.
+  *
+@@ -939,6 +957,19 @@ struct phy_driver {
+       int (*get_features)(struct phy_device *phydev);
+       /**
++       * @inband_caps: query whether in-band is supported for the given PHY
++       * interface mode. Returns a bitmask of bits defined by enum
++       * link_inband_signalling.
++       */
++      unsigned int (*inband_caps)(struct phy_device *phydev,
++                                  phy_interface_t interface);
++
++      /**
++       * @config_inband: configure in-band mode for the PHY
++       */
++      int (*config_inband)(struct phy_device *phydev, unsigned int modes);
++
++      /**
+        * @get_rate_matching: Get the supported type of rate matching for a
+        * particular phy interface. This is used by phy consumers to determine
+        * whether to advertise lower-speed modes for that interface. It is
+@@ -1774,6 +1805,9 @@ void phy_stop(struct phy_device *phydev)
+ int phy_config_aneg(struct phy_device *phydev);
+ int phy_start_aneg(struct phy_device *phydev);
+ int phy_aneg_done(struct phy_device *phydev);
++unsigned int phy_inband_caps(struct phy_device *phydev,
++                           phy_interface_t interface);
++int phy_config_inband(struct phy_device *phydev, unsigned int modes);
+ int phy_speed_down(struct phy_device *phydev, bool sync);
+ int phy_speed_up(struct phy_device *phydev);
+ bool phy_check_valid(int speed, int duplex, unsigned long *features);
+--- a/drivers/net/phy/bcm84881.c
++++ b/drivers/net/phy/bcm84881.c
+@@ -223,11 +223,21 @@ static int bcm84881_read_status(struct p
+       return genphy_c45_read_mdix(phydev);
+ }
++/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII
++ * or 802.3z control word, so inband will not work.
++ */
++static unsigned int bcm84881_inband_caps(struct phy_device *phydev,
++                                       phy_interface_t interface)
++{
++      return LINK_INBAND_DISABLE;
++}
++
+ static struct phy_driver bcm84881_drivers[] = {
+       {
+               .phy_id         = 0xae025150,
+               .phy_id_mask    = 0xfffffff0,
+               .name           = "Broadcom BCM84881",
++              .inband_caps    = bcm84881_inband_caps,
+               .config_init    = bcm84881_config_init,
+               .probe          = bcm84881_probe,
+               .get_features   = bcm84881_get_features,
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -673,6 +673,48 @@ static int marvell_config_aneg_fiber(str
+       return genphy_check_and_restart_aneg(phydev, changed);
+ }
++static unsigned int m88e1111_inband_caps(struct phy_device *phydev,
++                                       phy_interface_t interface)
++{
++      /* In 1000base-X and SGMII modes, the inband mode can be changed
++       * through the Fibre page BMCR ANENABLE bit.
++       */
++      if (interface == PHY_INTERFACE_MODE_1000BASEX ||
++          interface == PHY_INTERFACE_MODE_SGMII)
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE |
++                     LINK_INBAND_BYPASS;
++
++      return 0;
++}
++
++static int m88e1111_config_inband(struct phy_device *phydev, unsigned int modes)
++{
++      u16 extsr, bmcr;
++      int err;
++
++      if (phydev->interface != PHY_INTERFACE_MODE_1000BASEX &&
++          phydev->interface != PHY_INTERFACE_MODE_SGMII)
++              return -EINVAL;
++
++      if (modes == LINK_INBAND_BYPASS)
++              extsr = MII_M1111_HWCFG_SERIAL_AN_BYPASS;
++      else
++              extsr = 0;
++
++      if (modes == LINK_INBAND_DISABLE)
++              bmcr = 0;
++      else
++              bmcr = BMCR_ANENABLE;
++
++      err = phy_modify(phydev, MII_M1111_PHY_EXT_SR,
++                       MII_M1111_HWCFG_SERIAL_AN_BYPASS, extsr);
++      if (err < 0)
++              return extsr;
++
++      return phy_modify_paged(phydev, MII_MARVELL_FIBER_PAGE, MII_BMCR,
++                              BMCR_ANENABLE, bmcr);
++}
++
+ static int m88e1111_config_aneg(struct phy_device *phydev)
+ {
+       int extsr = phy_read(phydev, MII_M1111_PHY_EXT_SR);
+@@ -3292,6 +3334,8 @@ static struct phy_driver marvell_drivers
+               .name = "Marvell 88E1112",
+               /* PHY_GBIT_FEATURES */
+               .probe = marvell_probe,
++              .inband_caps = m88e1111_inband_caps,
++              .config_inband = m88e1111_config_inband,
+               .config_init = m88e1112_config_init,
+               .config_aneg = marvell_config_aneg,
+               .config_intr = marvell_config_intr,
+@@ -3312,6 +3356,8 @@ static struct phy_driver marvell_drivers
+               .name = "Marvell 88E1111",
+               /* PHY_GBIT_FEATURES */
+               .probe = marvell_probe,
++              .inband_caps = m88e1111_inband_caps,
++              .config_inband = m88e1111_config_inband,
+               .config_init = m88e1111gbe_config_init,
+               .config_aneg = m88e1111_config_aneg,
+               .read_status = marvell_read_status,
+@@ -3333,6 +3379,8 @@ static struct phy_driver marvell_drivers
+               .name = "Marvell 88E1111 (Finisar)",
+               /* PHY_GBIT_FEATURES */
+               .probe = marvell_probe,
++              .inband_caps = m88e1111_inband_caps,
++              .config_inband = m88e1111_config_inband,
+               .config_init = m88e1111gbe_config_init,
+               .config_aneg = m88e1111_config_aneg,
+               .read_status = marvell_read_status,
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -432,6 +432,7 @@ struct phylink_pcs {
+ /**
+  * struct phylink_pcs_ops - MAC PCS operations structure.
+  * @pcs_validate: validate the link configuration.
++ * @pcs_inband_caps: query inband support for interface mode.
+  * @pcs_enable: enable the PCS.
+  * @pcs_disable: disable the PCS.
+  * @pcs_pre_config: pre-mac_config method (for errata)
+@@ -445,6 +446,8 @@ struct phylink_pcs {
+ struct phylink_pcs_ops {
+       int (*pcs_validate)(struct phylink_pcs *pcs, unsigned long *supported,
+                           const struct phylink_link_state *state);
++      unsigned int (*pcs_inband_caps)(struct phylink_pcs *pcs,
++                                      phy_interface_t interface);
+       int (*pcs_enable)(struct phylink_pcs *pcs);
+       void (*pcs_disable)(struct phylink_pcs *pcs);
+       void (*pcs_pre_config)(struct phylink_pcs *pcs,
+@@ -481,6 +484,20 @@ int pcs_validate(struct phylink_pcs *pcs
+                const struct phylink_link_state *state);
+ /**
++ * pcs_inband_caps - query PCS in-band capabilities for interface mode.
++ * @pcs: a pointer to a &struct phylink_pcs.
++ * @interface: interface mode to be queried
++ *
++ * Returns zero if it is unknown what in-band signalling is supported by the
++ * PHY (e.g. because the PHY driver doesn't implement the method.) Otherwise,
++ * returns a bit mask of the LINK_INBAND_* values from
++ * &enum link_inband_signalling to describe which inband modes are supported
++ * for this interface mode.
++ */
++unsigned int pcs_inband_caps(struct phylink_pcs *pcs,
++                           phy_interface_t interface);
++
++/**
+  * pcs_enable() - enable the PCS.
+  * @pcs: a pointer to a &struct phylink_pcs.
+  */
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3959,20 +3959,27 @@ static struct mvneta_port *mvneta_pcs_to
+       return container_of(pcs, struct mvneta_port, phylink_pcs);
+ }
+-static int mvneta_pcs_validate(struct phylink_pcs *pcs,
+-                             unsigned long *supported,
+-                             const struct phylink_link_state *state)
++static unsigned int mvneta_pcs_inband_caps(struct phylink_pcs *pcs,
++                                         phy_interface_t interface)
+ {
+-      /* We only support QSGMII, SGMII, 802.3z and RGMII modes.
+-       * When in 802.3z mode, we must have AN enabled:
++      /* When operating in an 802.3z mode, we must have AN enabled:
+        * "Bit 2 Field InBandAnEn In-band Auto-Negotiation enable. ...
+        * When <PortType> = 1 (1000BASE-X) this field must be set to 1."
++       * Therefore, inband is "required".
+        */
+-      if (phy_interface_mode_is_8023z(state->interface) &&
+-          !phylink_test(state->advertising, Autoneg))
+-              return -EINVAL;
++      if (phy_interface_mode_is_8023z(interface))
++              return LINK_INBAND_ENABLE;
+-      return 0;
++      /* QSGMII, SGMII and RGMII can be configured to use inband
++       * signalling of the AN result. Indicate these as "possible".
++       */
++      if (interface == PHY_INTERFACE_MODE_SGMII ||
++          interface == PHY_INTERFACE_MODE_QSGMII ||
++          phy_interface_mode_is_rgmii(interface))
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++      /* For any other modes, indicate that inband is not supported. */
++      return LINK_INBAND_DISABLE;
+ }
+ static void mvneta_pcs_get_state(struct phylink_pcs *pcs,
+@@ -4070,7 +4077,7 @@ static void mvneta_pcs_an_restart(struct
+ }
+ static const struct phylink_pcs_ops mvneta_phylink_pcs_ops = {
+-      .pcs_validate = mvneta_pcs_validate,
++      .pcs_inband_caps = mvneta_pcs_inband_caps,
+       .pcs_get_state = mvneta_pcs_get_state,
+       .pcs_config = mvneta_pcs_config,
+       .pcs_an_restart = mvneta_pcs_an_restart,
+--- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
++++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c
+@@ -6214,19 +6214,26 @@ static const struct phylink_pcs_ops mvpp
+       .pcs_config = mvpp2_xlg_pcs_config,
+ };
+-static int mvpp2_gmac_pcs_validate(struct phylink_pcs *pcs,
+-                                 unsigned long *supported,
+-                                 const struct phylink_link_state *state)
++static unsigned int mvpp2_gmac_pcs_inband_caps(struct phylink_pcs *pcs,
++                                             phy_interface_t interface)
+ {
+-      /* When in 802.3z mode, we must have AN enabled:
++      /* When operating in an 802.3z mode, we must have AN enabled:
+        * Bit 2 Field InBandAnEn In-band Auto-Negotiation enable. ...
+        * When <PortType> = 1 (1000BASE-X) this field must be set to 1.
++       * Therefore, inband is "required".
+        */
+-      if (phy_interface_mode_is_8023z(state->interface) &&
+-          !phylink_test(state->advertising, Autoneg))
+-              return -EINVAL;
++      if (phy_interface_mode_is_8023z(interface))
++              return LINK_INBAND_ENABLE;
+-      return 0;
++      /* SGMII and RGMII can be configured to use inband signalling of the
++       * AN result. Indicate these as "possible".
++       */
++      if (interface == PHY_INTERFACE_MODE_SGMII ||
++          phy_interface_mode_is_rgmii(interface))
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++      /* For any other modes, indicate that inband is not supported. */
++      return LINK_INBAND_DISABLE;
+ }
+ static void mvpp2_gmac_pcs_get_state(struct phylink_pcs *pcs,
+@@ -6333,7 +6340,7 @@ static void mvpp2_gmac_pcs_an_restart(st
+ }
+ static const struct phylink_pcs_ops mvpp2_phylink_gmac_pcs_ops = {
+-      .pcs_validate = mvpp2_gmac_pcs_validate,
++      .pcs_inband_caps = mvpp2_gmac_pcs_inband_caps,
+       .pcs_get_state = mvpp2_gmac_pcs_get_state,
+       .pcs_config = mvpp2_gmac_pcs_config,
+       .pcs_an_restart = mvpp2_gmac_pcs_an_restart,
+--- a/drivers/net/pcs/pcs-lynx.c
++++ b/drivers/net/pcs/pcs-lynx.c
+@@ -35,6 +35,27 @@ enum sgmii_speed {
+ #define phylink_pcs_to_lynx(pl_pcs) container_of((pl_pcs), struct lynx_pcs, pcs)
+ #define lynx_to_phylink_pcs(lynx) (&(lynx)->pcs)
++static unsigned int lynx_pcs_inband_caps(struct phylink_pcs *pcs,
++                                       phy_interface_t interface)
++{
++      switch (interface) {
++      case PHY_INTERFACE_MODE_1000BASEX:
++      case PHY_INTERFACE_MODE_SGMII:
++      case PHY_INTERFACE_MODE_QSGMII:
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++      case PHY_INTERFACE_MODE_10GBASER:
++      case PHY_INTERFACE_MODE_2500BASEX:
++              return LINK_INBAND_DISABLE;
++
++      case PHY_INTERFACE_MODE_USXGMII:
++              return LINK_INBAND_ENABLE;
++
++      default:
++              return 0;
++      }
++}
++
+ static void lynx_pcs_get_state_usxgmii(struct mdio_device *pcs,
+                                      struct phylink_link_state *state)
+ {
+@@ -307,6 +328,7 @@ static void lynx_pcs_link_up(struct phyl
+ }
+ static const struct phylink_pcs_ops lynx_pcs_phylink_ops = {
++      .pcs_inband_caps = lynx_pcs_inband_caps,
+       .pcs_get_state = lynx_pcs_get_state,
+       .pcs_config = lynx_pcs_config,
+       .pcs_an_restart = lynx_pcs_an_restart,
+--- a/drivers/net/pcs/pcs-mtk-lynxi.c
++++ b/drivers/net/pcs/pcs-mtk-lynxi.c
+@@ -110,6 +110,21 @@ static struct mtk_pcs_lynxi *pcs_to_mtk_
+       return container_of(pcs, struct mtk_pcs_lynxi, pcs);
+ }
++static unsigned int mtk_pcs_lynxi_inband_caps(struct phylink_pcs *pcs,
++                                            phy_interface_t interface)
++{
++      switch (interface) {
++      case PHY_INTERFACE_MODE_1000BASEX:
++      case PHY_INTERFACE_MODE_2500BASEX:
++      case PHY_INTERFACE_MODE_SGMII:
++      case PHY_INTERFACE_MODE_QSGMII:
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++      default:
++              return 0;
++      }
++}
++
+ static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs,
+                                   struct phylink_link_state *state)
+ {
+@@ -302,6 +317,7 @@ static void mtk_pcs_lynxi_disable(struct
+ }
+ static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
++      .pcs_inband_caps = mtk_pcs_lynxi_inband_caps,
+       .pcs_get_state = mtk_pcs_lynxi_get_state,
+       .pcs_config = mtk_pcs_lynxi_config,
+       .pcs_an_restart = mtk_pcs_lynxi_restart_an,
+--- a/drivers/net/pcs/pcs-xpcs.c
++++ b/drivers/net/pcs/pcs-xpcs.c
+@@ -628,6 +628,33 @@ static int xpcs_validate(struct phylink_
+       return 0;
+ }
++static unsigned int xpcs_inband_caps(struct phylink_pcs *pcs,
++                                   phy_interface_t interface)
++{
++      struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
++      const struct dw_xpcs_compat *compat;
++
++      compat = xpcs_find_compat(xpcs, interface);
++      if (!compat)
++              return 0;
++
++      switch (compat->an_mode) {
++      case DW_AN_C73:
++              return LINK_INBAND_ENABLE;
++
++      case DW_AN_C37_SGMII:
++      case DW_AN_C37_1000BASEX:
++              return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
++
++      case DW_10GBASER:
++      case DW_2500BASEX:
++              return LINK_INBAND_DISABLE;
++
++      default:
++              return 0;
++      }
++}
++
+ void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces)
+ {
+       int i, j;
+@@ -1331,6 +1358,7 @@ static const struct xpcs_id xpcs_id_list
+ static const struct phylink_pcs_ops xpcs_phylink_ops = {
+       .pcs_validate = xpcs_validate,
++      .pcs_inband_caps = xpcs_inband_caps,
+       .pcs_config = xpcs_config,
+       .pcs_get_state = xpcs_get_state,
+       .pcs_an_restart = xpcs_an_restart,