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>;
--- /dev/null
+// 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");
--- /dev/null
+// 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), ®);
+ 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");
--- /dev/null
+/* 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 */
--- /dev/null
+// 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");
--- /dev/null
+// 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");
--- /dev/null
+// 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");
--- /dev/null
+/* 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
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
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
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
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
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
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
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
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
# 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
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
# 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
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
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
+# CONFIG_AIR_AN8855_PHY is not set
# CONFIG_AIROHA_EN8801SC_PHY is not set
CONFIG_ALIGNMENT_TRAP=y
CONFIG_ARCH_32BIT_OFF_T=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
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
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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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,