diff --git a/package/kernel/linux/modules/netdevices.mk b/package/kernel/linux/modules/netdevices.mk index 64a373a80dba33..5100f2e11d24a0 100644 --- a/package/kernel/linux/modules/netdevices.mk +++ b/package/kernel/linux/modules/netdevices.mk @@ -555,6 +555,23 @@ endef $(eval $(call KernelPackage,dsa-tag-dsa)) +define KernelPackage/dsa-an8855 + SUBMENU:=$(NETWORK_DEVICES_MENU) + TITLE:=Airoha AN8855 DSA switch + DEPENDS:=+kmod-dsa + KCONFIG:=CONFIG_NET_DSA_AN8855 \ + CONFIG_NET_DSA_TAG_AIROHA + FILES:=$(LINUX_DIR)/drivers/net/dsa/airoha/an8855/an8855-dsa.ko \ + $(LINUX_DIR)/net/dsa/tag_arht.ko + AUTOLOAD:=$(call AutoLoad,41,an8855-dsa,1) +endef + +define KernelPackage/dsa-an8855/description + Kernel modules for Airoha AN8855 DSA switch +endef + +$(eval $(call KernelPackage,dsa-an8855)) + define KernelPackage/dsa-mv88e6xxx SUBMENU:=$(NETWORK_DEVICES_MENU) TITLE:=Marvell MV88E6XXX DSA Switch diff --git a/target/linux/mediatek/dts/mt7981b-xiaomi-mi-router-common.dtsi b/target/linux/mediatek/dts/mt7981b-xiaomi-mi-router-common.dtsi index d6872395a9017a..de186d0a08c25f 100644 --- a/target/linux/mediatek/dts/mt7981b-xiaomi-mi-router-common.dtsi +++ b/target/linux/mediatek/dts/mt7981b-xiaomi-mi-router-common.dtsi @@ -83,6 +83,16 @@ interrupt-parent = <&pio>; interrupts = <38 IRQ_TYPE_LEVEL_HIGH>; }; + + switch2: switch@1 { + compatible = "airoha,an8855"; + reg = <1>; + reset-gpios = <&pio 39 GPIO_ACTIVE_HIGH>; + interrupt-controller; + #interrupt-cells = <1>; + interrupt-parent = <&pio>; + interrupts = <38 IRQ_TYPE_LEVEL_HIGH>; + }; }; &switch { @@ -124,6 +134,46 @@ }; }; +&switch2 { + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + label = "wan"; + }; + + port@1 { + reg = <1>; + label = "lan2"; + }; + + port@2 { + reg = <2>; + label = "lan3"; + }; + + port@3 { + reg = <3>; + label = "lan4"; + }; + + port@5 { + reg = <5>; + label = "cpu"; + ethernet = <&gmac0>; + phy-mode = "2500base-x"; + + fixed-link { + speed = <2500>; + full-duplex; + pause; + }; + }; + }; +}; + &spi0 { pinctrl-names = "default"; pinctrl-0 = <&spi0_flash_pins>; diff --git a/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/Kconfig b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/Kconfig new file mode 100644 index 00000000000000..fc00f75d1793c9 --- /dev/null +++ b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/Kconfig @@ -0,0 +1,10 @@ + +config NET_DSA_AN8855 + tristate "Airoha AN8855 Ethernet switch support" + depends on NET_DSA + select NET_DSA_TAG_AIROHA + help + AN8855 support 2.5G speed and managed by SMI interface. + This enables support for the Airoha AN8855 Ethernet switch + chip. + To compile this driver as a module, choose M here. diff --git a/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/Makefile b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/Makefile new file mode 100644 index 00000000000000..99c99bd6c45550 --- /dev/null +++ b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for Airoha AN8855 gigabit switch +# +obj-$(CONFIG_NET_DSA_AN8855) += an8855-dsa.o +an8855-dsa-objs := an8855.o an8855_nl.o an8855_phy.o diff --git a/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855.c b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855.c new file mode 100644 index 00000000000000..4653b800381fc9 --- /dev/null +++ b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855.c @@ -0,0 +1,2711 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Airoha AN8855 DSA Switch driver + * Copyright (C) 2023 Min Yao + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "an8855.h" +#include "an8855_nl.h" +#include "an8855_phy.h" + +/* AN8855 driver version */ +#define ARHT_AN8855_DSA_DRIVER_VER "1.0.2" + +#define ARHT_CHIP_NAME "an8855" +#define ARHT_PROC_DIR "air_sw" +#define ARHT_PROC_NODE_DEVICE "device" + +struct proc_dir_entry *proc_an8855_dsa_dir; + +static struct an8855_pcs *pcs_to_an8855_pcs(struct phylink_pcs *pcs) +{ + return container_of(pcs, struct an8855_pcs, pcs); +} + +/* T830 AN8855 Reference Board */ +static const struct an8855_led_cfg led_cfg[] = { +/************************************************************************* + * Enable, LED idx, LED Polarity, LED ON event, LED Blink event LED Freq + ************************************************************************* + */ + /* GPIO0 */ + {1, P4_LED0, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO1 */ + {1, P4_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO2 */ + {1, P0_LED0, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO3 */ + {1, P0_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO4 */ + {1, P1_LED0, LED_LOW, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO5 */ + {1, P1_LED1, LED_LOW, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO6 */ + {0, PHY_LED_MAX, LED_LOW, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO7 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO8 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO9 */ + {1, P2_LED0, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO10 */ + {1, P2_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO11 */ + {1, P3_LED0, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO12 */ + {1, P3_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO13 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO14 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO15 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO16 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO17 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO18 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO19 */ + {0, PHY_LED_MAX, LED_LOW, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO20 */ + {0, PHY_LED_MAX, LED_LOW, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, +}; + +/* String, offset, and register size in bytes if different from 4 bytes */ +static const struct an8855_mib_desc an8855_mib[] = { + MIB_DESC(1, 0x00, "TxDrop"), + MIB_DESC(1, 0x04, "TxCrcErr"), + MIB_DESC(1, 0x08, "TxUnicast"), + MIB_DESC(1, 0x0c, "TxMulticast"), + MIB_DESC(1, 0x10, "TxBroadcast"), + MIB_DESC(1, 0x14, "TxCollision"), + MIB_DESC(1, 0x18, "TxSingleCollision"), + MIB_DESC(1, 0x1c, "TxMultipleCollision"), + MIB_DESC(1, 0x20, "TxDeferred"), + MIB_DESC(1, 0x24, "TxLateCollision"), + MIB_DESC(1, 0x28, "TxExcessiveCollistion"), + MIB_DESC(1, 0x2c, "TxPause"), + MIB_DESC(1, 0x30, "TxPktSz64"), + MIB_DESC(1, 0x34, "TxPktSz65To127"), + MIB_DESC(1, 0x38, "TxPktSz128To255"), + MIB_DESC(1, 0x3c, "TxPktSz256To511"), + MIB_DESC(1, 0x40, "TxPktSz512To1023"), + MIB_DESC(1, 0x44, "TxPktSz1024To1518"), + MIB_DESC(1, 0x48, "TxPktSz1519ToMax"), + MIB_DESC(2, 0x4c, "TxBytes"), + MIB_DESC(1, 0x54, "TxOversizeDrop"), + MIB_DESC(2, 0x58, "TxBadPktBytes"), + MIB_DESC(1, 0x80, "RxDrop"), + MIB_DESC(1, 0x84, "RxFiltering"), + MIB_DESC(1, 0x88, "RxUnicast"), + MIB_DESC(1, 0x8c, "RxMulticast"), + MIB_DESC(1, 0x90, "RxBroadcast"), + MIB_DESC(1, 0x94, "RxAlignErr"), + MIB_DESC(1, 0x98, "RxCrcErr"), + MIB_DESC(1, 0x9c, "RxUnderSizeErr"), + MIB_DESC(1, 0xa0, "RxFragErr"), + MIB_DESC(1, 0xa4, "RxOverSzErr"), + MIB_DESC(1, 0xa8, "RxJabberErr"), + MIB_DESC(1, 0xac, "RxPause"), + MIB_DESC(1, 0xb0, "RxPktSz64"), + MIB_DESC(1, 0xb4, "RxPktSz65To127"), + MIB_DESC(1, 0xb8, "RxPktSz128To255"), + MIB_DESC(1, 0xbc, "RxPktSz256To511"), + MIB_DESC(1, 0xc0, "RxPktSz512To1023"), + MIB_DESC(1, 0xc4, "RxPktSz1024To1518"), + MIB_DESC(1, 0xc8, "RxPktSz1519ToMax"), + MIB_DESC(2, 0xcc, "RxBytes"), + MIB_DESC(1, 0xd4, "RxCtrlDrop"), + MIB_DESC(1, 0xd8, "RxIngressDrop"), + MIB_DESC(1, 0xdc, "RxArlDrop"), + MIB_DESC(1, 0xe0, "FlowControlDrop"), + MIB_DESC(1, 0xe4, "WredDrop"), + MIB_DESC(1, 0xe8, "MirrorDrop"), + MIB_DESC(2, 0xec, "RxBadPktBytes"), + MIB_DESC(1, 0xf4, "RxsFlowSamplingPktDrop"), + MIB_DESC(1, 0xf8, "RxsFlowTotalPktDrop"), + MIB_DESC(1, 0xfc, "PortControlDrop"), +}; + +static int +an8855_mii_write(struct an8855_priv *priv, u32 reg, u32 val) +{ + struct mii_bus *bus = priv->bus; + int ret = 0; + + ret = bus->write(bus, priv->phy_base, 0x1f, 0x4); + ret = bus->write(bus, priv->phy_base, 0x10, 0); + + ret = bus->write(bus, priv->phy_base, 0x11, ((reg >> 16) & 0xFFFF)); + ret = bus->write(bus, priv->phy_base, 0x12, (reg & 0xFFFF)); + + ret = bus->write(bus, priv->phy_base, 0x13, ((val >> 16) & 0xFFFF)); + ret = bus->write(bus, priv->phy_base, 0x14, (val & 0xFFFF)); + + ret = bus->write(bus, priv->phy_base, 0x1f, 0); + + if (ret < 0) { + dev_err(&bus->dev, "failed to write an8855 register\n"); + return ret; + } + + return ret; +} + +static u32 +an8855_mii_read(struct an8855_priv *priv, u32 reg) +{ + struct mii_bus *bus = priv->bus; + u16 lo, hi; + int ret; + + ret = bus->write(bus, priv->phy_base, 0x1f, 0x4); + ret = bus->write(bus, priv->phy_base, 0x10, 0); + + ret = bus->write(bus, priv->phy_base, 0x15, ((reg >> 16) & 0xFFFF)); + ret = bus->write(bus, priv->phy_base, 0x16, (reg & 0xFFFF)); + if (ret < 0) { + dev_err(&bus->dev, "failed to read an8855 register\n"); + return ret; + } + + lo = bus->read(bus, priv->phy_base, 0x18); + hi = bus->read(bus, priv->phy_base, 0x17); + + ret = bus->write(bus, priv->phy_base, 0x1f, 0); + if (ret < 0) { + dev_err(&bus->dev, "failed to read an8855 register\n"); + return ret; + } + + return (hi << 16) | (lo & 0xffff); +} + +void +an8855_write(struct an8855_priv *priv, u32 reg, u32 val) +{ + struct mii_bus *bus = priv->bus; + + mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); + + an8855_mii_write(priv, reg, val); + + mutex_unlock(&bus->mdio_lock); +} + +static u32 +_an8855_read(struct an8855_dummy_poll *p) +{ + struct mii_bus *bus = p->priv->bus; + u32 val; + + mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); + + val = an8855_mii_read(p->priv, p->reg); + + mutex_unlock(&bus->mdio_lock); + + return val; +} + +u32 +an8855_read(struct an8855_priv *priv, u32 reg) +{ + struct an8855_dummy_poll p; + + INIT_AN8855_DUMMY_POLL(&p, priv, reg); + return _an8855_read(&p); +} + +static void +an8855_rmw(struct an8855_priv *priv, u32 reg, u32 mask, u32 set) +{ + struct mii_bus *bus = priv->bus; + u32 val; + + mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); + + val = an8855_mii_read(priv, reg); + val &= ~mask; + val |= set; + an8855_mii_write(priv, reg, val); + + mutex_unlock(&bus->mdio_lock); +} + +static void +an8855_set(struct an8855_priv *priv, u32 reg, u32 val) +{ + an8855_rmw(priv, reg, 0, val); +} + +static void +an8855_clear(struct an8855_priv *priv, u32 reg, u32 val) +{ + an8855_rmw(priv, reg, val, 0); +} + +static int +an8855_fdb_cmd(struct an8855_priv *priv, u32 cmd, u32 *rsp) +{ + u32 val; + int ret; + struct an8855_dummy_poll p; + + /* Set the command operating upon the MAC address entries */ + val = ATC_BUSY | cmd; + an8855_write(priv, AN8855_ATC, val); + + INIT_AN8855_DUMMY_POLL(&p, priv, AN8855_ATC); + ret = readx_poll_timeout(_an8855_read, &p, val, + !(val & ATC_BUSY), 20, 200000); + if (ret < 0) { + dev_err(priv->dev, "reset timeout\n"); + return ret; + } + + if (rsp) + *rsp = val; + + return 0; +} + +static void +an8855_fdb_read(struct an8855_priv *priv, struct an8855_fdb *fdb) +{ + u32 reg[4]; + int i; + + /* Read from ARL table into an array */ + for (i = 0; i < 4; i++) + reg[i] = an8855_read(priv, AN8855_ATRD0 + (i * 4)); + + fdb->live = reg[0] & 0x1; + fdb->type = (reg[0] >> 3) & 0x3; + fdb->ivl = (reg[0] >> 9) & 0x1; + fdb->vid = (reg[0] >> 10) & 0xfff; + fdb->fid = (reg[0] >> 25) & 0xf; + fdb->aging = (reg[1] >> 3) & 0x1ff; + fdb->port_mask = reg[3] & 0xff; + fdb->mac[0] = (reg[2] >> MAC_BYTE_0) & MAC_BYTE_MASK; + fdb->mac[1] = (reg[2] >> MAC_BYTE_1) & MAC_BYTE_MASK; + fdb->mac[2] = (reg[2] >> MAC_BYTE_2) & MAC_BYTE_MASK; + fdb->mac[3] = (reg[2] >> MAC_BYTE_3) & MAC_BYTE_MASK; + fdb->mac[4] = (reg[1] >> MAC_BYTE_4) & MAC_BYTE_MASK; + fdb->mac[5] = (reg[1] >> MAC_BYTE_5) & MAC_BYTE_MASK; + fdb->noarp = !!((reg[0] >> 1) & 0x3); +} + +static void +an8855_fdb_write(struct an8855_priv *priv, u16 vid, + u8 port_mask, const u8 *mac, u8 add) +{ + u32 reg = 0; + + reg |= mac[3] << MAC_BYTE_3; + reg |= mac[2] << MAC_BYTE_2; + reg |= mac[1] << MAC_BYTE_1; + reg |= mac[0] << MAC_BYTE_0; + an8855_write(priv, AN8855_ATA1, reg); + reg = 0; + reg |= mac[5] << MAC_BYTE_5; + reg |= mac[4] << MAC_BYTE_4; + an8855_write(priv, AN8855_ATA2, reg); + reg = 0; + if (add) + reg |= 0x1; + reg |= 0x1 << 15; + reg |= vid << 16; + an8855_write(priv, AN8855_ATWD, reg); + an8855_write(priv, AN8855_ATWD2, port_mask); +} + +static int +an8855_pad_setup(struct dsa_switch *ds, phy_interface_t interface) +{ + return 0; +} + +static void +an8855_mib_reset(struct dsa_switch *ds) +{ + struct an8855_priv *priv = ds->priv; + + an8855_write(priv, AN8855_MIB_CCR, CCR_MIB_FLUSH); + an8855_write(priv, AN8855_MIB_CCR, CCR_MIB_ACTIVATE); +} + +static int +an8855_cl22_read(struct an8855_priv *priv, int port, int regnum) +{ + return mdiobus_read_nested(priv->bus, port, regnum); +} + +static int +an8855_cl22_write(struct an8855_priv *priv, int port, int regnum, u16 val) +{ + return mdiobus_write_nested(priv->bus, port, regnum, val); +} + +static int +an8855_phy_read(struct dsa_switch *ds, int port, int regnum) +{ + struct an8855_priv *priv = ds->priv; + + port += priv->phy_base; + return an8855_cl22_read(ds->priv, port, regnum); +} + +static int +an8855_phy_write(struct dsa_switch *ds, int port, int regnum, + u16 val) +{ + struct an8855_priv *priv = ds->priv; + + port += priv->phy_base; + return an8855_cl22_write(ds->priv, port, regnum, val); +} + +static int +an8855_cl45_read(struct an8855_priv *priv, int port, int devad, int regnum) +{ + an8855_cl22_write(priv, port, 0x0d, devad); + an8855_cl22_write(priv, port, 0x0e, regnum); + an8855_cl22_write(priv, port, 0x0d, devad | (0x4000)); + return an8855_cl22_read(priv, port, 0x0e); +} + +static int +an8855_cl45_write(struct an8855_priv *priv, int port, int devad, int regnum, + u16 val) +{ + an8855_cl22_write(priv, port, 0x0d, devad); + an8855_cl22_write(priv, port, 0x0e, regnum); + an8855_cl22_write(priv, port, 0x0d, devad | (0x4000)); + an8855_cl22_write(priv, port, 0x0e, val); + + return 0; +} + +int +an8855_phy_cl22_read(struct an8855_priv *priv, int port, int regnum) +{ + port += priv->phy_base; + return an8855_cl22_read(priv, port, regnum); +} + +int +an8855_phy_cl22_write(struct an8855_priv *priv, int port, int regnum, + u16 val) +{ + port += priv->phy_base; + return an8855_cl22_write(priv, port, regnum, val); +} + +int +an8855_phy_cl45_read(struct an8855_priv *priv, int port, int devad, int regnum) +{ + port += priv->phy_base; + return an8855_cl45_read(priv, port, devad, regnum); +} + +int +an8855_phy_cl45_write(struct an8855_priv *priv, int port, int devad, int regnum, + u16 val) +{ + port += priv->phy_base; + return an8855_cl45_write(priv, port, devad, regnum, val); +} + +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++) + strncpy(data + i * ETH_GSTRING_LEN, an8855_mib[i].name, + ETH_GSTRING_LEN); +} + +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; + u32 reg, i; + u64 hi; + + for (i = 0; i < ARRAY_SIZE(an8855_mib); i++) { + mib = &an8855_mib[i]; + reg = AN8855_PORT_MIB_COUNTER(port) + mib->offset; + + data[i] = an8855_read(priv, reg); + if (mib->size == 2) { + hi = an8855_read(priv, reg + 4); + data[i] |= hi << 32; + } + } +} + +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 int +an8855_cpu_port_enable(struct dsa_switch *ds, int port) +{ + struct an8855_priv *priv = ds->priv; + + /* Setup max capability of CPU port at first */ + if (priv->info->cpu_port_config) + priv->info->cpu_port_config(ds, port); + + /* Enable Airoha header mode on the cpu port */ + an8855_write(priv, AN8855_PVC_P(port), + PORT_SPEC_REPLACE_MODE | PORT_SPEC_TAG); + + /* Unknown multicast frame forwarding to the cpu port */ + an8855_write(priv, AN8855_UNMF, BIT(port)); + + /* Set CPU port number */ + an8855_rmw(priv, AN8855_MFC, CPU_MASK, CPU_EN | CPU_PORT(port)); + + /* CPU port gets connected to all user ports of + * the switch. + */ + an8855_write(priv, AN8855_PORTMATRIX_P(port), + PORTMATRIX_MATRIX(dsa_user_ports(priv->ds))); + + return 0; +} + +static int +an8855_port_enable(struct dsa_switch *ds, int port, struct phy_device *phy) +{ + struct an8855_priv *priv = ds->priv; + + if (!dsa_is_user_port(ds, port)) + return 0; + + mutex_lock(&priv->reg_mutex); + + /* Allow the user port gets connected to the cpu port and also + * restore the port matrix if the port is the member of a certain + * bridge. + */ + priv->ports[port].pm |= PORTMATRIX_MATRIX(BIT(AN8855_CPU_PORT)); + priv->ports[port].enable = true; + an8855_write(priv, AN8855_PORTMATRIX_P(port), priv->ports[port].pm); + + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +static void +an8855_port_disable(struct dsa_switch *ds, int port) +{ + struct an8855_priv *priv = ds->priv; + + if (!dsa_is_user_port(ds, port)) + return; + + mutex_lock(&priv->reg_mutex); + + /* Clear up all port matrix which could be restored in the next + * enablement for the port. + */ + priv->ports[port].enable = false; + an8855_write(priv, AN8855_PORTMATRIX_P(port), PORTMATRIX_CLR); + + mutex_unlock(&priv->reg_mutex); +} + +static void +an8855_stp_state_set(struct dsa_switch *ds, int port, u8 state) +{ + struct an8855_priv *priv = ds->priv; + u32 stp_state; + + if (dsa_is_unused_port(ds, port)) + return; + + 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; + break; + case BR_STATE_FORWARDING: + default: + stp_state = AN8855_STP_FORWARDING; + break; + } + + an8855_rmw(priv, AN8855_SSP_P(port), FID_PST_MASK, stp_state); +} + +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; + u32 port_bitmap = BIT(AN8855_CPU_PORT); + int i; + + mutex_lock(&priv->reg_mutex); + + for (i = 0; i < AN8855_NUM_PORTS; i++) { + /* Add this port to the port matrix of the other ports in the + * same bridge. If the port is disabled, port matrix is kept + * and not being setup until the port becomes enabled. + */ + if (dsa_is_user_port(ds, i) && i != port) { + if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) + continue; + if (priv->ports[i].enable) + an8855_set(priv, AN8855_PORTMATRIX_P(i), + PORTMATRIX_MATRIX(BIT(port))); + priv->ports[i].pm |= PORTMATRIX_MATRIX(BIT(port)); + + port_bitmap |= BIT(i); + } + } + + /* Add the all other ports to this port matrix. */ + if (priv->ports[port].enable) + an8855_rmw(priv, AN8855_PORTMATRIX_P(port), + PORTMATRIX_MASK, PORTMATRIX_MATRIX(port_bitmap)); + priv->ports[port].pm |= PORTMATRIX_MATRIX(port_bitmap); + + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +static void +an8855_port_set_vlan_unaware(struct dsa_switch *ds, int port) +{ + struct an8855_priv *priv = ds->priv; + bool all_user_ports_removed = true; + int i; + + /* 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. + */ + an8855_rmw(priv, AN8855_PCR_P(port), PCR_PORT_VLAN_MASK, + AN8855_PORT_MATRIX_MODE); + an8855_rmw(priv, AN8855_PVC_P(port), VLAN_ATTR_MASK | PVC_EG_TAG_MASK, + VLAN_ATTR(AN8855_VLAN_TRANSPARENT) | + PVC_EG_TAG(AN8855_VLAN_EG_CONSISTENT)); + + for (i = 0; i < AN8855_NUM_PORTS; i++) { + if (dsa_is_user_port(ds, i) && + dsa_port_is_vlan_filtering(dsa_to_port(ds, i))) { + all_user_ports_removed = false; + break; + } + } + + /* CPU port also does the same thing until all user ports belonging to + * the CPU port get out of VLAN filtering mode. + */ + if (all_user_ports_removed) { + an8855_write(priv, AN8855_PORTMATRIX_P(AN8855_CPU_PORT), + PORTMATRIX_MATRIX(dsa_user_ports(priv->ds))); + an8855_write(priv, AN8855_PVC_P(AN8855_CPU_PORT), + PORT_SPEC_REPLACE_MODE | PORT_SPEC_TAG | + PVC_EG_TAG(AN8855_VLAN_EG_CONSISTENT)); + } +} + +static void +an8855_port_set_vlan_aware(struct dsa_switch *ds, int port) +{ + struct an8855_priv *priv = ds->priv; + + /* Trapped into security mode allows packet forwarding through VLAN + * table lookup. CPU port is set to fallback mode to let untagged + * frames pass through. + */ + if (dsa_is_cpu_port(ds, port)) + an8855_rmw(priv, AN8855_PCR_P(port), PCR_PORT_VLAN_MASK, + AN8855_PORT_FALLBACK_MODE); + else + an8855_rmw(priv, AN8855_PCR_P(port), PCR_PORT_VLAN_MASK, + AN8855_PORT_SECURITY_MODE); + + /* 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. + */ + an8855_rmw(priv, AN8855_PVC_P(port), VLAN_ATTR_MASK | PVC_EG_TAG_MASK, + VLAN_ATTR(AN8855_VLAN_USER) | + PVC_EG_TAG(AN8855_VLAN_EG_DISABLED)); +} + +static void +an8855_port_bridge_leave(struct dsa_switch *ds, int port, + struct dsa_bridge bridge) +{ + struct an8855_priv *priv = ds->priv; + int i; + + mutex_lock(&priv->reg_mutex); + + for (i = 0; i < AN8855_NUM_PORTS; i++) { + /* Remove this port from the port matrix of the other ports + * in the same bridge. If the port is disabled, port matrix + * is kept and not being setup until the port becomes enabled. + */ + if (dsa_is_user_port(ds, i) && i != port) { + if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) + continue; + if (priv->ports[i].enable) + an8855_clear(priv, AN8855_PORTMATRIX_P(i), + PORTMATRIX_MATRIX(BIT(port))); + priv->ports[i].pm &= PORTMATRIX_MATRIX(BIT(port)); + } + } + + /* Set the cpu port to be the only one in the port matrix of + * this port. + */ + if (priv->ports[port].enable) + an8855_rmw(priv, AN8855_PORTMATRIX_P(port), PORTMATRIX_MASK, + PORTMATRIX_MATRIX(BIT(AN8855_CPU_PORT))); + priv->ports[port].pm = PORTMATRIX_MATRIX(BIT(AN8855_CPU_PORT)); + + mutex_unlock(&priv->reg_mutex); +} + +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; + int ret; + u8 port_mask = BIT(port); + + mutex_lock(&priv->reg_mutex); + an8855_fdb_write(priv, vid, port_mask, addr, 1); + 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; + int ret; + u8 port_mask = BIT(port); + + mutex_lock(&priv->reg_mutex); + an8855_fdb_write(priv, vid, port_mask, addr, 0); + 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; + struct an8855_fdb _fdb = { 0 }; + int cnt = 512; + int num = 4; + int index = 0; + bool flag = false; + int banks = 0; + int i = 0; + int ret = 0; + u32 rsp = 0; + + mutex_lock(&priv->reg_mutex); + + an8855_write(priv, AN8855_ATWD2, (0x1 << port)); + ret = an8855_fdb_cmd(priv, ATC_MAT(0xc) | AN8855_FDB_START, &rsp); + if (ret < 0) + goto err; + + index = (rsp >> ATC_HASH) & ATC_HASH_MASK; + if (index == (cnt - 1)) + flag = true; + else + flag = false; + + banks = (rsp >> ATC_HIT) & ATC_HIT_MASK; + if (banks == 0) { + mutex_unlock(&priv->reg_mutex); + return 0; + } + for (i = 0; i < num; i++) { + if ((banks >> i) & 0x1) { + an8855_write(priv, AN8855_ATRDS, i); + udelay(1000); + an8855_fdb_read(priv, &_fdb); + if (!_fdb.live) + continue; + if (_fdb.port_mask & BIT(port)) { + ret = cb(_fdb.mac, _fdb.vid, _fdb.noarp, data); + if (ret < 0) + continue; + } + } + } + while (1) { + if (flag == true) + break; + + ret = + an8855_fdb_cmd(priv, ATC_MAT(0xc) | AN8855_FDB_NEXT, &rsp); + index = (rsp >> ATC_HASH) & ATC_HASH_MASK; + if (index == (cnt - 1)) + flag = true; + else + flag = false; + + banks = (rsp >> ATC_HIT) & ATC_HIT_MASK; + if (banks == 0) { + mutex_unlock(&priv->reg_mutex); + return 0; + } + for (i = 0; i < num; i++) { + if ((banks >> i) & 0x1) { + an8855_write(priv, AN8855_ATRDS, i); + udelay(1000); + an8855_fdb_read(priv, &_fdb); + if (!_fdb.live) + continue; + if (_fdb.port_mask & BIT(port)) { + ret = cb(_fdb.mac, _fdb.vid, _fdb.noarp, + data); + if (ret < 0) + continue; + } + } + } + } + +err: + mutex_unlock(&priv->reg_mutex); + return 0; +} + +static int +an8855_vlan_cmd(struct an8855_priv *priv, enum an8855_vlan_cmd cmd, u16 vid) +{ + struct an8855_dummy_poll p; + u32 val; + int ret; + + if (vid > 0xFFF) { + dev_err(priv->dev, "vid number invalid\n"); + return -EINVAL; + } + + val = VTCR_BUSY | VTCR_FUNC(cmd) | vid; + an8855_write(priv, AN8855_VTCR, val); + + INIT_AN8855_DUMMY_POLL(&p, priv, AN8855_VTCR); + ret = readx_poll_timeout(_an8855_read, &p, val, + !(val & VTCR_BUSY), 20, 200000); + if (ret < 0) { + dev_err(priv->dev, "poll timeout\n"); + return ret; + } + + return 0; +} + +static int +an8855_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering, + struct netlink_ext_ack *extack) +{ + if (vlan_filtering) { + /* 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. + */ + an8855_port_set_vlan_aware(ds, port); + an8855_port_set_vlan_aware(ds, AN8855_CPU_PORT); + } else { + an8855_port_set_vlan_unaware(ds, port); + } + + return 0; +} + +static void +an8855_hw_vlan_add(struct an8855_priv *priv, + struct an8855_hw_vlan_entry *entry) +{ + u8 new_members; + u32 val; + + new_members = entry->old_members | BIT(entry->port) | + BIT(AN8855_CPU_PORT); + + /* Validate the entry with independent learning, create egress tag per + * VLAN and joining the port as one of the port members. + */ + val = + an8855_read(priv, + AN8855_VARD0) & (ETAG_CTRL_MASK << PORT_EG_CTRL_SHIFT); + val |= (IVL_MAC | VTAG_EN | PORT_MEM(new_members) | VLAN_VALID); + an8855_write(priv, AN8855_VAWD0, val); + an8855_write(priv, AN8855_VAWD1, 0); + + /* Decide whether adding tag or not for those outgoing packets from the + * port inside the VLAN. + */ + val = + entry->untagged ? AN8855_VLAN_EGRESS_UNTAG : AN8855_VLAN_EGRESS_TAG; + an8855_rmw(priv, AN8855_VAWD0, + ETAG_CTRL_P_MASK(entry->port) << PORT_EG_CTRL_SHIFT, + ETAG_CTRL_P(entry->port, val) << PORT_EG_CTRL_SHIFT); + + /* 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. + */ + an8855_rmw(priv, AN8855_VAWD0, + ETAG_CTRL_P_MASK(AN8855_CPU_PORT) << PORT_EG_CTRL_SHIFT, + ETAG_CTRL_P(AN8855_CPU_PORT, + AN8855_VLAN_EGRESS_STACK) << PORT_EG_CTRL_SHIFT); +} + +static void +an8855_hw_vlan_del(struct an8855_priv *priv, + struct an8855_hw_vlan_entry *entry) +{ + u8 new_members; + u32 val; + + new_members = entry->old_members & ~BIT(entry->port); + + val = an8855_read(priv, AN8855_VARD0); + if (!(val & VLAN_VALID)) { + dev_err(priv->dev, "Cannot be deleted due to invalid entry\n"); + return; + } + + /* If certain member apart from CPU port is still alive in the VLAN, + * the entry would be kept valid. Otherwise, the entry is got to be + * disabled. + */ + if (new_members && new_members != BIT(AN8855_CPU_PORT)) { + val = IVL_MAC | VTAG_EN | PORT_MEM(new_members) | VLAN_VALID; + an8855_write(priv, AN8855_VAWD0, val); + } else { + an8855_write(priv, AN8855_VAWD0, 0); + an8855_write(priv, AN8855_VAWD1, 0); + } +} + +static void +an8855_hw_vlan_update(struct an8855_priv *priv, u16 vid, + struct an8855_hw_vlan_entry *entry, + an8855_vlan_op vlan_op) +{ + u32 val; + + /* Fetch entry */ + an8855_vlan_cmd(priv, AN8855_VTCR_RD_VID, vid); + + val = an8855_read(priv, AN8855_VARD0); + + entry->old_members = (val >> PORT_MEM_SHFT) & PORT_MEM_MASK; + + /* Manipulate entry */ + vlan_op(priv, entry); + + /* Flush result to hardware */ + an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID, vid); +} + +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_hw_vlan_entry new_entry; + struct an8855_priv *priv = ds->priv; + + mutex_lock(&priv->reg_mutex); + + an8855_hw_vlan_entry_init(&new_entry, port, untagged); + an8855_hw_vlan_update(priv, vlan->vid, &new_entry, an8855_hw_vlan_add); + + if (pvid) { + an8855_rmw(priv, AN8855_PVID_P(port), G0_PORT_VID_MASK, + G0_PORT_VID(vlan->vid)); + priv->ports[port].pvid = vlan->vid; + } + + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +static int +an8855_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct an8855_hw_vlan_entry target_entry; + struct an8855_priv *priv = ds->priv; + u16 pvid; + + mutex_lock(&priv->reg_mutex); + + pvid = priv->ports[port].pvid; + an8855_hw_vlan_entry_init(&target_entry, port, 0); + an8855_hw_vlan_update(priv, vlan->vid, &target_entry, + an8855_hw_vlan_del); + + /* PVID is being restored to the default whenever the PVID port + * is being removed from the VLAN. + */ + if (pvid == vlan->vid) + pvid = G0_PORT_VID_DEF; + + an8855_rmw(priv, AN8855_PVID_P(port), G0_PORT_VID_MASK, pvid); + priv->ports[port].pvid = pvid; + + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +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; + + /* Check for existent entry */ + if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port)) + return -EEXIST; + + val = an8855_read(priv, AN8855_MIR); + + /* AN8855 supports 4 monitor port, but only use first group */ + monitor_port = AN8855_MIRROR_PORT_GET(val); + if (val & AN8855_MIRROR_EN && monitor_port != mirror->to_local_port) + return -EEXIST; + + val |= AN8855_MIRROR_EN; + val &= ~AN8855_MIRROR_MASK; + val |= AN8855_MIRROR_PORT_SET(mirror->to_local_port); + an8855_write(priv, AN8855_MIR, val); + + val = an8855_read(priv, AN8855_PCR_P(port)); + if (ingress) { + val |= PORT_RX_MIR; + priv->mirror_rx |= BIT(port); + } else { + val |= PORT_TX_MIR; + priv->mirror_tx |= BIT(port); + } + an8855_write(priv, AN8855_PCR_P(port), val); + + 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; + u32 val; + + val = an8855_read(priv, AN8855_PCR_P(port)); + if (mirror->ingress) { + val &= ~PORT_RX_MIR; + priv->mirror_rx &= ~BIT(port); + } else { + val &= ~PORT_TX_MIR; + priv->mirror_tx &= ~BIT(port); + } + an8855_write(priv, AN8855_PCR_P(port), val); + + if (!priv->mirror_rx && !priv->mirror_tx) { + val = an8855_read(priv, AN8855_MIR); + val &= ~AN8855_MIRROR_EN; + an8855_write(priv, AN8855_MIR, val); + } +} + +static enum dsa_tag_protocol +air_get_tag_protocol(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp) +{ + struct an8855_priv *priv = ds->priv; + + if (port != AN8855_CPU_PORT) { + dev_warn(priv->dev, "port not matched with tagging CPU port\n"); + return DSA_TAG_PROTO_NONE; + } else { + return DSA_TAG_PROTO_ARHT; + } +} + +static int +setup_unused_ports(struct dsa_switch *ds, u32 pm) +{ + struct an8855_priv *priv = ds->priv; + u32 egtag_mask = 0; + u32 egtag_val = 0; + int i; + + if (!pm) + return 0; + + for (i = 0; i < AN8855_NUM_PORTS; i++) { + if (!dsa_is_unused_port(ds, i)) + continue; + + /* Setup MAC port with maximum capability. */ + if (i == 5) + if (priv->info->cpu_port_config) + priv->info->cpu_port_config(ds, i); + + an8855_rmw(priv, AN8855_PORTMATRIX_P(i), PORTMATRIX_MASK, + AN8855_PORTMATRIX_P(pm)); + an8855_rmw(priv, AN8855_PCR_P(i), PCR_PORT_VLAN_MASK, + AN8855_PORT_SECURITY_MODE); + egtag_mask |= ETAG_CTRL_P_MASK(i); + egtag_val |= ETAG_CTRL_P(i, AN8855_VLAN_EGRESS_UNTAG); + } + + /* Add unused ports to VLAN2 group for using IVL fdb. */ + an8855_write(priv, AN8855_VAWD0, + IVL_MAC | VTAG_EN | PORT_MEM(pm) | VLAN_VALID); + an8855_rmw(priv, AN8855_VAWD0, egtag_mask << PORT_EG_CTRL_SHIFT, + egtag_val << PORT_EG_CTRL_SHIFT); + an8855_write(priv, AN8855_VAWD1, 0); + an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID, AN8855_RESERVED_VLAN); + + for (i = 0; i < AN8855_NUM_PORTS; i++) { + if (!dsa_is_unused_port(ds, i)) + continue; + + an8855_rmw(priv, AN8855_PVID_P(i), G0_PORT_VID_MASK, + G0_PORT_VID(AN8855_RESERVED_VLAN)); + an8855_rmw(priv, AN8855_SSP_P(i), FID_PST_MASK, + AN8855_STP_FORWARDING); + + dev_dbg(ds->dev, "Add unused port%d to reserved VLAN%d group\n", + i, AN8855_RESERVED_VLAN); + } + + return 0; +} + +static int an8855_led_set_usr_def(struct dsa_switch *ds, u8 entity, + int polar, u16 on_evt, u16 blk_evt, u8 led_freq) +{ + struct an8855_priv *priv = ds->priv; + u32 cl45_data = 0; + + if (polar == LED_HIGH) + on_evt |= LED_ON_POL; + else + on_evt &= ~LED_ON_POL; + + /* LED on event */ + an8855_phy_cl45_write(priv, (entity / 4), PHY_DEV1E, + PHY_SINGLE_LED_ON_CTRL(entity % 4), on_evt | LED_ON_EN); + + /* LED blink event */ + an8855_phy_cl45_write(priv, (entity / 4), PHY_DEV1E, + PHY_SINGLE_LED_BLK_CTRL(entity % 4), blk_evt); + + /* LED freq */ + switch (led_freq) { + case AIR_LED_BLK_DUR_32M: + cl45_data = 0x30e; + break; + case AIR_LED_BLK_DUR_64M: + cl45_data = 0x61a; + break; + case AIR_LED_BLK_DUR_128M: + cl45_data = 0xc35; + break; + case AIR_LED_BLK_DUR_256M: + cl45_data = 0x186a; + break; + case AIR_LED_BLK_DUR_512M: + cl45_data = 0x30d4; + break; + case AIR_LED_BLK_DUR_1024M: + cl45_data = 0x61a8; + break; + default: + break; + } + an8855_phy_cl45_write(priv, (entity / 4), PHY_DEV1E, + PHY_SINGLE_LED_BLK_DUR(entity % 4), cl45_data); + + an8855_phy_cl45_write(priv, (entity / 4), PHY_DEV1E, + PHY_SINGLE_LED_ON_DUR(entity % 4), (cl45_data >> 1)); + + /* Disable DATA & BAD_SSD for port LED blink behavior */ + cl45_data = an8855_phy_cl45_read(priv, (entity / 4), PHY_DEV1E, + PHY_PMA_CTRL); + cl45_data &= ~BIT(0); + cl45_data &= ~BIT(15); + an8855_phy_cl45_write(priv, (entity / 4), PHY_DEV1E, + PHY_PMA_CTRL, cl45_data); + + return 0; +} + +static int an8855_led_set_mode(struct dsa_switch *ds, u8 mode) +{ + struct an8855_priv *priv = ds->priv; + u16 cl45_data; + + cl45_data = an8855_phy_cl45_read(priv, 0, PHY_DEV1F, PHY_LED_BCR); + switch (mode) { + case AN8855_LED_MODE_DISABLE: + cl45_data &= ~LED_BCR_EXT_CTRL; + cl45_data &= ~LED_BCR_MODE_MASK; + cl45_data |= LED_BCR_MODE_DISABLE; + break; + case AN8855_LED_MODE_USER_DEFINE: + cl45_data |= LED_BCR_EXT_CTRL; + cl45_data |= LED_BCR_CLK_EN; + break; + default: + dev_err(priv->dev, "LED mode%d is not supported!\n", mode); + return -EINVAL; + } + an8855_phy_cl45_write(priv, 0, PHY_DEV1F, PHY_LED_BCR, cl45_data); + + return 0; +} + +static int an8855_led_set_state(struct dsa_switch *ds, u8 entity, u8 state) +{ + struct an8855_priv *priv = ds->priv; + u16 cl45_data = 0; + + /* Change to per port contorl */ + cl45_data = an8855_phy_cl45_read(priv, (entity / 4), PHY_DEV1E, + PHY_LED_CTRL_SELECT); + + if (state == 1) + cl45_data |= (1 << (entity % 4)); + else + cl45_data &= ~(1 << (entity % 4)); + + an8855_phy_cl45_write(priv, (entity / 4), PHY_DEV1E, + PHY_LED_CTRL_SELECT, cl45_data); + + /* LED enable setting */ + cl45_data = an8855_phy_cl45_read(priv, (entity / 4), + PHY_DEV1E, PHY_SINGLE_LED_ON_CTRL(entity % 4)); + + if (state == 1) + cl45_data |= LED_ON_EN; + else + cl45_data &= ~LED_ON_EN; + + an8855_phy_cl45_write(priv, (entity / 4), PHY_DEV1E, + PHY_SINGLE_LED_ON_CTRL(entity % 4), cl45_data); + + return 0; +} + +static int an8855_led_init(struct dsa_switch *ds) +{ + struct an8855_priv *priv = ds->priv; + u32 val, led_count = ARRAY_SIZE(led_cfg); + int ret = 0, id; + u32 tmp_val = 0; + u32 tmp_id = 0; + + ret = an8855_led_set_mode(ds, AN8855_LED_MODE_USER_DEFINE); + if (ret != 0) { + dev_err(priv->dev, "led_set_mode fail(ret:%d)!\n", ret); + return ret; + } + + for (id = 0; id < led_count; id++) { + ret = an8855_led_set_state(ds, + led_cfg[id].phy_led_idx, led_cfg[id].en); + if (ret != 0) { + dev_err(priv->dev, "led_set_state fail(ret:%d)!\n", ret); + return ret; + } + if (led_cfg[id].en == 1) { + ret = an8855_led_set_usr_def(ds, + led_cfg[id].phy_led_idx, + led_cfg[id].pol, led_cfg[id].on_cfg, + led_cfg[id].blk_cfg, + led_cfg[id].led_freq); + if (ret != 0) { + dev_err(priv->dev, "led_set_usr_def fail!\n"); + return ret; + } + } + } + + /* Setting for System LED & Loop LED */ + an8855_write(priv, RG_GPIO_OE, 0x0); + an8855_write(priv, RG_GPIO_CTRL, 0x0); + val = 0; + an8855_write(priv, RG_GPIO_L_INV, val); + + val = 0x1001; + an8855_write(priv, RG_GPIO_CTRL, val); + val = an8855_read(priv, RG_GPIO_DATA); + val |= BITS(1, 3); + val &= ~(BIT(0)); + val &= ~(BIT(6)); + + an8855_write(priv, RG_GPIO_DATA, val); + val = an8855_read(priv, RG_GPIO_OE); + val |= 0x41; + an8855_write(priv, RG_GPIO_OE, val); + + /* Mapping between GPIO & LED */ + val = 0; + for (id = 0; id < led_count; id++) { + /* Skip GPIO6, due to GPIO6 does not support PORT LED */ + if (id == 6) + continue; + + if (led_cfg[id].en == 1) { + if (id < 7) + val |= led_cfg[id].phy_led_idx << ((id % 4) * 8); + else + val |= led_cfg[id].phy_led_idx << (((id - 1) % 4) * 8); + } + + if (id < 7) + tmp_id = id; + else + tmp_id = id - 1; + + if ((tmp_id % 4) == 0x3) { + an8855_write(priv, RG_GPIO_LED_SEL(tmp_id / 4), val); + tmp_val = an8855_read(priv, RG_GPIO_LED_SEL(tmp_id / 4)); + val = 0; + } + } + + /* Turn on LAN LED mode */ + val = 0; + for (id = 0; id < led_count; id++) { + if (led_cfg[id].en == 1) + val |= 0x1 << id; + } + an8855_write(priv, RG_GPIO_LED_MODE, val); + + /* Force clear blink pulse for per port LED */ + an8855_phy_cl45_write(priv, 0, PHY_DEV1F, PHY_LED_BLINK_DUR_CTRL, 0x1f); + usleep_range(1000, 5000); + an8855_phy_cl45_write(priv, 0, PHY_DEV1F, PHY_LED_BLINK_DUR_CTRL, 0); + + return 0; +} + +static int +an8855_setup(struct dsa_switch *ds) +{ + struct an8855_priv *priv = ds->priv; + struct an8855_dummy_poll p; + u32 unused_pm = 0; + u32 val, id, led_count = ARRAY_SIZE(led_cfg); + int ret, i; + + /* Reset whole chip through gpio pin or memory-mapped registers for + * different type of hardware + */ + gpiod_set_value_cansleep(priv->reset, 0); + usleep_range(100000, 150000); + gpiod_set_value_cansleep(priv->reset, 1); + usleep_range(100000, 150000); + + /* Waiting for AN8855 got to stable */ + INIT_AN8855_DUMMY_POLL(&p, priv, 0x1000009c); + ret = readx_poll_timeout(_an8855_read, &p, val, val != 0, 20, 1000000); + if (ret < 0) { + dev_err(priv->dev, "reset timeout\n"); + return ret; + } + + id = an8855_read(priv, AN8855_CREV); + if (id != AN8855_ID) { + dev_err(priv->dev, "chip %x can't be supported\n", id); + return -ENODEV; + } + + /* Reset the switch through internal reset */ + an8855_write(priv, AN8855_RST_CTRL, SYS_CTRL_SYS_RST); + usleep_range(100000, 110000); + + /* change gphy smi address */ + if (priv->phy_base_new > 0) { + an8855_write(priv, RG_GPHY_SMI_ADDR, priv->phy_base_new); + priv->phy_base = priv->phy_base_new; + } + + for (i = 0; i < AN8855_NUM_PHYS; i++) { + val = an8855_phy_read(ds, i, MII_BMCR); + val |= BMCR_ISOLATE; + an8855_phy_write(ds, i, MII_BMCR, val); + } + + /* AN8855H need to setup before switch init */ + val = an8855_read(priv, PKG_SEL); + if ((val & 0x7) == PAG_SEL_AN8855H) { + /* Invert for LED activity change */ + val = an8855_read(priv, RG_GPIO_L_INV); + for (id = 0; id < led_count; id++) { + if ((led_cfg[id].pol == LED_HIGH) && + (led_cfg[id].en == 1)) + val |= 0x1 << id; + } + an8855_write(priv, RG_GPIO_L_INV, (val | 0x1)); + + /* MCU NOP CMD */ + an8855_write(priv, RG_GDMP_RAM, 0x846); + an8855_write(priv, RG_GDMP_RAM + 4, 0x4a); + + /* Enable MCU */ + val = an8855_read(priv, RG_CLK_CPU_ICG); + an8855_write(priv, RG_CLK_CPU_ICG, val | MCU_ENABLE); + usleep_range(1000, 5000); + + /* Disable MCU watchdog */ + val = an8855_read(priv, RG_TIMER_CTL); + an8855_write(priv, RG_TIMER_CTL, (val & (~WDOG_ENABLE))); + + /* Configure interrupt */ + an8855_write(priv, RG_INTB_MODE, (0x1 << priv->intr_pin)); + + /* LED settings for T830 reference board */ + ret = an8855_led_init(ds); + if (ret < 0) { + dev_err(priv->dev, "an8855_led_init fail. (ret=%d)\n", ret); + return ret; + } + } + + /* Adjust to reduce noise */ + for (i = 0; i < AN8855_NUM_PHYS; i++) { + an8855_phy_cl45_write(priv, i, PHY_DEV1E, + PHY_TX_PAIR_DLY_SEL_GBE, 0x4040); + + an8855_phy_cl45_write(priv, i, PHY_DEV1E, + PHY_RXADC_CTRL, 0x1010); + + an8855_phy_cl45_write(priv, i, PHY_DEV1E, + PHY_RXADC_REV_0, 0x100); + + an8855_phy_cl45_write(priv, i, PHY_DEV1E, + PHY_RXADC_REV_1, 0x100); + } + + /* Let phylink decide the interface later. */ + priv->p5_interface = PHY_INTERFACE_MODE_NA; + + /* BPDU to CPU port */ + //an8855_rmw(priv, AN8855_CFC, AN8855_CPU_PMAP_MASK, + // BIT(AN8855_CPU_PORT)); + an8855_rmw(priv, AN8855_BPC, AN8855_BPDU_PORT_FW_MASK, + AN8855_BPDU_CPU_ONLY); + + val = an8855_read(priv, AN8855_CKGCR); + val &= ~(CKG_LNKDN_GLB_STOP | CKG_LNKDN_PORT_STOP); + an8855_write(priv, AN8855_CKGCR, val); + + /* Enable and reset MIB counters */ + an8855_mib_reset(ds); + + for (i = 0; i < AN8855_NUM_PORTS; i++) { + /* Disable forwarding by default on all ports */ + an8855_rmw(priv, AN8855_PORTMATRIX_P(i), PORTMATRIX_MASK, + PORTMATRIX_CLR); + if (dsa_is_unused_port(ds, i)) + unused_pm |= BIT(i); + else if (dsa_is_cpu_port(ds, i)) + an8855_cpu_port_enable(ds, i); + else + an8855_port_disable(ds, i); + /* Enable consistent egress tag */ + an8855_rmw(priv, AN8855_PVC_P(i), PVC_EG_TAG_MASK, + PVC_EG_TAG(AN8855_VLAN_EG_CONSISTENT)); + } + + for (i = 0; i < AN8855_NUM_PHYS; i++) { + val = an8855_phy_read(ds, i, MII_BMCR); + val &= ~BMCR_ISOLATE; + an8855_phy_write(ds, i, MII_BMCR, val); + } + + an8855_phy_setup(ds); + + /* PHY restart AN*/ + for (i = 0; i < AN8855_NUM_PHYS; i++) + an8855_phy_write(ds, i, MII_BMCR, 0x1240); + + /* Group and enable unused ports as a standalone dumb switch. */ + setup_unused_ports(ds, unused_pm); + + 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; + + return 0; +} + +static bool +an8855_phy_supported(struct dsa_switch *ds, int port, + const struct phylink_link_state *state) +{ + struct an8855_priv *priv = ds->priv; + + switch (port) { + case 0: /* Internal phy */ + case 1: + case 2: + case 3: + case 4: + if (state->interface != PHY_INTERFACE_MODE_GMII) + goto unsupported; + break; + case 5: + if (state->interface != PHY_INTERFACE_MODE_SGMII + && state->interface != PHY_INTERFACE_MODE_RGMII + && state->interface != PHY_INTERFACE_MODE_2500BASEX) + goto unsupported; + break; + default: + dev_err(priv->dev, "%s: unsupported port: %i\n", __func__, + port); + goto unsupported; + } + + return true; + +unsupported: + return false; +} + +static int +an8855_rgmii_setup(struct an8855_priv *priv, u32 port, + phy_interface_t interface, + struct phy_device *phydev) +{ + return 0; +} + +static void +an8855_sgmii_validate(struct an8855_priv *priv, int port, + unsigned long *supported) +{ + switch (port) { + case 5: + phylink_set(supported, 1000baseX_Full); + phylink_set(supported, 2500baseX_Full); + } +} + +static bool +an8855_is_mac_port(u32 port) +{ + return (port == 5); +} + +static int +an8855_set_hsgmii_mode(struct an8855_priv *priv) +{ + u32 val = 0; + + /* TX FIR - improve TX EYE */ + val = an8855_read(priv, INTF_CTRL_10); + val &= ~(0x3f << 16); + val |= BIT(21); + val &= ~(0x1f << 24); + val |= (0x4 << 24); + val |= BIT(29); + an8855_write(priv, INTF_CTRL_10, val); + + val = an8855_read(priv, INTF_CTRL_11); + val &= ~(0x3f); + val |= BIT(6); + an8855_write(priv, INTF_CTRL_11, val); + + /* RX CDR - improve RX Jitter Tolerance */ + val = an8855_read(priv, RG_QP_CDR_LPF_BOT_LIM); + val &= ~(0x7 << 24); + val |= (0x5 << 24); + val &= ~(0x7 << 20); + val |= (0x5 << 20); + an8855_write(priv, RG_QP_CDR_LPF_BOT_LIM, val); + + /* PLL */ + val = an8855_read(priv, QP_DIG_MODE_CTRL_1); + val &= ~(0x3 << 2); + val |= (0x1 << 2); + an8855_write(priv, QP_DIG_MODE_CTRL_1, val); + + /* PLL - LPF */ + val = an8855_read(priv, PLL_CTRL_2); + val &= ~(0x3 << 0); + val |= (0x1 << 0); + val &= ~(0x7 << 2); + val |= (0x5 << 2); + val &= ~BITS(6, 7); + val &= ~(0x7 << 8); + val |= (0x3 << 8); + val |= BIT(29); + val &= ~BITS(12, 13); + an8855_write(priv, PLL_CTRL_2, val); + + /* PLL - ICO */ + val = an8855_read(priv, PLL_CTRL_4); + val |= BIT(2); + an8855_write(priv, PLL_CTRL_4, val); + + val = an8855_read(priv, PLL_CTRL_2); + val &= ~BIT(14); + an8855_write(priv, PLL_CTRL_2, val); + + /* PLL - CHP */ + val = an8855_read(priv, PLL_CTRL_2); + val &= ~(0xf << 16); + val |= (0x6 << 16); + an8855_write(priv, PLL_CTRL_2, val); + + /* PLL - PFD */ + val = an8855_read(priv, PLL_CTRL_2); + val &= ~(0x3 << 20); + val |= (0x1 << 20); + val &= ~(0x3 << 24); + val |= (0x1 << 24); + val &= ~BIT(26); + an8855_write(priv, PLL_CTRL_2, val); + + /* PLL - POSTDIV */ + val = an8855_read(priv, PLL_CTRL_2); + val |= BIT(22); + val &= ~BIT(27); + val &= ~BIT(28); + an8855_write(priv, PLL_CTRL_2, val); + + /* PLL - SDM */ + val = an8855_read(priv, PLL_CTRL_4); + val &= ~BITS(3, 4); + an8855_write(priv, PLL_CTRL_4, val); + + val = an8855_read(priv, PLL_CTRL_2); + val &= ~BIT(30); + an8855_write(priv, PLL_CTRL_2, val); + + val = an8855_read(priv, SS_LCPLL_PWCTL_SETTING_2); + val &= ~(0x3 << 16); + val |= (0x1 << 16); + an8855_write(priv, SS_LCPLL_PWCTL_SETTING_2, val); + + an8855_write(priv, SS_LCPLL_TDC_FLT_2, 0x7a000000); + an8855_write(priv, SS_LCPLL_TDC_PCW_1, 0x7a000000); + + val = an8855_read(priv, SS_LCPLL_TDC_FLT_5); + val &= ~BIT(24); + an8855_write(priv, SS_LCPLL_TDC_FLT_5, val); + + val = an8855_read(priv, PLL_CK_CTRL_0); + val &= ~BIT(8); + an8855_write(priv, PLL_CK_CTRL_0, val); + + /* PLL - SS */ + val = an8855_read(priv, PLL_CTRL_3); + val &= ~BITS(0, 15); + an8855_write(priv, PLL_CTRL_3, val); + + val = an8855_read(priv, PLL_CTRL_4); + val &= ~BITS(0, 1); + an8855_write(priv, PLL_CTRL_4, val); + + val = an8855_read(priv, PLL_CTRL_3); + val &= ~BITS(16, 31); + an8855_write(priv, PLL_CTRL_3, val); + + /* PLL - TDC */ + val = an8855_read(priv, PLL_CK_CTRL_0); + val &= ~BIT(9); + an8855_write(priv, PLL_CK_CTRL_0, val); + + val = an8855_read(priv, RG_QP_PLL_SDM_ORD); + val |= BIT(3); + val |= BIT(4); + an8855_write(priv, RG_QP_PLL_SDM_ORD, val); + + val = an8855_read(priv, RG_QP_RX_DAC_EN); + val &= ~(0x3 << 16); + val |= (0x2 << 16); + an8855_write(priv, RG_QP_RX_DAC_EN, val); + + /* TCL Disable (only for Co-SIM) */ + val = an8855_read(priv, PON_RXFEDIG_CTRL_0); + val &= ~BIT(12); + an8855_write(priv, PON_RXFEDIG_CTRL_0, val); + + /* TX Init */ + val = an8855_read(priv, RG_QP_TX_MODE_16B_EN); + val &= ~BIT(0); + val &= ~(0xffff << 16); + val |= (0x4 << 16); + an8855_write(priv, RG_QP_TX_MODE_16B_EN, val); + + /* RX Control */ + val = an8855_read(priv, RG_QP_RXAFE_RESERVE); + val |= BIT(11); + an8855_write(priv, RG_QP_RXAFE_RESERVE, val); + + val = an8855_read(priv, RG_QP_CDR_LPF_MJV_LIM); + val &= ~(0x3 << 4); + val |= (0x1 << 4); + an8855_write(priv, RG_QP_CDR_LPF_MJV_LIM, val); + + val = an8855_read(priv, RG_QP_CDR_LPF_SETVALUE); + val &= ~(0xf << 25); + val |= (0x1 << 25); + val &= ~(0x7 << 29); + val |= (0x6 << 29); + an8855_write(priv, RG_QP_CDR_LPF_SETVALUE, val); + + val = an8855_read(priv, RG_QP_CDR_PR_CKREF_DIV1); + val &= ~(0x1f << 8); + val |= (0xf << 8); + an8855_write(priv, RG_QP_CDR_PR_CKREF_DIV1, val); + + val = an8855_read(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE); + val &= ~(0x3f << 0); + val |= (0x19 << 0); + val &= ~BIT(6); + an8855_write(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE, val); + + val = an8855_read(priv, RG_QP_CDR_FORCE_IBANDLPF_R_OFF); + val &= ~(0x7f << 6); + val |= (0x21 << 6); + val &= ~(0x3 << 16); + val |= (0x2 << 16); + val &= ~BIT(13); + an8855_write(priv, RG_QP_CDR_FORCE_IBANDLPF_R_OFF, val); + + val = an8855_read(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE); + val &= ~BIT(30); + an8855_write(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE, val); + + val = an8855_read(priv, RG_QP_CDR_PR_CKREF_DIV1); + val &= ~(0x7 << 24); + val |= (0x4 << 24); + an8855_write(priv, RG_QP_CDR_PR_CKREF_DIV1, val); + + val = an8855_read(priv, RX_CTRL_26); + val |= BIT(23); + val &= ~BIT(24); + val |= BIT(26); + an8855_write(priv, RX_CTRL_26, val); + + val = an8855_read(priv, RX_DLY_0); + val &= ~(0xff << 0); + val |= (0x6f << 0); + val |= BITS(8, 13); + an8855_write(priv, RX_DLY_0, val); + + val = an8855_read(priv, RX_CTRL_42); + val &= ~(0x1fff << 0); + val |= (0x150 << 0); + an8855_write(priv, RX_CTRL_42, val); + + val = an8855_read(priv, RX_CTRL_2); + val &= ~(0x1fff << 16); + val |= (0x150 << 16); + an8855_write(priv, RX_CTRL_2, val); + + val = an8855_read(priv, PON_RXFEDIG_CTRL_9); + val &= ~(0x7 << 0); + val |= (0x1 << 0); + an8855_write(priv, PON_RXFEDIG_CTRL_9, val); + + val = an8855_read(priv, RX_CTRL_8); + val &= ~(0xfff << 16); + val |= (0x200 << 16); + val &= ~(0x7fff << 0); + val |= (0xfff << 0); + an8855_write(priv, RX_CTRL_8, val); + + /* Frequency memter */ + val = an8855_read(priv, RX_CTRL_5); + val &= ~(0xfffff << 10); + val |= (0x10 << 10); + an8855_write(priv, RX_CTRL_5, val); + + val = an8855_read(priv, RX_CTRL_6); + val &= ~(0xfffff << 0); + val |= (0x64 << 0); + an8855_write(priv, RX_CTRL_6, val); + + val = an8855_read(priv, RX_CTRL_7); + val &= ~(0xfffff << 0); + val |= (0x2710 << 0); + an8855_write(priv, RX_CTRL_7, val); + + val = an8855_read(priv, PLL_CTRL_0); + val |= BIT(0); + an8855_write(priv, PLL_CTRL_0, val); + + /* PCS Init */ + val = an8855_read(priv, RG_HSGMII_PCS_CTROL_1); + val &= ~BIT(30); + an8855_write(priv, RG_HSGMII_PCS_CTROL_1, val); + + /* Rate Adaption */ + val = an8855_read(priv, RATE_ADP_P0_CTRL_0); + val &= ~BIT(31); + an8855_write(priv, RATE_ADP_P0_CTRL_0, val); + + val = an8855_read(priv, RG_RATE_ADAPT_CTRL_0); + val |= BIT(0); + val |= BIT(4); + val |= BITS(26, 27); + an8855_write(priv, RG_RATE_ADAPT_CTRL_0, val); + + /* Disable AN */ + val = an8855_read(priv, SGMII_REG_AN0); + val &= ~BIT(12); + an8855_write(priv, SGMII_REG_AN0, val); + + /* Force Speed */ + val = an8855_read(priv, SGMII_STS_CTRL_0); + val |= BIT(2); + val |= BITS(4, 5); + an8855_write(priv, SGMII_STS_CTRL_0, val); + + /* bypass flow control to MAC */ + an8855_write(priv, MSG_RX_LIK_STS_0, 0x01010107); + an8855_write(priv, MSG_RX_LIK_STS_2, 0x00000EEF); + + return 0; +} + +static int +an8855_sgmii_setup(struct an8855_priv *priv, int mode) +{ + u32 val = 0; + + /* TX FIR - improve TX EYE */ + val = an8855_read(priv, INTF_CTRL_10); + val &= ~(0x3f << 16); + val |= BIT(21); + val &= ~(0x1f << 24); + val |= BIT(29); + an8855_write(priv, INTF_CTRL_10, val); + + val = an8855_read(priv, INTF_CTRL_11); + val &= ~(0x3f); + val |= (0xd << 0); + val |= BIT(6); + an8855_write(priv, INTF_CTRL_11, val); + + /* RX CDR - improve RX Jitter Tolerance */ + val = an8855_read(priv, RG_QP_CDR_LPF_BOT_LIM); + val &= ~(0x7 << 24); + val |= (0x6 << 24); + val &= ~(0x7 << 20); + val |= (0x6 << 20); + an8855_write(priv, RG_QP_CDR_LPF_BOT_LIM, val); + + /* PMA Init */ + /* PLL */ + val = an8855_read(priv, QP_DIG_MODE_CTRL_1); + val &= ~BITS(2, 3); + an8855_write(priv, QP_DIG_MODE_CTRL_1, val); + + /* PLL - LPF */ + val = an8855_read(priv, PLL_CTRL_2); + val &= ~(0x3 << 0); + val |= (0x1 << 0); + val &= ~(0x7 << 2); + val |= (0x5 << 2); + val &= ~BITS(6, 7); + val &= ~(0x7 << 8); + val |= (0x3 << 8); + val |= BIT(29); + val &= ~BITS(12, 13); + an8855_write(priv, PLL_CTRL_2, val); + + /* PLL - ICO */ + val = an8855_read(priv, PLL_CTRL_4); + val |= BIT(2); + an8855_write(priv, PLL_CTRL_4, val); + + val = an8855_read(priv, PLL_CTRL_2); + val &= ~BIT(14); + an8855_write(priv, PLL_CTRL_2, val); + + /* PLL - CHP */ + val = an8855_read(priv, PLL_CTRL_2); + val &= ~(0xf << 16); + val |= (0x4 << 16); + an8855_write(priv, PLL_CTRL_2, val); + + /* PLL - PFD */ + val = an8855_read(priv, PLL_CTRL_2); + val &= ~(0x3 << 20); + val |= (0x1 << 20); + val &= ~(0x3 << 24); + val |= (0x1 << 24); + val &= ~BIT(26); + an8855_write(priv, PLL_CTRL_2, val); + + /* PLL - POSTDIV */ + val = an8855_read(priv, PLL_CTRL_2); + val |= BIT(22); + val &= ~BIT(27); + val &= ~BIT(28); + an8855_write(priv, PLL_CTRL_2, val); + + /* PLL - SDM */ + val = an8855_read(priv, PLL_CTRL_4); + val &= ~BITS(3, 4); + an8855_write(priv, PLL_CTRL_4, val); + + val = an8855_read(priv, PLL_CTRL_2); + val &= ~BIT(30); + an8855_write(priv, PLL_CTRL_2, val); + + val = an8855_read(priv, SS_LCPLL_PWCTL_SETTING_2); + val &= ~(0x3 << 16); + val |= (0x1 << 16); + an8855_write(priv, SS_LCPLL_PWCTL_SETTING_2, val); + + an8855_write(priv, SS_LCPLL_TDC_FLT_2, 0x48000000); + an8855_write(priv, SS_LCPLL_TDC_PCW_1, 0x48000000); + + val = an8855_read(priv, SS_LCPLL_TDC_FLT_5); + val &= ~BIT(24); + an8855_write(priv, SS_LCPLL_TDC_FLT_5, val); + + val = an8855_read(priv, PLL_CK_CTRL_0); + val &= ~BIT(8); + an8855_write(priv, PLL_CK_CTRL_0, val); + + /* PLL - SS */ + val = an8855_read(priv, PLL_CTRL_3); + val &= ~BITS(0, 15); + an8855_write(priv, PLL_CTRL_3, val); + + val = an8855_read(priv, PLL_CTRL_4); + val &= ~BITS(0, 1); + an8855_write(priv, PLL_CTRL_4, val); + + val = an8855_read(priv, PLL_CTRL_3); + val &= ~BITS(16, 31); + an8855_write(priv, PLL_CTRL_3, val); + + /* PLL - TDC */ + val = an8855_read(priv, PLL_CK_CTRL_0); + val &= ~BIT(9); + an8855_write(priv, PLL_CK_CTRL_0, val); + + val = an8855_read(priv, RG_QP_PLL_SDM_ORD); + val |= BIT(3); + val |= BIT(4); + an8855_write(priv, RG_QP_PLL_SDM_ORD, val); + + val = an8855_read(priv, RG_QP_RX_DAC_EN); + val &= ~(0x3 << 16); + val |= (0x2 << 16); + an8855_write(priv, RG_QP_RX_DAC_EN, val); + + /* PLL - TCL Disable (only for Co-SIM) */ + val = an8855_read(priv, PON_RXFEDIG_CTRL_0); + val &= ~BIT(12); + an8855_write(priv, PON_RXFEDIG_CTRL_0, val); + + /* TX Init */ + val = an8855_read(priv, RG_QP_TX_MODE_16B_EN); + val &= ~BIT(0); + val &= ~BITS(16, 31); + an8855_write(priv, RG_QP_TX_MODE_16B_EN, val); + + /* RX Init */ + val = an8855_read(priv, RG_QP_RXAFE_RESERVE); + val |= BIT(11); + an8855_write(priv, RG_QP_RXAFE_RESERVE, val); + + val = an8855_read(priv, RG_QP_CDR_LPF_MJV_LIM); + val &= ~(0x3 << 4); + val |= (0x2 << 4); + an8855_write(priv, RG_QP_CDR_LPF_MJV_LIM, val); + + val = an8855_read(priv, RG_QP_CDR_LPF_SETVALUE); + val &= ~(0xf << 25); + val |= (0x1 << 25); + val &= ~(0x7 << 29); + val |= (0x6 << 29); + an8855_write(priv, RG_QP_CDR_LPF_SETVALUE, val); + + val = an8855_read(priv, RG_QP_CDR_PR_CKREF_DIV1); + val &= ~(0x1f << 8); + val |= (0xc << 8); + an8855_write(priv, RG_QP_CDR_PR_CKREF_DIV1, val); + + val = an8855_read(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE); + val &= ~(0x3f << 0); + val |= (0x19 << 0); + val &= ~BIT(6); + an8855_write(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE, val); + + val = an8855_read(priv, RG_QP_CDR_FORCE_IBANDLPF_R_OFF); + val &= ~(0x7f << 6); + val |= (0x21 << 6); + val &= ~(0x3 << 16); + val |= (0x2 << 16); + val &= ~BIT(13); + an8855_write(priv, RG_QP_CDR_FORCE_IBANDLPF_R_OFF, val); + + val = an8855_read(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE); + val &= ~BIT(30); + an8855_write(priv, RG_QP_CDR_PR_KBAND_DIV_PCIE, val); + + val = an8855_read(priv, RG_QP_CDR_PR_CKREF_DIV1); + val &= ~(0x7 << 24); + val |= (0x4 << 24); + an8855_write(priv, RG_QP_CDR_PR_CKREF_DIV1, val); + + val = an8855_read(priv, RX_CTRL_26); + val |= BIT(23); + val &= ~BIT(24); + val |= BIT(26); + an8855_write(priv, RX_CTRL_26, val); + + val = an8855_read(priv, RX_DLY_0); + val &= ~(0xff << 0); + val |= (0x6f << 0); + val |= BITS(8, 13); + an8855_write(priv, RX_DLY_0, val); + + val = an8855_read(priv, RX_CTRL_42); + val &= ~(0x1fff << 0); + val |= (0x150 << 0); + an8855_write(priv, RX_CTRL_42, val); + + val = an8855_read(priv, RX_CTRL_2); + val &= ~(0x1fff << 16); + val |= (0x150 << 16); + an8855_write(priv, RX_CTRL_2, val); + + val = an8855_read(priv, PON_RXFEDIG_CTRL_9); + val &= ~(0x7 << 0); + val |= (0x1 << 0); + an8855_write(priv, PON_RXFEDIG_CTRL_9, val); + + val = an8855_read(priv, RX_CTRL_8); + val &= ~(0xfff << 16); + val |= (0x200 << 16); + val &= ~(0x7fff << 0); + val |= (0xfff << 0); + an8855_write(priv, RX_CTRL_8, val); + + /* Frequency memter */ + val = an8855_read(priv, RX_CTRL_5); + val &= ~(0xfffff << 10); + val |= (0x28 << 10); + an8855_write(priv, RX_CTRL_5, val); + + val = an8855_read(priv, RX_CTRL_6); + val &= ~(0xfffff << 0); + val |= (0x64 << 0); + an8855_write(priv, RX_CTRL_6, val); + + val = an8855_read(priv, RX_CTRL_7); + val &= ~(0xfffff << 0); + val |= (0x2710 << 0); + an8855_write(priv, RX_CTRL_7, val); + + val = an8855_read(priv, PLL_CTRL_0); + val |= BIT(0); + an8855_write(priv, PLL_CTRL_0, val); + + if (mode == SGMII_MODE_FORCE) { + /* PCS Init */ + val = an8855_read(priv, QP_DIG_MODE_CTRL_0); + val &= ~BIT(0); + val &= ~BITS(4, 5); + an8855_write(priv, QP_DIG_MODE_CTRL_0, val); + + val = an8855_read(priv, RG_HSGMII_PCS_CTROL_1); + val &= ~BIT(30); + an8855_write(priv, RG_HSGMII_PCS_CTROL_1, val); + + /* Rate Adaption - GMII path config. */ + val = an8855_read(priv, RG_AN_SGMII_MODE_FORCE); + val |= BIT(0); + val &= ~BITS(4, 5); + an8855_write(priv, RG_AN_SGMII_MODE_FORCE, val); + + val = an8855_read(priv, SGMII_STS_CTRL_0); + val |= BIT(2); + val &= ~(0x3 << 4); + val |= (0x2 << 4); + an8855_write(priv, SGMII_STS_CTRL_0, val); + + val = an8855_read(priv, SGMII_REG_AN0); + val &= ~BIT(12); + an8855_write(priv, SGMII_REG_AN0, val); + + val = an8855_read(priv, PHY_RX_FORCE_CTRL_0); + val |= BIT(4); + an8855_write(priv, PHY_RX_FORCE_CTRL_0, val); + + val = an8855_read(priv, RATE_ADP_P0_CTRL_0); + val &= ~BITS(0, 3); + val |= BIT(28); + an8855_write(priv, RATE_ADP_P0_CTRL_0, val); + + val = an8855_read(priv, RG_RATE_ADAPT_CTRL_0); + val |= BIT(0); + val |= BIT(4); + val |= BITS(26, 27); + an8855_write(priv, RG_RATE_ADAPT_CTRL_0, val); + } else { + /* PCS Init */ + val = an8855_read(priv, RG_HSGMII_PCS_CTROL_1); + val &= ~BIT(30); + an8855_write(priv, RG_HSGMII_PCS_CTROL_1, val); + + /* Set AN Ability - Interrupt */ + val = an8855_read(priv, SGMII_REG_AN_FORCE_CL37); + val |= BIT(0); + an8855_write(priv, SGMII_REG_AN_FORCE_CL37, val); + + val = an8855_read(priv, SGMII_REG_AN_13); + val &= ~(0x3f << 0); + val |= (0xb << 0); + val |= BIT(8); + an8855_write(priv, SGMII_REG_AN_13, val); + + /* Rate Adaption - GMII path config. */ + val = an8855_read(priv, SGMII_REG_AN0); + val |= BIT(12); + an8855_write(priv, SGMII_REG_AN0, val); + + val = an8855_read(priv, MII_RA_AN_ENABLE); + val |= BIT(0); + an8855_write(priv, MII_RA_AN_ENABLE, val); + + val = an8855_read(priv, RATE_ADP_P0_CTRL_0); + val |= BIT(28); + an8855_write(priv, RATE_ADP_P0_CTRL_0, val); + + val = an8855_read(priv, RG_RATE_ADAPT_CTRL_0); + val |= BIT(0); + val |= BIT(4); + val |= BITS(26, 27); + an8855_write(priv, RG_RATE_ADAPT_CTRL_0, val); + + /* Only for Co-SIM */ + + /* AN Speed up (Only for Co-SIM) */ + + /* Restart AN */ + val = an8855_read(priv, SGMII_REG_AN0); + val |= BIT(9); + an8855_write(priv, SGMII_REG_AN0, val); + } + + /* bypass flow control to MAC */ + an8855_write(priv, MSG_RX_LIK_STS_0, 0x01010107); + an8855_write(priv, MSG_RX_LIK_STS_2, 0x00000EEF); + + return 0; +} + +static int +an8855_sgmii_setup_mode_an(struct an8855_priv *priv, int port, + phy_interface_t interface) +{ + return an8855_sgmii_setup(priv, SGMII_MODE_AN); +} + +static int +an8855_mac_config(struct dsa_switch *ds, int port, unsigned int mode, + phy_interface_t interface) +{ + struct an8855_priv *priv = ds->priv; + struct phy_device *phydev; + const struct dsa_port *dp; + + if (!an8855_is_mac_port(port)) { + dev_err(priv->dev, "port %d is not a MAC port\n", port); + return -EINVAL; + } + + switch (interface) { + case PHY_INTERFACE_MODE_RGMII: + dp = dsa_to_port(ds, port); + phydev = (dp->slave) ? dp->slave->phydev : NULL; + return an8855_rgmii_setup(priv, port, interface, phydev); + case PHY_INTERFACE_MODE_SGMII: + return an8855_sgmii_setup_mode_an(priv, port, interface); + case PHY_INTERFACE_MODE_2500BASEX: + if (phylink_autoneg_inband(mode)) + return -EINVAL; + return an8855_set_hsgmii_mode(priv); + default: + return -EINVAL; + } + + return -EINVAL; +} + +static struct phylink_pcs * +an8855_phylink_mac_select_pcs(struct dsa_switch *ds, int port, + phy_interface_t interface) +{ + struct an8855_priv *priv = ds->priv; + switch (interface) { + case PHY_INTERFACE_MODE_TRGMII: + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_2500BASEX: + return &priv->pcs[port].pcs; + default: + return NULL; + } +} + +static void an8855_phylink_pcs_link_up(struct phylink_pcs *pcs, + unsigned int mode, + phy_interface_t interface, + int speed, int duplex) +{ + if (pcs->ops->pcs_link_up) + pcs->ops->pcs_link_up(pcs, mode, interface, speed, duplex); +} + +static void +an8855_phylink_mac_link_up(struct dsa_switch *ds, int port, + unsigned int mode, + phy_interface_t interface, + struct phy_device *phydev, + int speed, int duplex, + bool tx_pause, bool rx_pause) +{ + struct an8855_priv *priv = ds->priv; + u32 mcr; + + mcr = an8855_read(priv, AN8855_PMCR_P(port)); + mcr |= PMCR_RX_EN | PMCR_TX_EN | PMCR_FORCE_LNK; + mcr &= + ~(PMCR_FORCE_FDX | PMCR_SPEED_MASK | PMCR_TX_FC_EN | PMCR_RX_FC_EN); + + if (interface == PHY_INTERFACE_MODE_RGMII + || interface == PHY_INTERFACE_MODE_SGMII) { + speed = SPEED_1000; + duplex = DUPLEX_FULL; + } else if (interface == PHY_INTERFACE_MODE_2500BASEX) { + speed = SPEED_2500; + duplex = DUPLEX_FULL; + } + + switch (speed) { + case SPEED_2500: + mcr |= PMCR_FORCE_SPEED_2500; + break; + case SPEED_1000: + mcr |= PMCR_FORCE_SPEED_1000; + if (priv->eee_enable & BIT(port)) + mcr |= PMCR_FORCE_EEE1G; + break; + case SPEED_100: + mcr |= PMCR_FORCE_SPEED_100; + if (priv->eee_enable & BIT(port)) + mcr |= PMCR_FORCE_EEE100; + break; + } + if (duplex == DUPLEX_FULL) { + mcr |= PMCR_FORCE_FDX; + if (tx_pause) + mcr |= PMCR_TX_FC_EN; + if (rx_pause) + mcr |= PMCR_RX_FC_EN; + } + + an8855_write(priv, AN8855_PMCR_P(port), mcr); +} + +static int +an8855_cpu_port_config(struct dsa_switch *ds, int port) +{ + struct an8855_priv *priv = ds->priv; + phy_interface_t interface = PHY_INTERFACE_MODE_NA; + int speed; + + switch (port) { + case 5: + interface = PHY_INTERFACE_MODE_2500BASEX; + + priv->p5_interface = interface; + break; + }; + if (interface == PHY_INTERFACE_MODE_NA) + dev_err(priv->dev, "invalid interface\n"); + + if (interface == PHY_INTERFACE_MODE_2500BASEX) + speed = SPEED_2500; + else + speed = SPEED_1000; + + an8855_mac_config(ds, port, MLO_AN_FIXED, interface); + an8855_write(priv, AN8855_PMCR_P(port), + PMCR_CPU_PORT_SETTING(priv->id)); + an8855_phylink_pcs_link_up(&priv->pcs[port].pcs, MLO_AN_FIXED, + interface, speed, DUPLEX_FULL); + an8855_phylink_mac_link_up(ds, port, MLO_AN_FIXED, interface, NULL, + speed, DUPLEX_FULL, true, true); + + return 0; +} + +static void +an8855_mac_port_validate(struct dsa_switch *ds, int port, + unsigned long *supported) +{ + struct an8855_priv *priv = ds->priv; + + an8855_sgmii_validate(priv, port, supported); +} + +static int +an8855_pcs_validate(struct phylink_pcs *pcs, + unsigned long *supported, + const struct phylink_link_state *state) +{ + /* Autonegotiation is not supported in TRGMII nor 802.3z modes */ + if (state->interface == PHY_INTERFACE_MODE_TRGMII || + phy_interface_mode_is_8023z(state->interface)) + phylink_clear(supported, Autoneg); + + return 0; +} + +static int +an8855_get_mac_eee(struct dsa_switch *ds, int port, + struct ethtool_eee *e) +{ + struct an8855_priv *priv = ds->priv; + u32 eeecr, pmsr, ckgcr; + + e->eee_enabled = !!(priv->eee_enable & BIT(port)); + + if (e->eee_enabled) { + eeecr = an8855_read(priv, AN8855_PMEEECR_P(port)); + e->tx_lpi_enabled = !(eeecr & LPI_MODE_EN); + ckgcr = an8855_read(priv, AN8855_CKGCR); + e->tx_lpi_timer = + ((ckgcr & LPI_TXIDLE_THD_MASK) >> LPI_TXIDLE_THD) / 500; + pmsr = an8855_read(priv, AN8855_PMSR_P(port)); + e->eee_active = e->eee_enabled + && !!(pmsr & (PMSR_EEE1G | PMSR_EEE100M)); + } else { + e->tx_lpi_enabled = 0; + e->tx_lpi_timer = 0; + e->eee_active = 0; + } + return 0; +} + +static int +an8855_set_mac_eee(struct dsa_switch *ds, int port, + struct ethtool_eee *e) +{ + struct an8855_priv *priv = ds->priv; + u32 eeecr; + + if (e->eee_enabled) { + priv->eee_enable |= BIT(port); + eeecr = an8855_read(priv, AN8855_PMEEECR_P(port)); + eeecr &= ~LPI_MODE_EN; + if (e->tx_lpi_enabled) + eeecr |= LPI_MODE_EN; + an8855_write(priv, AN8855_PMEEECR_P(port), eeecr); + } else { + priv->eee_enable &= ~(BIT(port)); + eeecr = an8855_read(priv, AN8855_PMEEECR_P(port)); + eeecr &= ~LPI_MODE_EN; + an8855_write(priv, AN8855_PMEEECR_P(port), eeecr); + } + + return 0; +} + +static void +an8855_pcs_get_state(struct phylink_pcs *pcs, + struct phylink_link_state *state) +{ + struct an8855_priv *priv = pcs_to_an8855_pcs(pcs)->priv; + int port = pcs_to_an8855_pcs(pcs)->port; + u32 pmsr; + + if (port < 0 || port >= AN8855_NUM_PORTS) + return; + + pmsr = an8855_read(priv, AN8855_PMSR_P(port)); + + state->link = (pmsr & PMSR_LINK); + state->an_complete = state->link; + state->duplex = !!(pmsr & PMSR_DPX); + + switch (pmsr & PMSR_SPEED_MASK) { + case PMSR_SPEED_10: + state->speed = SPEED_10; + break; + case PMSR_SPEED_100: + state->speed = SPEED_100; + break; + case PMSR_SPEED_1000: + state->speed = SPEED_1000; + break; + case PMSR_SPEED_2500: + state->speed = SPEED_2500; + break; + default: + state->speed = SPEED_UNKNOWN; + break; + } + + state->pause &= ~(MLO_PAUSE_RX | MLO_PAUSE_TX); + if (pmsr & PMSR_RX_FC) + state->pause |= MLO_PAUSE_RX; + if (pmsr & PMSR_TX_FC) + state->pause |= MLO_PAUSE_TX; +} + +static int an8855_pcs_config(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + return 0; +} + +static void an8855_pcs_an_restart(struct phylink_pcs *pcs) +{ +} + +static const struct phylink_pcs_ops an8855_pcs_ops = { + .pcs_validate = an8855_pcs_validate, + .pcs_get_state = an8855_pcs_get_state, + .pcs_config = an8855_pcs_config, + .pcs_an_restart = an8855_pcs_an_restart, +}; + +static int +an8855_sw_setup(struct dsa_switch *ds) +{ + struct an8855_priv *priv = ds->priv; + int i; + for (i = 0; i < priv->ds->num_ports; i++) { + priv->pcs[i].pcs.ops = priv->info->pcs_ops; + priv->pcs[i].pcs.neg_mode = true; + priv->pcs[i].priv = priv; + priv->pcs[i].port = i; + } + + int ret = priv->info->sw_setup(ds); + return ret; +} + +static int +an8855_sw_phy_read(struct dsa_switch *ds, int port, int regnum) +{ + struct an8855_priv *priv = ds->priv; + + return priv->info->phy_read(ds, port, regnum); +} + +static int +an8855_sw_phy_write(struct dsa_switch *ds, int port, int regnum, u16 val) +{ + struct an8855_priv *priv = ds->priv; + + return priv->info->phy_write(ds, port, regnum, val); +} + +static int an8855_proc_device_read(struct seq_file *seq, void *v) +{ + seq_printf(seq, "%s\n", ARHT_CHIP_NAME); + + return 0; +} + +static int an8855_proc_device_open(struct inode *inode, struct file *file) +{ + return single_open(file, an8855_proc_device_read, 0); +} + +static const struct proc_ops an8855_proc_device_fops = { + .proc_open = an8855_proc_device_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +static int an8855_proc_device_init(void) +{ + if (!proc_an8855_dsa_dir) + proc_an8855_dsa_dir = proc_mkdir(ARHT_PROC_DIR, 0); + + proc_create(ARHT_PROC_NODE_DEVICE, 0400, proc_an8855_dsa_dir, + &an8855_proc_device_fops); + + return 0; +} + +static void an8855_proc_device_exit(void) +{ + remove_proc_entry(ARHT_PROC_NODE_DEVICE, 0); +} + +static const struct dsa_switch_ops an8855_switch_ops = { + .get_tag_protocol = air_get_tag_protocol, + .setup = an8855_sw_setup, + .get_strings = an8855_get_strings, + .phy_read = an8855_sw_phy_read, + .phy_write = an8855_sw_phy_write, + .get_ethtool_stats = an8855_get_ethtool_stats, + .get_sset_count = an8855_get_sset_count, + .port_enable = an8855_port_enable, + .port_disable = an8855_port_disable, + .port_stp_state_set = an8855_stp_state_set, + .port_bridge_join = an8855_port_bridge_join, + .port_bridge_leave = an8855_port_bridge_leave, + .port_fdb_add = an8855_port_fdb_add, + .port_fdb_del = an8855_port_fdb_del, + .port_fdb_dump = an8855_port_fdb_dump, + .port_vlan_filtering = an8855_port_vlan_filtering, + .port_vlan_add = an8855_port_vlan_add, + .port_vlan_del = an8855_port_vlan_del, + .port_mirror_add = an8855_port_mirror_add, + .port_mirror_del = an8855_port_mirror_del, + .phylink_mac_select_pcs = an8855_phylink_mac_select_pcs, + .get_mac_eee = an8855_get_mac_eee, + .set_mac_eee = an8855_set_mac_eee, +}; + +static const struct an8855_dev_info an8855_table[] = { + [ID_AN8855] = { + .id = ID_AN8855, + .pcs_ops = &an8855_pcs_ops, + .sw_setup = an8855_setup, + .phy_read = an8855_phy_read, + .phy_write = an8855_phy_write, + .pad_setup = an8855_pad_setup, + .cpu_port_config = an8855_cpu_port_config, + .phy_mode_supported = an8855_phy_supported, + .mac_port_validate = an8855_mac_port_validate, + .mac_port_config = an8855_mac_config, + }, +}; + +static const struct of_device_id an8855_of_match[] = { + {.compatible = "airoha,an8855", .data = &an8855_table[ID_AN8855], + }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, an8855_of_match); + +static int +an8855_probe(struct mdio_device *mdiodev) +{ + struct an8855_priv *priv; + struct device_node *dn; + struct device_node *switch_node = NULL; + int ret; + + dn = mdiodev->dev.of_node; + + priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL); + if (!priv->ds) + return -ENOMEM; + + priv->ds->dev = &mdiodev->dev; + priv->ds->num_ports = AN8855_NUM_PORTS; + + /* Get the hardware identifier from the devicetree node. + * We will need it for some of the clock and regulator setup. + */ + priv->info = of_device_get_match_data(&mdiodev->dev); + if (!priv->info) + return -EINVAL; + + /* Sanity check if these required device operations are filled + * properly. + */ + if (!priv->info->sw_setup || !priv->info->pad_setup || + !priv->info->phy_read || !priv->info->phy_write || + !priv->info->phy_mode_supported || + !priv->info->mac_port_validate || + !priv->info->mac_port_config) + return -EINVAL; + + dev_info(&mdiodev->dev, "Airoha AN8855 DSA driver, version %s\n", + ARHT_AN8855_DSA_DRIVER_VER); + priv->phy_base = AN8855_GPHY_SMI_ADDR_DEFAULT; + priv->id = priv->info->id; + + priv->reset = devm_gpiod_get_optional(&mdiodev->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(priv->reset)) { + dev_err(&mdiodev->dev, "Couldn't get our reset line\n"); + return PTR_ERR(priv->reset); + } + + switch_node = of_find_node_by_name(NULL, "switch0"); + if (switch_node) { + priv->base = of_iomap(switch_node, 0); + if (priv->base == NULL) { + dev_err(&mdiodev->dev, "of_iomap failed\n"); + return -ENOMEM; + } + } + + ret = of_property_read_u32(dn, "changesmiaddr", &priv->phy_base_new); + if ((ret < 0) || (priv->phy_base_new > 0x1f)) + priv->phy_base_new = -1; + + /* Assign AN8855 interrupt pin */ + if (of_property_read_u32(dn, "airoha,intr", &priv->intr_pin)) + priv->intr_pin = AN8855_DFL_INTR_ID; + + if (of_property_read_u32(dn, "airoha,extSurge", &priv->extSurge)) + priv->extSurge = AN8855_DFL_EXT_SURGE; + + priv->bus = mdiodev->bus; + priv->dev = &mdiodev->dev; + priv->ds->priv = priv; + priv->ds->ops = &an8855_switch_ops; + mutex_init(&priv->reg_mutex); + dev_set_drvdata(&mdiodev->dev, priv); + + ret = dsa_register_switch(priv->ds); + if (ret) { + if (priv->base) + iounmap(priv->base); + + return ret; + } + an8855_nl_init(&priv); + + an8855_proc_device_init(); + return 0; +} + +static void +an8855_remove(struct mdio_device *mdiodev) +{ + struct an8855_priv *priv = dev_get_drvdata(&mdiodev->dev); + + dsa_unregister_switch(priv->ds); + mutex_destroy(&priv->reg_mutex); + + if (priv->base) + iounmap(priv->base); + + an8855_proc_device_exit(); + + an8855_nl_exit(); +} + +static void an8855_shutdown(struct mdio_device *mdiodev) +{ + struct an8855_priv *priv = dev_get_drvdata(&mdiodev->dev); + + if (!priv) + return; + + dsa_switch_shutdown(priv->ds); + + dev_set_drvdata(&mdiodev->dev, NULL); +} + +static struct mdio_driver an8855_mdio_driver = { + .probe = an8855_probe, + .remove = an8855_remove, + .shutdown = an8855_shutdown, + .mdiodrv.driver = { + .name = "an8855", + .of_match_table = an8855_of_match, + }, +}; + +mdio_module_driver(an8855_mdio_driver); + +MODULE_AUTHOR("Min Yao "); +MODULE_DESCRIPTION("Driver for Airoha AN8855 Switch"); +MODULE_VERSION(ARHT_AN8855_DSA_DRIVER_VER); +MODULE_LICENSE("GPL"); diff --git a/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855.h b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855.h new file mode 100644 index 00000000000000..36060b736e75f6 --- /dev/null +++ b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855.h @@ -0,0 +1,687 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2023 Min Yao + */ + +#ifndef __AN8855_H +#define __AN8855_H + +#define BITS(m, n) (~(BIT(m) - 1) & ((BIT(n) - 1) | BIT(n))) + +#define AN8855_NUM_PORTS 6 +#define AN8855_CPU_PORT 5 +#define AN8855_WORD_SIZE 4 +#define AN8855_NUM_FDB_RECORDS 2048 +#define AN8855_ALL_MEMBERS 0x3f +#define AN8855_RESERVED_VLAN 2 +#define AN8855_GPHY_SMI_ADDR_DEFAULT 1 +#define AN8855_DFL_INTR_ID 0xd +#define AN8855_DFL_EXT_SURGE 0x0 + +enum an8855_id { + ID_AN8855 = 0, +}; + +enum sgmii_mode { + SGMII_MODE_AN, + SGMII_MODE_FORCE, +}; + +/* Registers to mac forward control for unknown frames */ +#define AN8855_MFC 0x10200010 +#define CPU_EN BIT(15) +#define CPU_PORT(x) ((x) << 8) +#define CPU_MASK (0x9f << 8) + +#define AN8855_UNUF 0x102000b4 +#define AN8855_UNMF 0x102000b8 +#define AN8855_BCF 0x102000bc + +/* Registers for mirror port control */ +#define AN8855_MIR 0x102000cc +#define AN8855_MIRROR_EN BIT(7) +#define AN8855_MIRROR_MASK (0x1f) +#define AN8855_MIRROR_PORT_GET(x) ((x) & AN8855_MIRROR_MASK) +#define AN8855_MIRROR_PORT_SET(x) ((x) & AN8855_MIRROR_MASK) + +/* Registers for BPDU and PAE frame control*/ +#define AN8855_BPC 0x102000D0 +#define AN8855_BPDU_PORT_FW_MASK GENMASK(2, 0) + +enum an8855_bpdu_port_fw { + AN8855_BPDU_FOLLOW_MFC, + AN8855_BPDU_CPU_EXCLUDE = 4, + AN8855_BPDU_CPU_INCLUDE = 5, + AN8855_BPDU_CPU_ONLY = 6, + AN8855_BPDU_DROP = 7, +}; + +/* Registers for address table access */ +#define AN8855_ATA1 0x10200304 +#define AN8855_ATA2 0x10200308 + +/* Register for address table write data */ +#define AN8855_ATWD 0x10200324 +#define AN8855_ATWD2 0x10200328 + +/* Register for address table control */ +#define AN8855_ATC 0x10200300 +#define ATC_BUSY BIT(31) +#define ATC_INVALID ~BIT(30) +#define ATC_HASH 16 +#define ATC_HASH_MASK 0x1ff +#define ATC_HIT 12 +#define ATC_HIT_MASK 0xf +#define ATC_MAT(x) (((x) & 0x1f) << 7) +#define ATC_MAT_MACTAB ATC_MAT(1) + +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 table search read address */ +#define AN8855_ATRDS 0x10200330 +#define AN8855_ATRD0 0x10200334 +#define CVID 10 +#define CVID_MASK 0xfff + +enum an8855_fdb_type { + AN8855_MAC_TB_TY_MAC = 0, + AN8855_MAC_TB_TY_DIP = 1, + AN8855_MAC_TB_TY_DIP_SIP = 2, +}; + +#define AN8855_ATRD1 0x10200338 +#define MAC_BYTE_4 24 +#define MAC_BYTE_5 16 +#define AGE_TIMER 3 +#define AGE_TIMER_MASK 0x1ff + +#define AN8855_ATRD2 0x1020033c +#define MAC_BYTE_0 24 +#define MAC_BYTE_1 16 +#define MAC_BYTE_2 8 +#define MAC_BYTE_3 0 +#define MAC_BYTE_MASK 0xff + +#define AN8855_ATRD3 0x10200340 +#define PORT_MAP 4 +#define PORT_MAP_MASK 0xff + +/* Register for vlan table control */ +#define AN8855_VTCR 0x10200600 +#define VTCR_BUSY BIT(31) +#define VTCR_FUNC(x) (((x) & 0xf) << 12) +#define VTCR_VID ((x) & 0xfff) + +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 + +/* Independent VLAN Learning */ +#define IVL_MAC BIT(5) +/* Per VLAN Egress Tag Control */ +#define VTAG_EN BIT(10) +/* Egress Tag Control */ +#define PORT_EG_CTRL_SHIFT 12 +/* VLAN Member Control */ +#define PORT_MEM_SHFT 26 +#define PORT_MEM_MASK 0x7f +#define PORT_MEM(x) (((x) & PORT_MEM_MASK) << PORT_MEM_SHFT) +/* VLAN Entry Valid */ +#define VLAN_VALID BIT(0) + +#define AN8855_VAWD1 0x10200608 +#define PORT_STAG BIT(1) +/* Egress Tag Control */ +#define ETAG_CTRL_P(p, x) (((x) & 0x3) << ((p) << 1)) +#define ETAG_CTRL_P_MASK(p) ETAG_CTRL_P(p, 3) +#define ETAG_CTRL_MASK (0x3FFF) + +#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)) +#define FID_PST(x) ((x) & 0x3) +#define FID_PST_MASK FID_PST(0x3) + +enum an8855_stp_state { + AN8855_STP_DISABLED = 0, + AN8855_STP_BLOCKING = 1, + AN8855_STP_LISTENING = 1, + AN8855_STP_LEARNING = 2, + AN8855_STP_FORWARDING = 3 +}; + +/* Register for port control */ +#define AN8855_PCR_P(x) (0x10208004 + ((x) * 0x200)) +#define PORT_TX_MIR BIT(20) +#define PORT_RX_MIR BIT(16) +#define PORT_VLAN(x) ((x) & 0x3) + +enum an8855_port_mode { + /* Port Matrix Mode: Frames are forwarded by the PCR_MATRIX members. */ + AN8855_PORT_MATRIX_MODE = PORT_VLAN(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 = PORT_VLAN(1), + + /* Security Mode: Discard any frame due to ingress membership + * violation or VID missed on the VLAN table. + */ + AN8855_PORT_SECURITY_MODE = PORT_VLAN(3), +}; + +#define PORT_PRI(x) (((x) & 0x7) << 24) +#define EG_TAG(x) (((x) & 0x3) << 28) +#define PCR_PORT_VLAN_MASK PORT_VLAN(3) + +/* Register for port security control */ +#define AN8855_PSC_P(x) (0x1020800c + ((x) * 0x200)) +#define SA_DIS BIT(4) + +/* Register for port vlan control */ +#define AN8855_PVC_P(x) (0x10208010 + ((x) * 0x200)) +#define PORT_SPEC_REPLACE_MODE BIT(11) +#define PORT_SPEC_TAG BIT(5) +#define PVC_EG_TAG(x) (((x) & 0x7) << 8) +#define PVC_EG_TAG_MASK PVC_EG_TAG(7) +#define VLAN_ATTR(x) (((x) & 0x3) << 6) +#define VLAN_ATTR_MASK VLAN_ATTR(3) + +#define AN8855_PORTMATRIX_P(x) (0x10208044 + ((x) * 0x200)) +#define PORTMATRIX_MATRIX(x) ((x) & 0x3f) +#define PORTMATRIX_MASK PORTMATRIX_MATRIX(0x3f) +#define PORTMATRIX_CLR PORTMATRIX_MATRIX(0) + +enum an8855_vlan_port_eg_tag { + AN8855_VLAN_EG_DISABLED = 0, + AN8855_VLAN_EG_CONSISTENT = 1, +}; + +enum an8855_vlan_port_attr { + AN8855_VLAN_USER = 0, + AN8855_VLAN_TRANSPARENT = 3, +}; + +/* Register for port PVID */ +#define AN8855_PVID_P(x) (0x10208048 + ((x) * 0x200)) +#define G0_PORT_VID(x) (((x) & 0xfff) << 0) +#define G0_PORT_VID_MASK G0_PORT_VID(0xfff) +#define G0_PORT_VID_DEF G0_PORT_VID(1) + +/* Register for port MAC control register */ +#define AN8855_PMCR_P(x) (0x10210000 + ((x) * 0x200)) +#define PMCR_IFG_XMIT(x) (((x) & 0x3) << 20) +#define PMCR_EXT_PHY BIT(19) +#define PMCR_MAC_MODE BIT(18) +#define PMCR_FORCE_MODE BIT(31) +#define PMCR_TX_EN BIT(16) +#define PMCR_RX_EN BIT(15) +#define PMCR_BACKOFF_EN BIT(12) +#define PMCR_BACKPR_EN BIT(11) +#define PMCR_FORCE_EEE2P5G BIT(8) +#define PMCR_FORCE_EEE1G BIT(7) +#define PMCR_FORCE_EEE100 BIT(6) +#define PMCR_TX_FC_EN BIT(5) +#define PMCR_RX_FC_EN BIT(4) +#define PMCR_FORCE_SPEED_2500 (0x3 << 28) +#define PMCR_FORCE_SPEED_1000 (0x2 << 28) +#define PMCR_FORCE_SPEED_100 (0x1 << 28) +#define PMCR_FORCE_FDX BIT(25) +#define PMCR_FORCE_LNK BIT(24) +#define PMCR_SPEED_MASK BITS(28, 30) +#define AN8855_FORCE_LNK BIT(31) +#define AN8855_FORCE_MODE (AN8855_FORCE_LNK) +#define PMCR_LINK_SETTINGS_MASK (PMCR_TX_EN | \ + PMCR_RX_EN | PMCR_FORCE_SPEED_2500 | \ + PMCR_TX_FC_EN | PMCR_RX_FC_EN | \ + PMCR_FORCE_FDX | PMCR_FORCE_LNK) +#define PMCR_CPU_PORT_SETTING(id) (AN8855_FORCE_MODE | \ + PMCR_IFG_XMIT(1) | PMCR_MAC_MODE | \ + PMCR_BACKOFF_EN | PMCR_BACKPR_EN | \ + PMCR_TX_EN | PMCR_RX_EN | \ + PMCR_TX_FC_EN | PMCR_RX_FC_EN | \ + PMCR_FORCE_SPEED_2500 | \ + PMCR_FORCE_FDX | PMCR_FORCE_LNK) + +#define AN8855_PMSR_P(x) (0x10210010 + (x) * 0x200) +#define PMSR_EEE1G BIT(7) +#define PMSR_EEE100M BIT(6) +#define PMSR_RX_FC BIT(5) +#define PMSR_TX_FC BIT(4) +#define PMSR_SPEED_2500 (0x3 << 28) +#define PMSR_SPEED_1000 (0x2 << 28) +#define PMSR_SPEED_100 (0x1 << 28) +#define PMSR_SPEED_10 (0x0 << 28) +#define PMSR_SPEED_MASK BITS(28, 30) +#define PMSR_DPX BIT(25) +#define PMSR_LINK BIT(24) + +#define AN8855_PMEEECR_P(x) (0x10210004 + (x) * 0x200) +#define WAKEUP_TIME_2500(x) ((x & 0xFF) << 16) +#define WAKEUP_TIME_1000(x) ((x & 0xFF) << 8) +#define WAKEUP_TIME_100(x) ((x & 0xFF) << 0) +#define LPI_MODE_EN BIT(31) +#define AN8855_PMEEECR2_P(x) (0x10210008 + (x) * 0x200) +#define WAKEUP_TIME_5000(x) ((x & 0xFF) << 0) + +#define AN8855_CKGCR (0x10213e1c) +#define LPI_TXIDLE_THD 14 +#define LPI_TXIDLE_THD_MASK BITS(14, 31) +#define CKG_LNKDN_GLB_STOP 0x01 +#define CKG_LNKDN_PORT_STOP 0x02 + +/* Register for MIB */ +#define AN8855_PORT_MIB_COUNTER(x) (0x10214000 + (x) * 0x200) +#define AN8855_MIB_CCR 0x10213e30 +#define CCR_MIB_ENABLE BIT(31) +#define CCR_RX_OCT_CNT_GOOD BIT(7) +#define CCR_RX_OCT_CNT_BAD BIT(6) +#define CCR_TX_OCT_CNT_GOOD BIT(5) +#define CCR_TX_OCT_CNT_BAD BIT(4) +#define CCR_RX_OCT_CNT_GOOD_2 BIT(3) +#define CCR_RX_OCT_CNT_BAD_2 BIT(2) +#define CCR_TX_OCT_CNT_GOOD_2 BIT(1) +#define CCR_TX_OCT_CNT_BAD_2 BIT(0) +#define CCR_MIB_FLUSH (CCR_RX_OCT_CNT_GOOD | \ + CCR_RX_OCT_CNT_BAD | \ + CCR_TX_OCT_CNT_GOOD | \ + CCR_TX_OCT_CNT_BAD | \ + CCR_RX_OCT_CNT_BAD_2 | \ + CCR_TX_OCT_CNT_BAD_2) +#define CCR_MIB_ACTIVATE (CCR_MIB_ENABLE | \ + CCR_RX_OCT_CNT_GOOD | \ + CCR_RX_OCT_CNT_BAD | \ + CCR_TX_OCT_CNT_GOOD | \ + CCR_TX_OCT_CNT_BAD | \ + CCR_RX_OCT_CNT_BAD_2 | \ + CCR_TX_OCT_CNT_BAD_2) + +/* AN8855 SGMII register group */ +#define AN8855_SGMII_REG_BASE 0x10220000 +#define AN8855_SGMII_REG(p, r) (AN8855_SGMII_REG_BASE + \ + ((p) - 5) * 0x1000 + (r)) + +/* Register forSGMII PCS_CONTROL_1 */ +#define AN8855_PCS_CONTROL_1(p) AN8855_SGMII_REG(p, 0x00) +#define AN8855_SGMII_AN_ENABLE BIT(12) +#define AN8855_SGMII_AN_RESTART BIT(9) + +/* Register for system reset */ +#define AN8855_RST_CTRL 0x100050c0 +#define SYS_CTRL_SYS_RST BIT(31) + +#define INT_MASK 0x100050F0 +#define INT_SYS_BIT BIT(15) + +#define RG_CLK_CPU_ICG 0x10005034 +#define MCU_ENABLE BIT(3) + +#define RG_TIMER_CTL 0x1000a100 +#define WDOG_ENABLE BIT(25) + +#define CKGCR 0x10213E1C +#define CKG_LNKDN_GLB_STOP 0x01 +#define CKG_LNKDN_PORT_STOP 0x02 + +#define PKG_SEL 0x10000094 +#define PAG_SEL_AN8855H 0x2 + +/* Register for hw trap status */ +#define AN8855_HWTRAP 0x1000009c + +#define AN8855_CREV 0x10005000 +#define AN8855_ID 0x8855 + +#define SCU_BASE 0x10000000 +#define RG_RGMII_TXCK_C (SCU_BASE + 0x1d0) +#define RG_GPIO_LED_MODE (SCU_BASE + 0x0054) +#define RG_GPIO_LED_SEL(i) (SCU_BASE + (0x0058 + ((i) * 4))) +#define RG_INTB_MODE (SCU_BASE + 0x0080) +#define RG_GDMP_RAM (SCU_BASE + 0x10000) + +#define RG_GPIO_L_INV (SCU_BASE + 0x0010) +#define RG_GPIO_CTRL (SCU_BASE + 0xa300) +#define RG_GPIO_DATA (SCU_BASE + 0xa304) +#define RG_GPIO_OE (SCU_BASE + 0xa314) + +#define HSGMII_AN_CSR_BASE 0x10220000 +#define SGMII_REG_AN0 (HSGMII_AN_CSR_BASE + 0x000) +#define SGMII_REG_AN_13 (HSGMII_AN_CSR_BASE + 0x034) +#define SGMII_REG_AN_FORCE_CL37 (HSGMII_AN_CSR_BASE + 0x060) + +#define HSGMII_CSR_PCS_BASE 0x10220000 +#define RG_HSGMII_PCS_CTROL_1 (HSGMII_CSR_PCS_BASE + 0xa00) +#define RG_AN_SGMII_MODE_FORCE (HSGMII_CSR_PCS_BASE + 0xa24) + +#define MULTI_SGMII_CSR_BASE 0x10224000 +#define SGMII_STS_CTRL_0 (MULTI_SGMII_CSR_BASE + 0x018) +#define MSG_RX_CTRL_0 (MULTI_SGMII_CSR_BASE + 0x100) +#define MSG_RX_LIK_STS_0 (MULTI_SGMII_CSR_BASE + 0x514) +#define MSG_RX_LIK_STS_2 (MULTI_SGMII_CSR_BASE + 0x51c) +#define PHY_RX_FORCE_CTRL_0 (MULTI_SGMII_CSR_BASE + 0x520) + +#define XFI_CSR_PCS_BASE 0x10225000 +#define RG_USXGMII_AN_CONTROL_0 (XFI_CSR_PCS_BASE + 0xbf8) + +#define MULTI_PHY_RA_CSR_BASE 0x10226000 +#define RG_RATE_ADAPT_CTRL_0 (MULTI_PHY_RA_CSR_BASE + 0x000) +#define RATE_ADP_P0_CTRL_0 (MULTI_PHY_RA_CSR_BASE + 0x100) +#define MII_RA_AN_ENABLE (MULTI_PHY_RA_CSR_BASE + 0x300) + +#define QP_DIG_CSR_BASE 0x1022a000 +#define QP_CK_RST_CTRL_4 (QP_DIG_CSR_BASE + 0x310) +#define QP_DIG_MODE_CTRL_0 (QP_DIG_CSR_BASE + 0x324) +#define QP_DIG_MODE_CTRL_1 (QP_DIG_CSR_BASE + 0x330) + +#define SERDES_WRAPPER_BASE 0x1022c000 +#define USGMII_CTRL_0 (SERDES_WRAPPER_BASE + 0x000) + +#define QP_PMA_TOP_BASE 0x1022e000 +#define PON_RXFEDIG_CTRL_0 (QP_PMA_TOP_BASE + 0x100) +#define PON_RXFEDIG_CTRL_9 (QP_PMA_TOP_BASE + 0x124) + +#define SS_LCPLL_PWCTL_SETTING_2 (QP_PMA_TOP_BASE + 0x208) +#define SS_LCPLL_TDC_FLT_2 (QP_PMA_TOP_BASE + 0x230) +#define SS_LCPLL_TDC_FLT_5 (QP_PMA_TOP_BASE + 0x23c) +#define SS_LCPLL_TDC_PCW_1 (QP_PMA_TOP_BASE + 0x248) +#define INTF_CTRL_8 (QP_PMA_TOP_BASE + 0x320) +#define INTF_CTRL_9 (QP_PMA_TOP_BASE + 0x324) +#define INTF_CTRL_10 (QP_PMA_TOP_BASE + 0x328) +#define INTF_CTRL_11 (QP_PMA_TOP_BASE + 0x32c) +#define PLL_CTRL_0 (QP_PMA_TOP_BASE + 0x400) +#define PLL_CTRL_2 (QP_PMA_TOP_BASE + 0x408) +#define PLL_CTRL_3 (QP_PMA_TOP_BASE + 0x40c) +#define PLL_CTRL_4 (QP_PMA_TOP_BASE + 0x410) +#define PLL_CK_CTRL_0 (QP_PMA_TOP_BASE + 0x414) +#define RX_DLY_0 (QP_PMA_TOP_BASE + 0x614) +#define RX_CTRL_2 (QP_PMA_TOP_BASE + 0x630) +#define RX_CTRL_5 (QP_PMA_TOP_BASE + 0x63c) +#define RX_CTRL_6 (QP_PMA_TOP_BASE + 0x640) +#define RX_CTRL_7 (QP_PMA_TOP_BASE + 0x644) +#define RX_CTRL_8 (QP_PMA_TOP_BASE + 0x648) +#define RX_CTRL_26 (QP_PMA_TOP_BASE + 0x690) +#define RX_CTRL_42 (QP_PMA_TOP_BASE + 0x6d0) + +#define QP_ANA_CSR_BASE 0x1022f000 +#define RG_QP_RX_DAC_EN (QP_ANA_CSR_BASE + 0x00) +#define RG_QP_RXAFE_RESERVE (QP_ANA_CSR_BASE + 0x04) +#define RG_QP_CDR_LPF_BOT_LIM (QP_ANA_CSR_BASE + 0x08) +#define RG_QP_CDR_LPF_MJV_LIM (QP_ANA_CSR_BASE + 0x0c) +#define RG_QP_CDR_LPF_SETVALUE (QP_ANA_CSR_BASE + 0x14) +#define RG_QP_CDR_PR_CKREF_DIV1 (QP_ANA_CSR_BASE + 0x18) +#define RG_QP_CDR_PR_KBAND_DIV_PCIE (QP_ANA_CSR_BASE + 0x1c) +#define RG_QP_CDR_FORCE_IBANDLPF_R_OFF (QP_ANA_CSR_BASE + 0x20) +#define RG_QP_TX_MODE_16B_EN (QP_ANA_CSR_BASE + 0x28) +#define RG_QP_PLL_IPLL_DIG_PWR_SEL (QP_ANA_CSR_BASE + 0x3c) +#define RG_QP_PLL_SDM_ORD (QP_ANA_CSR_BASE + 0x40) + +#define ETHER_SYS_BASE 0x1028c800 +#define RG_GPHY_AFE_PWD (ETHER_SYS_BASE + 0x40) +#define RG_GPHY_SMI_ADDR (ETHER_SYS_BASE + 0x48) + +#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; + u8 aging; + u8 mac[6]; + bool noarp; + u8 live; + u8 type; + u8 fid; + u8 ivl; +}; + +/* Definition of LED */ +#define LED_ON_EVENT (LED_ON_EVT_LINK_1000M | \ + LED_ON_EVT_LINK_100M | LED_ON_EVT_LINK_10M |\ + LED_ON_EVT_LINK_HD | LED_ON_EVT_LINK_FD) + +#define LED_BLK_EVENT (LED_BLK_EVT_1000M_TX_ACT | \ + LED_BLK_EVT_1000M_RX_ACT | \ + LED_BLK_EVT_100M_TX_ACT | \ + LED_BLK_EVT_100M_RX_ACT | \ + LED_BLK_EVT_10M_TX_ACT | \ + LED_BLK_EVT_10M_RX_ACT) + +#define LED_FREQ AIR_LED_BLK_DUR_64M + +enum phy_led_idx { + P0_LED0, + P0_LED1, + P0_LED2, + P0_LED3, + P1_LED0, + P1_LED1, + P1_LED2, + P1_LED3, + P2_LED0, + P2_LED1, + P2_LED2, + P2_LED3, + P3_LED0, + P3_LED1, + P3_LED2, + P3_LED3, + P4_LED0, + P4_LED1, + P4_LED2, + P4_LED3, + PHY_LED_MAX +}; + +/* TBD */ +enum an8855_led_blk_dur { + AIR_LED_BLK_DUR_32M, + AIR_LED_BLK_DUR_64M, + AIR_LED_BLK_DUR_128M, + AIR_LED_BLK_DUR_256M, + AIR_LED_BLK_DUR_512M, + AIR_LED_BLK_DUR_1024M, + AIR_LED_BLK_DUR_LAST +}; + +enum an8855_led_polarity { + LED_LOW, + LED_HIGH, +}; +enum an8855_led_mode { + AN8855_LED_MODE_DISABLE, + AN8855_LED_MODE_USER_DEFINE, + AN8855_LED_MODE_LAST +}; + +struct an8855_led_cfg { + u16 en; + u8 phy_led_idx; + u16 pol; + u16 on_cfg; + u16 blk_cfg; + u8 led_freq; +}; + +/* struct an8855_port - This is the main data structure for holding the state + * of the port. + * @enable: The status used for show port is enabled or not. + * @pm: The matrix used to show all connections with the port. + * @pvid: The VLAN specified is to be considered a PVID at ingress. Any + * untagged frames will be assigned to the related VLAN. + * @vlan_filtering: The flags indicating whether the port that can recognize + * VLAN-tagged frames. + */ +struct an8855_port { + bool enable; + u32 pm; + u16 pvid; +}; + +/* struct an8855_info - This is the main data structure for holding the specific + * part for each supported device + * @sw_setup: Holding the handler to a device initialization + * @phy_read: Holding the way reading PHY port + * @phy_write: Holding the way writing PHY port + * @pad_setup: Holding the way setting up the bus pad for a certain + * MAC port + * @phy_mode_supported: Check if the PHY type is being supported on a certain + * port + * @mac_port_validate: Holding the way to set addition validate type for a + * certan MAC port + * @mac_port_get_state: Holding the way getting the MAC/PCS state for a certain + * MAC port + * @mac_port_config: Holding the way setting up the PHY attribute to a + * certain MAC port + * @mac_pcs_an_restart Holding the way restarting PCS autonegotiation for a + * certain MAC port + * @mac_pcs_link_up: Holding the way setting up the PHY attribute to the pcs + * of the certain MAC port + */ +struct an8855_dev_info { + enum an8855_id id; + + const struct phylink_pcs_ops *pcs_ops; + + int (*sw_setup)(struct dsa_switch *ds); + int (*phy_read)(struct dsa_switch *ds, int port, int regnum); + int (*phy_write)(struct dsa_switch *ds, int port, int regnum, + u16 val); + int (*pad_setup)(struct dsa_switch *ds, phy_interface_t interface); + int (*cpu_port_config)(struct dsa_switch *ds, int port); + bool (*phy_mode_supported)(struct dsa_switch *ds, int port, + const struct phylink_link_state *state); + void (*mac_port_validate)(struct dsa_switch *ds, int port, + unsigned long *supported); + int (*mac_port_config)(struct dsa_switch *ds, int port, + unsigned int mode, phy_interface_t interface); +}; + +struct an8855_priv; + +struct an8855_pcs { + struct phylink_pcs pcs; + struct an8855_priv *priv; + int port; +}; + +/* struct an8855_priv - This is the main data structure for holding the state + * of the driver + * @dev: The device pointer + * @ds: The pointer to the dsa core structure + * @bus: The bus used for the device and built-in PHY + * @rstc: The pointer to reset control used by MCM + * @core_pwr: The power supplied into the core + * @io_pwr: The power supplied into the I/O + * @reset: The descriptor for GPIO line tied to its reset pin + * @mcm: Flag for distinguishing if standalone IC or module + * coupling + * @ports: Holding the state among ports + * @reg_mutex: The lock for protecting among process accessing + * registers + * @p6_interface Holding the current port 6 interface + * @p5_intf_sel: Holding the current port 5 interface select + */ +struct an8855_priv { + struct device *dev; + struct dsa_switch *ds; + struct mii_bus *bus; + struct reset_control *rstc; + struct regulator *core_pwr; + struct regulator *io_pwr; + struct gpio_desc *reset; + void __iomem *base; + const struct an8855_dev_info *info; + unsigned int phy_base; + int phy_base_new; + unsigned int id; + u32 intr_pin; + phy_interface_t p5_interface; + unsigned int p5_intf_sel; + u8 mirror_rx; + u8 mirror_tx; + u8 eee_enable; + u32 extSurge; + + struct an8855_port ports[AN8855_NUM_PORTS]; + struct an8855_pcs pcs[AN8855_NUM_PORTS]; + /* protect among processes for registers access */ + struct mutex reg_mutex; +}; + +struct an8855_hw_vlan_entry { + int port; + u8 old_members; + bool untagged; +}; + +static inline void an8855_hw_vlan_entry_init(struct an8855_hw_vlan_entry *e, + int port, bool untagged) +{ + e->port = port; + e->untagged = untagged; +} + +typedef void (*an8855_vlan_op) (struct an8855_priv *, + struct an8855_hw_vlan_entry *); + +struct an8855_hw_stats { + const char *string; + u16 reg; + u8 sizeof_stat; +}; + +struct an8855_dummy_poll { + struct an8855_priv *priv; + u32 reg; +}; + +static inline void INIT_AN8855_DUMMY_POLL(struct an8855_dummy_poll *p, + struct an8855_priv *priv, u32 reg) +{ + p->priv = priv; + p->reg = reg; +} + +int an8855_phy_setup(struct dsa_switch *ds); +u32 an8855_read(struct an8855_priv *priv, u32 reg); +void an8855_write(struct an8855_priv *priv, u32 reg, u32 val); +int an8855_phy_cl22_read(struct an8855_priv *priv, int port, int regnum); +int an8855_phy_cl22_write(struct an8855_priv *priv, int port, + int regnum, u16 val); +int an8855_phy_cl45_read(struct an8855_priv *priv, int port, int devad, + int regnum); +int an8855_phy_cl45_write(struct an8855_priv *priv, int port, int devad, + int regnum, u16 val); +#endif /* __AN8855_H */ diff --git a/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855_nl.c b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855_nl.c new file mode 100644 index 00000000000000..45099d886658a0 --- /dev/null +++ b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855_nl.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023 Airoha Inc. + * Author: Min Yao + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "an8855.h" +#include "an8855_nl.h" + +struct an8855_nl_cmd_item { + enum an8855_cmd cmd; + bool require_dev; + int (*process)(struct genl_info *info); + u32 nr_required_attrs; + const enum an8855_attr *required_attrs; +}; + +struct an8855_priv *an8855_sw_priv; + +static DEFINE_MUTEX(an8855_devs_lock); + +void +an8855_put(void) +{ + mutex_unlock(&an8855_devs_lock); +} + +void +an8855_lock(void) +{ + mutex_lock(&an8855_devs_lock); +} + +static int an8855_nl_response(struct sk_buff *skb, struct genl_info *info); + +static const struct nla_policy an8855_nl_cmd_policy[] = { + [AN8855_ATTR_TYPE_MESG] = {.type = NLA_STRING}, + [AN8855_ATTR_TYPE_PHY] = {.type = NLA_S32}, + [AN8855_ATTR_TYPE_REG] = {.type = NLA_U32}, + [AN8855_ATTR_TYPE_VAL] = {.type = NLA_U32}, + [AN8855_ATTR_TYPE_DEV_NAME] = {.type = NLA_S32}, + [AN8855_ATTR_TYPE_DEV_ID] = {.type = NLA_S32}, + [AN8855_ATTR_TYPE_DEVAD] = {.type = NLA_S32}, +}; + +static const struct genl_ops an8855_nl_ops[] = { + { + .cmd = AN8855_CMD_REQUEST, + .doit = an8855_nl_response, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = AN8855_CMD_READ, + .doit = an8855_nl_response, + .flags = GENL_ADMIN_PERM, + }, + { + .cmd = AN8855_CMD_WRITE, + .doit = an8855_nl_response, + .flags = GENL_ADMIN_PERM, + }, +}; + +static struct genl_family an8855_nl_family = { + .name = AN8855_DSA_GENL_NAME, + .version = AN8855_GENL_VERSION, + .maxattr = AN8855_NR_ATTR_TYPE, + .ops = an8855_nl_ops, + .n_ops = ARRAY_SIZE(an8855_nl_ops), + .policy = an8855_nl_cmd_policy, +}; + +static int +an8855_nl_prepare_reply(struct genl_info *info, u8 cmd, + struct sk_buff **skbp) +{ + struct sk_buff *msg; + void *reply; + + if (!info) + return -EINVAL; + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + /* Construct send-back message header */ + reply = genlmsg_put(msg, info->snd_portid, info->snd_seq, + &an8855_nl_family, 0, cmd); + if (!reply) { + nlmsg_free(msg); + return -EINVAL; + } + + *skbp = msg; + return 0; +} + +static int +an8855_nl_send_reply(struct sk_buff *skb, struct genl_info *info) +{ + struct genlmsghdr *genlhdr = nlmsg_data(nlmsg_hdr(skb)); + void *reply = genlmsg_data(genlhdr); + + /* Finalize a generic netlink message (update message header) */ + genlmsg_end(skb, reply); + + /* reply to a request */ + return genlmsg_reply(skb, info); +} + +static s32 +an8855_nl_get_s32(struct genl_info *info, enum an8855_attr attr, + s32 defval) +{ + struct nlattr *na; + + na = info->attrs[attr]; + if (na) + return nla_get_s32(na); + + return defval; +} + +static int +an8855_nl_get_u32(struct genl_info *info, enum an8855_attr attr, + u32 *val) +{ + struct nlattr *na; + + na = info->attrs[attr]; + if (na) { + *val = nla_get_u32(na); + return 0; + } + + return -1; +} + +static int +an8855_nl_reply_read(struct genl_info *info) +{ + struct sk_buff *rep_skb = NULL; + s32 phy, devad; + u32 reg = 0; + int value = 0; + int ret = 0; + + phy = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_PHY, -1); + devad = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_DEVAD, -1); + + if (an8855_nl_get_u32(info, AN8855_ATTR_TYPE_REG, ®)) + goto err; + + ret = an8855_nl_prepare_reply(info, AN8855_CMD_READ, &rep_skb); + if (ret < 0) + goto err; + if (phy >= 0) { + if (devad < 0) + value = an8855_phy_cl22_read(an8855_sw_priv, phy, reg); + else + value = an8855_phy_cl45_read(an8855_sw_priv, phy, + devad, reg); + } else + value = an8855_read(an8855_sw_priv, reg); + ret = nla_put_u32(rep_skb, AN8855_ATTR_TYPE_REG, reg); + if (ret < 0) + goto err; + + ret = nla_put_u32(rep_skb, AN8855_ATTR_TYPE_VAL, value); + if (ret < 0) + goto err; + + return an8855_nl_send_reply(rep_skb, info); + +err: + if (rep_skb) + nlmsg_free(rep_skb); + + return ret; +} + +static int +an8855_nl_reply_write(struct genl_info *info) +{ + struct sk_buff *rep_skb = NULL; + s32 phy, devad; + u32 value = 0, reg = 0; + int ret = 0; + + phy = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_PHY, -1); + devad = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_DEVAD, -1); + if (an8855_nl_get_u32(info, AN8855_ATTR_TYPE_REG, ®)) + goto err; + + if (an8855_nl_get_u32(info, AN8855_ATTR_TYPE_VAL, &value)) + goto err; + + ret = an8855_nl_prepare_reply(info, AN8855_CMD_WRITE, &rep_skb); + if (ret < 0) + goto err; + if (phy >= 0) { + if (devad < 0) + an8855_phy_cl22_write(an8855_sw_priv, phy, reg, value); + else + an8855_phy_cl45_write(an8855_sw_priv, phy, devad, reg, + value); + } else + an8855_write(an8855_sw_priv, reg, value); + ret = nla_put_u32(rep_skb, AN8855_ATTR_TYPE_REG, reg); + if (ret < 0) + goto err; + + ret = nla_put_u32(rep_skb, AN8855_ATTR_TYPE_VAL, value); + if (ret < 0) + goto err; + + return an8855_nl_send_reply(rep_skb, info); + +err: + if (rep_skb) + nlmsg_free(rep_skb); + + return ret; +} + +static const enum an8855_attr an8855_nl_cmd_read_attrs[] = { + AN8855_ATTR_TYPE_REG +}; + +static const enum an8855_attr an8855_nl_cmd_write_attrs[] = { + AN8855_ATTR_TYPE_REG, + AN8855_ATTR_TYPE_VAL +}; + +static const struct an8855_nl_cmd_item an8855_nl_cmds[] = { + { + .cmd = AN8855_CMD_READ, + .require_dev = true, + .process = an8855_nl_reply_read, + .required_attrs = an8855_nl_cmd_read_attrs, + .nr_required_attrs = ARRAY_SIZE(an8855_nl_cmd_read_attrs), + }, + { + .cmd = AN8855_CMD_WRITE, + .require_dev = true, + .process = an8855_nl_reply_write, + .required_attrs = an8855_nl_cmd_write_attrs, + .nr_required_attrs = ARRAY_SIZE(an8855_nl_cmd_write_attrs), + } +}; + +static int +an8855_nl_response(struct sk_buff *skb, struct genl_info *info) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct an8855_nl_cmd_item *cmditem = NULL; + u32 sat_req_attrs = 0; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(an8855_nl_cmds); i++) { + if (hdr->cmd == an8855_nl_cmds[i].cmd) { + cmditem = &an8855_nl_cmds[i]; + break; + } + } + + if (!cmditem) { + pr_info("an8855-nl: unknown cmd %u\n", hdr->cmd); + return -EINVAL; + } + + for (i = 0; i < cmditem->nr_required_attrs; i++) { + if (info->attrs[cmditem->required_attrs[i]]) + sat_req_attrs++; + } + + if (sat_req_attrs != cmditem->nr_required_attrs) { + pr_info("an8855-nl: missing required attr(s) for cmd %u\n", + hdr->cmd); + return -EINVAL; + } + + ret = cmditem->process(info); + + an8855_put(); + + return ret; +} + +int +an8855_nl_init(struct an8855_priv **priv) +{ + int ret; + + pr_info("an8855-nl: genl_register_family_with_ops\n"); + + an8855_sw_priv = *priv; + ret = genl_register_family(&an8855_nl_family); + if (ret) + return ret; + + return 0; +} + +void +an8855_nl_exit(void) +{ + an8855_sw_priv = NULL; + genl_unregister_family(&an8855_nl_family); +} diff --git a/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855_nl.h b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855_nl.h new file mode 100644 index 00000000000000..f8a462d5dc21be --- /dev/null +++ b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855_nl.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023 Airoha Inc. + * Author: Min Yao + */ + +#ifndef _AN8855_NL_H_ +#define _AN8855_NL_H_ + +#define AN8855_DSA_GENL_NAME "an8855_dsa" +#define AN8855_GENL_VERSION 0x1 + +enum an8855_cmd { + AN8855_CMD_UNSPEC = 0, + AN8855_CMD_REQUEST, + AN8855_CMD_REPLY, + AN8855_CMD_READ, + AN8855_CMD_WRITE, + + __AN8855_CMD_MAX, +}; + +enum an8855_attr { + AN8855_ATTR_TYPE_UNSPEC = 0, + AN8855_ATTR_TYPE_MESG, + AN8855_ATTR_TYPE_PHY, + AN8855_ATTR_TYPE_DEVAD, + AN8855_ATTR_TYPE_REG, + AN8855_ATTR_TYPE_VAL, + AN8855_ATTR_TYPE_DEV_NAME, + AN8855_ATTR_TYPE_DEV_ID, + + __AN8855_ATTR_TYPE_MAX, +}; + +#define AN8855_NR_ATTR_TYPE (__AN8855_ATTR_TYPE_MAX - 1) + +#ifdef __KERNEL__ +int an8855_nl_init(struct an8855_priv **priv); +void an8855_nl_exit(void); +#endif /* __KERNEL__ */ + +#endif /* _AN8855_NL_H_ */ diff --git a/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855_phy.c b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855_phy.c new file mode 100644 index 00000000000000..b147075203780e --- /dev/null +++ b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855_phy.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Common part for Airoha AN8855 gigabit switch + * + * Copyright (C) 2023 Airoha Inc. All Rights Reserved. + * + * Author: Min Yao + */ + +#include +#include +#include +#include +#include "an8855.h" +#include "an8855_phy.h" + +#define AN8855_EFUSE_DATA0 0x1000a500 + +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 u8 shift_check(u8 base) +{ + u8 i; + u32 sz = sizeof(dsa_r50ohm_table)/sizeof(u8); + + for (i = 0; i < sz; ++i) + if (dsa_r50ohm_table[i] == base) + break; + + if (i < 8 || i >= sz) + return 25; /* index of 94 */ + + return (i - 8); +} + +static u8 get_shift_val(u8 idx) +{ + return dsa_r50ohm_table[idx]; +} + +static void +an8855_switch_phy_write(struct dsa_switch *ds, u32 port_num, + u32 reg_addr, u32 write_data) +{ + struct an8855_priv *priv = ds->priv; + + priv->info->phy_write(ds, port_num, reg_addr, write_data); +} + +static u32 +an8855_switch_phy_read(struct dsa_switch *ds, u32 port_num, + u32 reg_addr) +{ + struct an8855_priv *priv = ds->priv; + + return priv->info->phy_read(ds, port_num, reg_addr); +} + +static void +an8855_phy_setting(struct dsa_switch *ds) +{ + struct an8855_priv *priv = ds->priv; + int i, j; + u8 shift_sel = 0, rsel_tx_a = 0, rsel_tx_b = 0; + u8 rsel_tx_c = 0, rsel_tx_d = 0; + u16 cl45_data = 0; + u32 val; + + /* Release power down */ + an8855_write(priv, RG_GPHY_AFE_PWD, 0x0); + for (i = 0; i < AN8855_NUM_PHYS; i++) { + /* Enable HW auto downshift */ + an8855_switch_phy_write(ds, i, 0x1f, 0x1); + val = an8855_switch_phy_read(ds, i, PHY_EXT_REG_14); + val |= PHY_EN_DOWN_SHFIT; + an8855_switch_phy_write(ds, i, PHY_EXT_REG_14, val); + an8855_switch_phy_write(ds, i, 0x1f, 0x0); + + /* Enable Asymmetric Pause Capability */ + val = an8855_switch_phy_read(ds, i, MII_ADVERTISE); + val |= ADVERTISE_PAUSE_ASYM; + an8855_switch_phy_write(ds, i, MII_ADVERTISE, val); + } + + if (priv->extSurge) { + for (i = 0; i < AN8855_NUM_PHYS; i++) { + /* Read data */ + for (j = 0; j < AN8855_WORD_SIZE; j++) { + val = an8855_read(priv, AN8855_EFUSE_DATA0 + + (AN8855_WORD_SIZE * (3 + j + (4 * i)))); + + shift_sel = shift_check((val & 0x7f000000) >> 24); + switch (j) { + case 0: + rsel_tx_a = get_shift_val(shift_sel); + break; + case 1: + rsel_tx_b = get_shift_val(shift_sel); + break; + case 2: + rsel_tx_c = get_shift_val(shift_sel); + break; + case 3: + rsel_tx_d = get_shift_val(shift_sel); + break; + default: + continue; + } + } + cl45_data = an8855_phy_cl45_read(priv, i, PHY_DEV1E, 0x174); + cl45_data &= ~(0x7f7f); + cl45_data |= (rsel_tx_a << 8); + cl45_data |= rsel_tx_b; + an8855_phy_cl45_write(priv, i, PHY_DEV1E, 0x174, cl45_data); + cl45_data = an8855_phy_cl45_read(priv, i, PHY_DEV1E, 0x175); + cl45_data &= ~(0x7f7f); + cl45_data |= (rsel_tx_c << 8); + cl45_data |= rsel_tx_d; + an8855_phy_cl45_write(priv, i, PHY_DEV1E, 0x175, cl45_data); + } + } +} + +static void +an8855_eee_setting(struct dsa_switch *ds, u32 port) +{ + struct an8855_priv *priv = ds->priv; + + /* Disable EEE */ + an8855_phy_cl45_write(priv, port, PHY_DEV07, PHY_DEV07_REG_03C, 0); +} + +int +an8855_phy_setup(struct dsa_switch *ds) +{ + int ret = 0; + int i; + + an8855_phy_setting(ds); + + for (i = 0; i < AN8855_NUM_PHYS; i++) + an8855_eee_setting(ds, i); + + return ret; +} diff --git a/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855_phy.h b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855_phy.h new file mode 100644 index 00000000000000..1193e676643b0c --- /dev/null +++ b/target/linux/mediatek/files-6.6/drivers/net/dsa/airoha/an8855/an8855_phy.h @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Common part for Airoha AN8855 gigabit switch + * + * Copyright (C) 2023 Airoha Inc. All Rights Reserved. + * + * Author: Min Yao + */ + +#ifndef _AN8855_PHY_H_ +#define _AN8855_PHY_H_ + +#include + +#define AN8855_NUM_PHYS 5 + +/*phy calibration use*/ +#define DEV_1E 0x1E +/*global device 0x1f, always set P0*/ +#define DEV_1F 0x1F + +/************IEXT/REXT CAL***************/ +/* bits range: for example BITS(16,23) = 0xFF0000*/ +#define BITS(m, n) (~(BIT(m) - 1) & ((BIT(n) - 1) | BIT(n))) +#define ANACAL_INIT 0x01 +#define ANACAL_ERROR 0xFD +#define ANACAL_SATURATION 0xFE +#define ANACAL_FINISH 0xFF +#define ANACAL_PAIR_A 0 +#define ANACAL_PAIR_B 1 +#define ANACAL_PAIR_C 2 +#define ANACAL_PAIR_D 3 +#define DAC_IN_0V 0x00 +#define DAC_IN_2V 0xf0 +#define TX_AMP_OFFSET_0MV 0x20 +#define TX_AMP_OFFSET_VALID_BITS 6 + +#define R0 0 +#define PHY0 0 +#define PHY1 1 +#define PHY2 2 +#define PHY3 3 +#define PHY4 4 +#define ANA_TEST_MODE BITS(8, 15) +#define TST_TCLK_SEL BITs(6, 7) +#define ANA_TEST_VGA_RG 0x100 + +#define FORCE_MDI_CROSS_OVER BITS(3, 4) +#define T10_TEST_CTL_RG 0x145 +#define RG_185 0x185 +#define RG_TX_SLEW BIT(0) +#define ANA_CAL_0 0xdb +#define RG_CAL_CKINV BIT(12) +#define RG_ANA_CALEN BIT(8) +#define RG_REXT_CALEN BIT(4) +#define RG_ZCALEN_A BIT(0) +#define ANA_CAL_1 0xdc +#define RG_ZCALEN_B BIT(12) +#define RG_ZCALEN_C BIT(8) +#define RG_ZCALEN_D BIT(4) +#define RG_TXVOS_CALEN BIT(0) +#define ANA_CAL_6 0xe1 +#define RG_CAL_REFSEL BIT(4) +#define RG_CAL_COMP_PWD BIT(0) +#define ANA_CAL_5 0xe0 +#define RG_REXT_TRIM BITs(8, 13) +#define RG_ZCAL_CTRL BITs(0, 5) +#define RG_17A 0x17a +#define AD_CAL_COMP_OUT BIT(8) +#define RG_17B 0x17b +#define AD_CAL_CLK bit(0) +#define RG_17C 0x17c +#define DA_CALIN_FLAG bit(0) +/************R50 CAL****************************/ +#define RG_174 0x174 +#define RG_R50OHM_RSEL_TX_A_EN BIT[15] +#define CR_R50OHM_RSEL_TX_A BITS[8:14] +#define RG_R50OHM_RSEL_TX_B_EN BIT[7] +#define CR_R50OHM_RSEL_TX_B BITS[6:0] +#define RG_175 0x175 +#define RG_R50OHM_RSEL_TX_C_EN BITS[15] +#define CR_R50OHM_RSEL_TX_C BITS[8:14] +#define RG_R50OHM_RSEL_TX_D_EN BIT[7] +#define CR_R50OHM_RSEL_TX_D BITS[0:6] +/**********TX offset Calibration***************************/ +#define RG_95 0x96 +#define BYPASS_TX_OFFSET_CAL BIT(15) +#define RG_3E 0x3e +#define BYPASS_PD_TXVLD_A BIT(15) +#define BYPASS_PD_TXVLD_B BIT(14) +#define BYPASS_PD_TXVLD_C BIT(13) +#define BYPASS_PD_TXVLD_D BIT(12) +#define BYPASS_PD_TX_10M BIT(11) +#define POWER_DOWN_TXVLD_A BIT(7) +#define POWER_DOWN_TXVLD_B BIT(6) +#define POWER_DOWN_TXVLD_C BIT(5) +#define POWER_DOWN_TXVLD_D BIT(4) +#define POWER_DOWN_TX_10M BIT(3) +#define RG_DD 0xdd +#define RG_TXG_CALEN_A BIT(12) +#define RG_TXG_CALEN_B BIT(8) +#define RG_TXG_CALEN_C BIT(4) +#define RG_TXG_CALEN_D BIT(0) +#define RG_17D 0x17D +#define FORCE_DASN_DAC_IN0_A BIT(15) +#define DASN_DAC_IN0_A BITS(0, 9) +#define RG_17E 0x17E +#define FORCE_DASN_DAC_IN0_B BIT(15) +#define DASN_DAC_IN0_B BITS(0, 9) +#define RG_17F 0x17F + +#define FORCE_DASN_DAC_IN0_C BIT(15) +#define DASN_DAC_IN0_C BITS(0, 9) +#define RG_180 0x180 +#define FORCE_DASN_DAC_IN0_D BIT(15) +#define DASN_DAC_IN0_D BITS(0, 9) + +#define RG_181 0x181 +#define FORCE_DASN_DAC_IN1_A BIT(15) +#define DASN_DAC_IN1_A BITS(0, 9) +#define RG_182 0x182 +#define FORCE_DASN_DAC_IN1_B BIT(15) +#define DASN_DAC_IN1_B BITS(0, 9) +#define RG_183 0x183 +#define FORCE_DASN_DAC_IN1_C BIT(15) +#define DASN_DAC_IN1_C BITS(0, 9) +#define RG_184 0x184 +#define FORCE_DASN_DAC_IN1_D BIT(15) +#define DASN_DAC_IN1_D BITS(0, 9) +#define RG_172 0x172 +#define CR_TX_AMP_OFFSET_A BITS(8, 13) +#define CR_TX_AMP_OFFSET_B BITS(0, 5) +#define RG_173 0x173 +#define CR_TX_AMP_OFFSET_C BITS(8, 13) +#define CR_TX_AMP_OFFSET_D BITS(0, 5) +/**********TX Amp Calibration ***************************/ +#define RG_12 0x12 +#define DA_TX_I2MPB_A_GBE BITS(10, 15) +#define RG_17 0x17 +#define DA_TX_I2MPB_B_GBE BITS(8, 13) +#define RG_19 0x19 +#define DA_TX_I2MPB_C_GBE BITS(8, 13) +#define RG_21 0x21 +#define DA_TX_I2MPB_D_GBE BITS(8, 13) +#define TX_AMP_MAX 0x3f +#define TX_AMP_MAX_OFFSET 0xb +#define TX_AMP_HIGHEST_TS ((TX_AMP_MAX) + 3) +#define TX_AMP_LOWEST_TS (0 - 3) +#define TX_AMP_HIGH_TS (TX_AMP_MAX) +#define TX_AMP_LOW_TS 0 + +/* PHY Extend Register 0x14 bitmap of define */ +#define PHY_EXT_REG_14 0x14 + +/* Fields of PHY_EXT_REG_14 */ +#define PHY_EN_DOWN_SHFIT BIT(4) + +/* PHY Extend Register 0x17 bitmap of define */ +#define PHY_EXT_REG_17 0x17 + +/* Fields of PHY_EXT_REG_17 */ +#define PHY_LINKDOWN_POWER_SAVING_EN BIT(4) + +/* PHY PMA Register 0x17 bitmap of define */ +#define SLV_DSP_READY_TIME_S 15 +#define SLV_DSP_READY_TIME_M (0xff << SLV_DSP_READY_TIME_S) + +/* PHY PMA Register 0x18 bitmap of define */ +#define ENABLE_RANDOM_UPDATE_TRIGGER BIT(8) + +/* PHY EEE Register bitmap of define */ +#define PHY_DEV07 0x07 +#define PHY_DEV07_REG_03C 0x3c +#define ADV_EEE_100 0x2 +#define ADV_EEE_1000 0x4 + +/* PHY DEV 0x1e Register bitmap of define */ +#define PHY_DEV1E 0x1e +/* PHY TX PAIR DELAY SELECT Register */ +#define PHY_TX_PAIR_DLY_SEL_GBE 0x013 +/* PHY ADC Register */ +#define PHY_RXADC_CTRL 0x0d8 +#define PHY_RXADC_REV_0 0x0d9 +#define PHY_RXADC_REV_1 0x0da + +/* PHY LED Register bitmap of define */ +#define PHY_LED_CTRL_SELECT 0x3e8 +#define PHY_SINGLE_LED_ON_CTRL(i) (0x3e0 + ((i) * 2)) +#define PHY_SINGLE_LED_BLK_CTRL(i) (0x3e1 + ((i) * 2)) +#define PHY_SINGLE_LED_ON_DUR(i) (0x3e9 + ((i) * 2)) +#define PHY_SINGLE_LED_BLK_DUR(i) (0x3ea + ((i) * 2)) + +#define PHY_PMA_CTRL (0x340) + +#define PHY_DEV1F 0x1f + +#define PHY_LED_ON_CTRL(i) (0x24 + ((i) * 2)) +#define LED_ON_EN (1 << 15) +#define LED_ON_POL (1 << 14) +#define LED_ON_EVT_MASK (0x7f) + +/* LED ON Event */ +#define LED_ON_EVT_FORCE (1 << 6) +#define LED_ON_EVT_LINK_HD (1 << 5) +#define LED_ON_EVT_LINK_FD (1 << 4) +#define LED_ON_EVT_LINK_DOWN (1 << 3) +#define LED_ON_EVT_LINK_10M (1 << 2) +#define LED_ON_EVT_LINK_100M (1 << 1) +#define LED_ON_EVT_LINK_1000M (1 << 0) + +#define PHY_LED_BLK_CTRL(i) (0x25 + ((i) * 2)) +#define LED_BLK_EVT_MASK (0x3ff) +/* LED Blinking Event */ +#define LED_BLK_EVT_FORCE (1 << 9) +#define LED_BLK_EVT_10M_RX_ACT (1 << 5) +#define LED_BLK_EVT_10M_TX_ACT (1 << 4) +#define LED_BLK_EVT_100M_RX_ACT (1 << 3) +#define LED_BLK_EVT_100M_TX_ACT (1 << 2) +#define LED_BLK_EVT_1000M_RX_ACT (1 << 1) +#define LED_BLK_EVT_1000M_TX_ACT (1 << 0) + +#define PHY_LED_BCR (0x21) +#define LED_BCR_EXT_CTRL (1 << 15) +#define LED_BCR_CLK_EN (1 << 3) +#define LED_BCR_TIME_TEST (1 << 2) +#define LED_BCR_MODE_MASK (3) +#define LED_BCR_MODE_DISABLE (0) + +#define PHY_LED_ON_DUR (0x22) +#define LED_ON_DUR_MASK (0xffff) + +#define PHY_LED_BLK_DUR (0x23) +#define LED_BLK_DUR_MASK (0xffff) + +#define PHY_LED_BLINK_DUR_CTRL (0x720) + +/* Proprietory Control Register of Internal Phy device 0x1e */ +#define PHY_TX_MLT3_BASE 0x0 +#define PHY_DEV1E_REG_13 0x13 +#define PHY_DEV1E_REG_14 0x14 +#define PHY_DEV1E_REG_41 0x41 +#define PHY_DEV1E_REG_A6 0xa6 +#define RXADC_CONTROL_3 0xc2 +#define PHY_DEV1E_REG_0C6 0xc6 +#define RXADC_LDO_CONTROL_2 0xd3 +#define PHY_DEV1E_REG_0FE 0xfe +#define PHY_DEV1E_REG_123 0x123 +#define PHY_DEV1E_REG_189 0x189 +#define PHY_DEV1E_REG_234 0x234 + +/* Proprietory Control Register of Internal Phy device 0x1f */ +#define PHY_DEV1F_REG_44 0x44 +#define PHY_DEV1F_REG_268 0x268 +#define PHY_DEV1F_REG_269 0x269 +#define PHY_DEV1F_REG_26A 0x26A +#define TXVLD_DA_271 0x271 +#define TXVLD_DA_272 0x272 +#define TXVLD_DA_273 0x273 + +/* Fields of PHY_DEV1E_REG_0C6 */ +#define PHY_POWER_SAVING_S 8 +#define PHY_POWER_SAVING_M 0x300 +#define PHY_POWER_SAVING_TX 0x0 + +/* Fields of PHY_DEV1E_REG_189 */ +#define DESCRAMBLER_CLEAR_EN 0x1 + +/* Fields of PHY_DEV1E_REG_234 */ +#define TR_OPEN_LOOP_EN BIT(0) + +/* Internal GPHY Page Control Register */ +#define PHY_CL22_PAGE_CTRL 0x1f +#define PHY_TR_PAGE 0x52b5 + +/* Internal GPHY Token Ring Access Registers */ +#define PHY_TR_CTRL 0x10 +#define PHY_TR_LOW_DATA 0x11 +#define PHY_TR_HIGH_DATA 0x12 + +/* Fields of PHY_TR_CTRL */ +#define PHY_TR_PKT_XMT_STA BIT(15) +#define PHY_TR_WR_S 13 +#define PHY_TR_CH_ADDR_S 11 +#define PHY_TR_NODE_ADDR_S 7 +#define PHY_TR_DATA_ADDR_S 1 + +enum phy_tr_wr { + PHY_TR_WRITE = 0, + PHY_TR_READ = 1, +}; + +/* Helper macro for GPHY Token Ring Access */ +#define PHY_TR_LOW_VAL(x) ((x) & 0xffff) +#define PHY_TR_HIGH_VAL(x) (((x) & 0xff0000) >> 16) + +/* Token Ring Channels */ +#define PMA_CH 0x1 +#define DSP_CH 0x2 + +/* Token Ring Nodes */ +#define PMA_NOD 0xf +#define DSP_NOD 0xd + +/* Token Ring register range */ +enum tr_pma_reg_addr { + PMA_MIN = 0x0, + PMA_01 = 0x1, + PMA_17 = 0x17, + PMA_18 = 0x18, + PMA_MAX = 0x3d, +}; + +enum tr_dsp_reg_addr { + DSP_MIN = 0x0, + DSP_06 = 0x6, + DSP_08 = 0x8, + DSP_0f = 0xf, + DSP_10 = 0x10, + DSP_MAX = 0x3e, +}; +#endif /* _AN8855_REGS_H_ */ diff --git a/target/linux/mediatek/filogic/config-6.6 b/target/linux/mediatek/filogic/config-6.6 index 818bcfa081ea76..394e5fad531997 100644 --- a/target/linux/mediatek/filogic/config-6.6 +++ b/target/linux/mediatek/filogic/config-6.6 @@ -293,9 +293,11 @@ CONFIG_NEED_DMA_MAP_STATE=y CONFIG_NEED_SG_DMA_LENGTH=y CONFIG_NET_DEVLINK=y CONFIG_NET_DSA=y +CONFIG_NET_DSA_AN8855=y CONFIG_NET_DSA_MT7530=y CONFIG_NET_DSA_MT7530_MDIO=y CONFIG_NET_DSA_MT7530_MMIO=y +CONFIG_NET_DSA_TAG_AIROHA=y CONFIG_NET_DSA_TAG_MTK=y CONFIG_NET_EGRESS=y CONFIG_NET_FLOW_LIMIT=y diff --git a/target/linux/mediatek/image/filogic.mk b/target/linux/mediatek/image/filogic.mk index 3e10003e4c2b73..ac1994db8c484e 100644 --- a/target/linux/mediatek/image/filogic.mk +++ b/target/linux/mediatek/image/filogic.mk @@ -1259,7 +1259,7 @@ define Device/xiaomi_mi-router-ax3000t UBINIZE_OPTS := -E 5 BLOCKSIZE := 128k PAGESIZE := 2048 - DEVICE_PACKAGES := kmod-mt7915e kmod-mt7981-firmware mt7981-wo-firmware + DEVICE_PACKAGES := kmod-mt7915e kmod-mt7981-firmware mt7981-wo-firmware kmod-dsa-an8855 ifneq ($(CONFIG_TARGET_ROOTFS_INITRAMFS),) ARTIFACTS := initramfs-factory.ubi ARTIFACT/initramfs-factory.ubi := append-image-stage initramfs-kernel.bin | ubinize-kernel @@ -1276,7 +1276,7 @@ define Device/xiaomi_mi-router-ax3000t-ubootmod UBINIZE_OPTS := -E 5 BLOCKSIZE := 128k PAGESIZE := 2048 - DEVICE_PACKAGES := kmod-mt7915e kmod-mt7981-firmware mt7981-wo-firmware + DEVICE_PACKAGES := kmod-mt7915e kmod-mt7981-firmware mt7981-wo-firmware kmod-dsa-an8855 KERNEL_IN_UBI := 1 UBOOTENV_IN_UBI := 1 IMAGES := sysupgrade.itb diff --git a/target/linux/mediatek/mt7622/config-6.6 b/target/linux/mediatek/mt7622/config-6.6 index cf445b2b7195aa..2b3b5de60d2789 100644 --- a/target/linux/mediatek/mt7622/config-6.6 +++ b/target/linux/mediatek/mt7622/config-6.6 @@ -295,9 +295,11 @@ CONFIG_NEED_DMA_MAP_STATE=y CONFIG_NEED_SG_DMA_LENGTH=y CONFIG_NET_DEVLINK=y CONFIG_NET_DSA=y +# CONFIG_NET_DSA_AN8855 is not set CONFIG_NET_DSA_MT7530=y CONFIG_NET_DSA_MT7530_MDIO=y # CONFIG_NET_DSA_MT7530_MMIO is not set +# CONFIG_NET_DSA_TAG_AIROHA is not set CONFIG_NET_DSA_TAG_MTK=y CONFIG_NET_EGRESS=y CONFIG_NET_FLOW_LIMIT=y diff --git a/target/linux/mediatek/mt7623/config-6.6 b/target/linux/mediatek/mt7623/config-6.6 index bb00bee8f31407..07d652bd4c7693 100644 --- a/target/linux/mediatek/mt7623/config-6.6 +++ b/target/linux/mediatek/mt7623/config-6.6 @@ -414,9 +414,11 @@ CONFIG_NEED_SRCU_NMI_SAFE=y CONFIG_NEON=y CONFIG_NET_DEVLINK=y CONFIG_NET_DSA=y +# CONFIG_NET_DSA_AN8855 is not set CONFIG_NET_DSA_MT7530=y CONFIG_NET_DSA_MT7530_MDIO=y # CONFIG_NET_DSA_MT7530_MMIO is not set +# CONFIG_NET_DSA_TAG_AIROHA is not set CONFIG_NET_DSA_TAG_MTK=y CONFIG_NET_EGRESS=y CONFIG_NET_FLOW_LIMIT=y diff --git a/target/linux/mediatek/mt7629/config-6.6 b/target/linux/mediatek/mt7629/config-6.6 index 1748efab775fc6..6e53387072d662 100644 --- a/target/linux/mediatek/mt7629/config-6.6 +++ b/target/linux/mediatek/mt7629/config-6.6 @@ -220,9 +220,11 @@ CONFIG_NETFILTER=y CONFIG_NETFILTER_BPF_LINK=y CONFIG_NET_DEVLINK=y CONFIG_NET_DSA=y +# CONFIG_NET_DSA_AN8855 is not set CONFIG_NET_DSA_MT7530=y CONFIG_NET_DSA_MT7530_MDIO=y # CONFIG_NET_DSA_MT7530_MMIO is not set +# CONFIG_NET_DSA_TAG_AIROHA is not set CONFIG_NET_DSA_TAG_MTK=y CONFIG_NET_EGRESS=y CONFIG_NET_FLOW_LIMIT=y diff --git a/target/linux/mediatek/patches-6.6/735-net-dsa-add-Airoha-AN8855.patch b/target/linux/mediatek/patches-6.6/735-net-dsa-add-Airoha-AN8855.patch new file mode 100644 index 00000000000000..e5a1f89d64d637 --- /dev/null +++ b/target/linux/mediatek/patches-6.6/735-net-dsa-add-Airoha-AN8855.patch @@ -0,0 +1,219 @@ +From 13b2da6a267d2d6435f80ec13679777ff30cb1c7 Mon Sep 17 00:00:00 2001 +From: dimfish +Date: Fri, 11 Oct 2022 19:10:04 +0300 +Subject: 735-net-dsa-add-Airoha-AN8855.patch + +--- + include/net/dsa.h | 2 + + net/dsa/Makefile | 2 +- + net/dsa/tag_arht.c | 128 +++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 131 insertions(+), 1 deletion(-) + create mode 100644 net/dsa/tag_arht.c + +--- a/include/net/dsa.h ++++ b/include/net/dsa.h +@@ -56,6 +56,7 @@ + #define DSA_TAG_PROTO_RTL8_4T_VALUE 25 + #define DSA_TAG_PROTO_RZN1_A5PSW_VALUE 26 + #define DSA_TAG_PROTO_LAN937X_VALUE 27 ++#define DSA_TAG_PROTO_ARHT_VALUE 28 + + enum dsa_tag_protocol { + DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE, +@@ -86,6 +87,7 @@ + DSA_TAG_PROTO_RTL8_4T = DSA_TAG_PROTO_RTL8_4T_VALUE, + DSA_TAG_PROTO_RZN1_A5PSW = DSA_TAG_PROTO_RZN1_A5PSW_VALUE, + DSA_TAG_PROTO_LAN937X = DSA_TAG_PROTO_LAN937X_VALUE, ++ DSA_TAG_PROTO_ARHT = DSA_TAG_PROTO_ARHT_VALUE, + }; + + struct dsa_switch; +--- a/drivers/net/dsa/Kconfig ++++ b/drivers/net/dsa/Kconfig +@@ -2,6 +2,8 @@ + menu "Distributed Switch Architecture drivers" + depends on NET_DSA + ++source "drivers/net/dsa/airoha/an8855/Kconfig" ++ + source "drivers/net/dsa/b53/Kconfig" + + config NET_DSA_BCM_SF2 +--- a/drivers/net/dsa/Makefile ++++ b/drivers/net/dsa/Makefile +@@ -17,6 +17,7 @@ + obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o + obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o + obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o ++obj-y += airoha/an8855/ + obj-y += b53/ + obj-y += hirschmann/ + obj-y += microchip/ +--- a/net/dsa/Kconfig ++++ b/net/dsa/Kconfig +@@ -24,6 +24,12 @@ + Say Y or M if you want to enable support for switches which don't tag + frames over the CPU port. + ++config NET_DSA_TAG_AIROHA ++ tristate "Tag driver for Airoha switches" ++ help ++ Say Y or M if you want to enable support for tagging frames for ++ Airoha switches. ++ + config NET_DSA_TAG_AR9331 + tristate "Tag driver for Atheros AR9331 SoC with built-in switch" + help +--- a/net/dsa/Makefile ++++ b/net/dsa/Makefile +@@ -20,6 +20,7 @@ + trace.o + + # tagging formats ++obj-$(CONFIG_NET_DSA_TAG_AIROHA) += tag_arht.o + obj-$(CONFIG_NET_DSA_TAG_AR9331) += tag_ar9331.o + obj-$(CONFIG_NET_DSA_TAG_BRCM_COMMON) += tag_brcm.o + obj-$(CONFIG_NET_DSA_TAG_DSA_COMMON) += tag_dsa.o +--- /dev/null ++++ b/net/dsa/tag_arht.c +@@ -0,0 +1,140 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Airoha DSA Tag support ++ * Copyright (C) 2023 Min Yao ++ */ ++ ++#include ++#include ++#include ++#if (KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE) ++#include "slave.h" ++#include "tag.h" ++#else ++#include "dsa_priv.h" ++#endif ++ ++#define AIR_NAME "air" ++ ++#define AIR_HDR_LEN 4 ++#define AIR_HDR_XMIT_UNTAGGED 0 ++#define AIR_HDR_XMIT_TAGGED_TPID_8100 1 ++#define AIR_HDR_XMIT_TAGGED_TPID_88A8 2 ++#define AIR_HDR_RECV_SOURCE_PORT_MASK GENMASK(2, 0) ++#define AIR_HDR_XMIT_DP_BIT_MASK GENMASK(5, 0) ++ ++static struct sk_buff *air_tag_xmit(struct sk_buff *skb, ++ struct net_device *dev) ++{ ++ struct dsa_port *dp = dsa_slave_to_port(dev); ++ u8 xmit_tpid; ++ u8 *air_tag; ++ ++ /* Build the special tag after the MAC Source Address. If VLAN header ++ * is present, it's required that VLAN header and special tag is ++ * being combined. Only in this way we can allow the switch can parse ++ * the both special and VLAN tag at the same time and then look up VLAN ++ * table with VID. ++ */ ++ switch (skb->protocol) { ++ case htons(ETH_P_8021Q): ++ xmit_tpid = AIR_HDR_XMIT_TAGGED_TPID_8100; ++ break; ++ case htons(ETH_P_8021AD): ++ xmit_tpid = AIR_HDR_XMIT_TAGGED_TPID_88A8; ++ break; ++ default: ++ if (skb_cow_head(skb, AIR_HDR_LEN) < 0) ++ return NULL; ++ ++ xmit_tpid = AIR_HDR_XMIT_UNTAGGED; ++ skb_push(skb, AIR_HDR_LEN); ++ memmove(skb->data, skb->data + AIR_HDR_LEN, 2 * ETH_ALEN); ++ } ++ ++ air_tag = skb->data + 2 * ETH_ALEN; ++ ++ /* Mark tag attribute on special tag insertion to notify hardware ++ * whether that's a combined special tag with 802.1Q header. ++ */ ++ air_tag[0] = xmit_tpid; ++ air_tag[1] = (1 << dp->index) & AIR_HDR_XMIT_DP_BIT_MASK; ++ ++ /* Tag control information is kept for 802.1Q */ ++ if (xmit_tpid == AIR_HDR_XMIT_UNTAGGED) { ++ air_tag[2] = 0; ++ air_tag[3] = 0; ++ } ++ ++ return skb; ++} ++ ++static struct sk_buff *air_tag_rcv(struct sk_buff *skb, struct net_device *dev) ++{ ++ int port; ++ __be16 *phdr, hdr; ++ unsigned char *dest = eth_hdr(skb)->h_dest; ++ bool is_multicast_skb = is_multicast_ether_addr(dest) && ++ !is_broadcast_ether_addr(dest); ++ ++ if (dev->features & NETIF_F_HW_VLAN_CTAG_RX) { ++ hdr = ntohs(skb->vlan_proto); ++ skb->vlan_proto = 0; ++ skb->vlan_tci = 0; ++ } else { ++ if (unlikely(!pskb_may_pull(skb, AIR_HDR_LEN))) ++ return NULL; ++ ++ /* The AIR header is added by the switch between src addr ++ * and ethertype at this point, skb->data points to 2 bytes ++ * after src addr so header should be 2 bytes right before. ++ */ ++ phdr = (__be16 *)(skb->data - 2); ++ hdr = ntohs(*phdr); ++ ++ /* Remove AIR tag and recalculate checksum. */ ++ skb_pull_rcsum(skb, AIR_HDR_LEN); ++ ++ memmove(skb->data - ETH_HLEN, ++ skb->data - ETH_HLEN - AIR_HDR_LEN, ++ 2 * ETH_ALEN); ++ } ++ ++ /* Get source port information */ ++ port = (hdr & AIR_HDR_RECV_SOURCE_PORT_MASK); ++ ++ skb->dev = dsa_master_find_slave(dev, 0, port); ++ if (!skb->dev) ++ return NULL; ++ ++ /* Only unicast or broadcast frames are offloaded */ ++ if (likely(!is_multicast_skb)) ++ skb->offload_fwd_mark = 1; ++ ++ return skb; ++} ++ ++static void air_tag_flow_dissect(const struct sk_buff *skb, __be16 *proto, ++ int *offset) ++{ ++ *offset = 4; ++ *proto = ((__be16 *)skb->data)[1]; ++} ++ ++static const struct dsa_device_ops air_netdev_ops = { ++ .name = AIR_NAME, ++ .proto = DSA_TAG_PROTO_ARHT, ++ .xmit = air_tag_xmit, ++ .rcv = air_tag_rcv, ++ .flow_dissect = air_tag_flow_dissect, ++ .needed_headroom = AIR_HDR_LEN, ++}; ++ ++MODULE_LICENSE("GPL"); ++#if (KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE) ++MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_AIR, AIR_NAME); ++#else ++MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_AIR); ++#endif ++ ++module_dsa_tag_driver(air_netdev_ops);