diff --git a/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/Kconfig b/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/Kconfig new file mode 100644 index 0000000000..fc00f75d17 --- /dev/null +++ b/target/linux/mediatek/files-5.4/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-5.4/drivers/net/dsa/airoha/an8855/Makefile b/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/Makefile new file mode 100644 index 0000000000..99c99bd6c4 --- /dev/null +++ b/target/linux/mediatek/files-5.4/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-5.4/drivers/net/dsa/airoha/an8855/an8855.c b/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855.c new file mode 100644 index 0000000000..78cfbdc60f --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855.c @@ -0,0 +1,2785 @@ +// 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; + +/* 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 net_device *bridge) +{ + 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_to_port(ds, i)->bridge_dev != 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(&ds->ports[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 net_device *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_to_port(ds, i)->bridge_dev != 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 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 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) +{ + 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 int +an8855_port_vlan_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + /* nothing needed */ + 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 void +an8855_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + 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; + u16 vid; + + mutex_lock(&priv->reg_mutex); + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { + an8855_hw_vlan_entry_init(&new_entry, port, untagged); + an8855_hw_vlan_update(priv, 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_end)); + priv->ports[port].pvid = vlan->vid_end; + } + + mutex_unlock(&priv->reg_mutex); +} + +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 vid, pvid; + + mutex_lock(&priv->reg_mutex); + + pvid = priv->ports[port].pvid; + for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { + an8855_hw_vlan_entry_init(&target_entry, port, 0); + an8855_hw_vlan_update(priv, 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 == 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 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 bool +an8855_phy_mode_supported(struct dsa_switch *ds, int port, + const struct phylink_link_state *state) +{ + struct an8855_priv *priv = ds->priv; + + return priv->info->phy_mode_supported(ds, port, state); +} + +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 void +an8855_sgmii_link_up_force(struct dsa_switch *ds, int port, + unsigned int mode, phy_interface_t interface, + int speed, int duplex) +{ + /* For adjusting speed and duplex of SGMII force mode. */ + if (interface != PHY_INTERFACE_MODE_SGMII || + phylink_autoneg_inband(mode)) + return; +} + +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_force(struct an8855_priv *priv, u32 port, + phy_interface_t interface) +{ + return an8855_sgmii_setup(priv, SGMII_MODE_FORCE); +} + +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 int +an8855_sw_mac_config(struct dsa_switch *ds, int port, unsigned int mode, + const struct phylink_link_state *state) +{ + struct an8855_priv *priv = ds->priv; + + return priv->info->mac_port_config(ds, port, mode, state->interface); +} + +static void +an8855_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode, + const struct phylink_link_state *state) +{ + struct an8855_priv *priv = ds->priv; + u32 mcr_cur, mcr_new; + + if (!an8855_phy_mode_supported(ds, port, state)) + goto unsupported; + + 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 (priv->p5_interface == state->interface) + break; + + if (an8855_sw_mac_config(ds, port, mode, state) < 0) + goto unsupported; + + priv->p5_interface = state->interface; + break; + default: +unsupported: + dev_err(ds->dev, "%s: unsupported %s port: %i\n", + __func__, phy_modes(state->interface), port); + return; + } + + if (phylink_autoneg_inband(mode) && + state->interface != PHY_INTERFACE_MODE_SGMII) { + dev_err(ds->dev, "%s: in-band negotiation unsupported\n", + __func__); + return; + } + + mcr_cur = an8855_read(priv, AN8855_PMCR_P(port)); + mcr_new = mcr_cur; + mcr_new &= ~PMCR_LINK_SETTINGS_MASK; + mcr_new |= PMCR_IFG_XMIT(1) | PMCR_MAC_MODE | PMCR_BACKOFF_EN | + PMCR_BACKPR_EN | AN8855_FORCE_MODE; + + if (mcr_new != mcr_cur) + an8855_write(priv, AN8855_PMCR_P(port), mcr_new); +} + +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_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 void +an8855_phylink_validate(struct dsa_switch *ds, int port, + unsigned long *supported, + struct phylink_link_state *state) +{ + struct an8855_priv *priv = ds->priv; + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = {0,}; + + if (state->interface != PHY_INTERFACE_MODE_NA && + !an8855_phy_mode_supported(ds, port, state)) { + linkmode_zero(supported); + return; + } + + phylink_set_port_modes(mask); + + if (state->interface != PHY_INTERFACE_MODE_TRGMII + || state->interface != PHY_INTERFACE_MODE_USXGMII + || state->interface != PHY_INTERFACE_MODE_10GKR + || !phy_interface_mode_is_8023z(state->interface)) { + phylink_set(mask, 10baseT_Half); + phylink_set(mask, 10baseT_Full); + phylink_set(mask, 100baseT_Half); + phylink_set(mask, 100baseT_Full); + phylink_set(mask, Autoneg); + } + + /* This switch only supports 1G full-duplex. */ + if (state->interface != PHY_INTERFACE_MODE_MII) + phylink_set(mask, 1000baseT_Full); + + priv->info->mac_port_validate(ds, port, mask); + + phylink_set(mask, Pause); + phylink_set(mask, Asym_Pause); + + linkmode_and(supported, supported, mask); + linkmode_and(state->advertising, state->advertising, mask); + + /* We can only operate at 2500BaseX or 1000BaseX. If requested + * to advertise both, only report advertising at 2500BaseX. + */ + phylink_helper_basex_speed(state); +} + +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 int +an8855_phylink_mac_link_state(struct dsa_switch *ds, int port, + struct phylink_link_state *state) +{ + struct an8855_priv *priv = ds->priv; + u32 pmsr; + + if (port < 0 || port >= AN8855_NUM_PORTS) + return -EINVAL; + + 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; + + return 1; +} + +static int +an8855_sw_phylink_mac_link_state(struct dsa_switch *ds, int port, + struct phylink_link_state *state) +{ + struct an8855_priv *priv = ds->priv; + + return priv->info->mac_port_get_state(ds, port, state); +} + +static int +an8855_sw_setup(struct dsa_switch *ds) +{ + struct an8855_priv *priv = ds->priv; + + return priv->info->sw_setup(ds); +} + +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); +} + +#if (KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE) +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, +}; +#else +static const struct file_operations an8855_proc_device_fops = { + .owner = THIS_MODULE, + .open = an8855_proc_device_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +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_prepare = an8855_port_vlan_prepare, + .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_validate = an8855_phylink_validate, + .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, + .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_get_state = an8855_phylink_mac_link_state, + .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 = dsa_switch_alloc(&mdiodev->dev, AN8855_NUM_PORTS); + if (!priv->ds) + return -ENOMEM; + + /* 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_get_state || !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 struct mdio_driver an8855_mdio_driver = { + .probe = an8855_probe, + .remove = an8855_remove, + .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-5.4/drivers/net/dsa/airoha/an8855/an8855.h b/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855.h new file mode 100644 index 0000000000..6c472c27cc --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855.h @@ -0,0 +1,679 @@ +/* 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; + + 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_get_state)(struct dsa_switch *ds, int port, + struct phylink_link_state *state); + int (*mac_port_config)(struct dsa_switch *ds, int port, + unsigned int mode, phy_interface_t interface); + void (*mac_pcs_an_restart)(struct dsa_switch *ds, 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]; + /* 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-5.4/drivers/net/dsa/airoha/an8855/an8855_nl.c b/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_nl.c new file mode 100644 index 0000000000..45099d8866 --- /dev/null +++ b/target/linux/mediatek/files-5.4/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-5.4/drivers/net/dsa/airoha/an8855/an8855_nl.h b/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_nl.h new file mode 100644 index 0000000000..f8a462d5dc --- /dev/null +++ b/target/linux/mediatek/files-5.4/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-5.4/drivers/net/dsa/airoha/an8855/an8855_phy.c b/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_phy.c new file mode 100644 index 0000000000..9df08344a9 --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_phy.c @@ -0,0 +1,189 @@ +// 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_low_power_setting(struct dsa_switch *ds) +{ + int port, addr; + struct an8855_priv *priv = ds->priv; + + for (port = 0; port < AN8855_NUM_PHYS; port++) { + an8855_phy_cl45_write(priv, port, 0x1e, 0x11, 0x0f00); + an8855_phy_cl45_write(priv, port, 0x1e, 0x3c, 0x0000); + an8855_phy_cl45_write(priv, port, 0x1e, 0x3d, 0x0000); + an8855_phy_cl45_write(priv, port, 0x1e, 0x3e, 0x0000); + an8855_phy_cl45_write(priv, port, 0x1e, 0xc6, 0x53aa); + } + + an8855_phy_cl45_write(priv, 0, 0x1f, 0x268, 0x07f1); + an8855_phy_cl45_write(priv, 0, 0x1f, 0x269, 0x2111); + an8855_phy_cl45_write(priv, 0, 0x1f, 0x26a, 0x0000); + an8855_phy_cl45_write(priv, 0, 0x1f, 0x26b, 0x0074); + an8855_phy_cl45_write(priv, 0, 0x1f, 0x26e, 0x00f6); + an8855_phy_cl45_write(priv, 0, 0x1f, 0x26f, 0x6666); + an8855_phy_cl45_write(priv, 0, 0x1f, 0x271, 0x2c02); + an8855_phy_cl45_write(priv, 0, 0x1f, 0x272, 0x0c22); + an8855_phy_cl45_write(priv, 0, 0x1f, 0x700, 0x0001); + an8855_phy_cl45_write(priv, 0, 0x1f, 0x701, 0x0803); + an8855_phy_cl45_write(priv, 0, 0x1f, 0x702, 0x01b6); + an8855_phy_cl45_write(priv, 0, 0x1f, 0x703, 0x2111); + + an8855_phy_cl45_write(priv, 1, 0x1f, 0x700, 0x0001); + + for (addr = 0x200; addr <= 0x230; addr += 2) + an8855_phy_cl45_write(priv, 0, 0x1f, addr, 0x2020); + + for (addr = 0x201; addr <= 0x231; addr += 2) + an8855_phy_cl45_write(priv, 0, 0x1f, addr, 0x0020); +} + +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-5.4/drivers/net/dsa/airoha/an8855/an8855_phy.h b/target/linux/mediatek/files-5.4/drivers/net/dsa/airoha/an8855/an8855_phy.h new file mode 100644 index 0000000000..1193e67664 --- /dev/null +++ b/target/linux/mediatek/files-5.4/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/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/Makefile b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/Makefile index bf1bbcbc20..8c6ac854c8 100644 --- a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/Makefile +++ b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/Makefile @@ -2,4 +2,8 @@ ccflags-y=-Werror obj-$(CONFIG_NET_MEDIATEK_HNAT) += mtkhnat.o mtkhnat-objs := hnat.o hnat_nf_hook.o hnat_debugfs.o hnat_mcast.o +ifeq ($(CONFIG_NET_DSA_AN8855), y) +mtkhnat-y += hnat_stag.o +else mtkhnat-$(CONFIG_NET_DSA_MT7530) += hnat_stag.o +endif diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/Kconfig b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/Kconfig new file mode 100644 index 0000000000..654aa37a36 --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/Kconfig @@ -0,0 +1,8 @@ + +config AN8855_GSW + tristate "Driver for the Airoha AN8855 switch" + help + Airoha AN8855 devices and swconfig driver code, + Provide swconfig command for basic switch operation. + AN8855 support 2.5G speed and managed by SMI interface. + To compile this driver as a module, choose M here. diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/Makefile b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/Makefile new file mode 100644 index 0000000000..5c24bdac81 --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for Airoha AN8855 gigabit switch +# + +obj-$(CONFIG_AN8855_GSW) += an8855.o + +an8855-$(CONFIG_SWCONFIG) += an8855_swconfig.o + +an8855-y += an8855_mdio.o an8855.o \ + an8855_common.o an8855_vlan.o an8855_nl.o diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855.c b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855.c new file mode 100644 index 0000000000..79d6d1a43e --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855.c @@ -0,0 +1,1381 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023 Airoha Inc. + * Author: Min Yao + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "an8855.h" +#include "an8855_regs.h" + +/* AN8855 registers */ +#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_P5MUX_MODE (ETHER_SYS_BASE + 0x00) +#define RG_FORCE_CKDIR_SEL (ETHER_SYS_BASE + 0x04) +#define RG_SWITCH_MODE (ETHER_SYS_BASE + 0x08) +#define RG_FORCE_MAC5_SB (ETHER_SYS_BASE + 0x2c) +#define RG_GPHY_AFE_PWD (ETHER_SYS_BASE + 0x40) +#define RG_GPHY_SMI_ADDR (ETHER_SYS_BASE + 0x48) +#define CSR_RMII (ETHER_SYS_BASE + 0x70) + +/* PHY EEE Register bitmap of define */ +#define PHY_DEV07 0x07 +#define PHY_DEV07_REG_03C 0x3c + +/* 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 dev address 0x1E */ +#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) + +/* PHY dev address 0x1F */ +#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) + +/* Unique fields of PMCR for AN8855 */ +#define FORCE_TX_FC BIT(4) +#define FORCE_RX_FC BIT(5) +#define FORCE_DPX BIT(25) +#define FORCE_SPD BITS(28, 30) +#define FORCE_LNK BIT(24) +#define FORCE_MODE BIT(31) + +#define CHIP_ID 0x10005000 +#define CHIP_REV 0x10005004 + +#define AN8855_EFUSE_DATA0 0x1000a500 + +const u8 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(r50ohm_table)/sizeof(u8); + + for (i = 0; i < sz; ++i) + if (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 r50ohm_table[idx]; +} + +/* 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 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO1 */ + {1, P0_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO2 */ + {1, P1_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO3 */ + {1, P2_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO4 */ + {1, P3_LED1, LED_LOW, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO5 */ + {1, P4_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 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO10 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO11 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO12 */ + {0, PHY_LED_MAX, LED_LOW, 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}, +}; + +static int an8855_set_hsgmii_mode(struct gsw_an8855 *gsw) +{ + u32 val = 0; + + /* TX FIR - improve TX EYE */ + val = an8855_reg_read(gsw, INTF_CTRL_10); + val &= ~(0x3f << 16); + val |= BIT(21); + val &= ~(0x1f << 24); + val |= (0x4 << 24); + val |= BIT(29); + an8855_reg_write(gsw, INTF_CTRL_10, val); + + val = an8855_reg_read(gsw, INTF_CTRL_11); + val &= ~(0x3f); + val |= BIT(6); + an8855_reg_write(gsw, INTF_CTRL_11, val); + + /* RX CDR - improve RX Jitter Tolerance */ + val = an8855_reg_read(gsw, RG_QP_CDR_LPF_BOT_LIM); + val &= ~(0x7 << 24); + val |= (0x5 << 24); + val &= ~(0x7 << 20); + val |= (0x5 << 20); + an8855_reg_write(gsw, RG_QP_CDR_LPF_BOT_LIM, val); + + /* PLL */ + val = an8855_reg_read(gsw, QP_DIG_MODE_CTRL_1); + val &= ~(0x3 << 2); + val |= (0x1 << 2); + an8855_reg_write(gsw, QP_DIG_MODE_CTRL_1, val); + + /* PLL - LPF */ + val = an8855_reg_read(gsw, 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_reg_write(gsw, PLL_CTRL_2, val); + + /* PLL - ICO */ + val = an8855_reg_read(gsw, PLL_CTRL_4); + val |= BIT(2); + an8855_reg_write(gsw, PLL_CTRL_4, val); + + val = an8855_reg_read(gsw, PLL_CTRL_2); + val &= ~BIT(14); + an8855_reg_write(gsw, PLL_CTRL_2, val); + + /* PLL - CHP */ + val = an8855_reg_read(gsw, PLL_CTRL_2); + val &= ~(0xf << 16); + val |= (0x6 << 16); + an8855_reg_write(gsw, PLL_CTRL_2, val); + + /* PLL - PFD */ + val = an8855_reg_read(gsw, PLL_CTRL_2); + val &= ~(0x3 << 20); + val |= (0x1 << 20); + val &= ~(0x3 << 24); + val |= (0x1 << 24); + val &= ~BIT(26); + an8855_reg_write(gsw, PLL_CTRL_2, val); + + /* PLL - POSTDIV */ + val = an8855_reg_read(gsw, PLL_CTRL_2); + val |= BIT(22); + val &= ~BIT(27); + val &= ~BIT(28); + an8855_reg_write(gsw, PLL_CTRL_2, val); + + /* PLL - SDM */ + val = an8855_reg_read(gsw, PLL_CTRL_4); + val &= ~BITS(3, 4); + an8855_reg_write(gsw, PLL_CTRL_4, val); + + val = an8855_reg_read(gsw, PLL_CTRL_2); + val &= ~BIT(30); + an8855_reg_write(gsw, PLL_CTRL_2, val); + + val = an8855_reg_read(gsw, SS_LCPLL_PWCTL_SETTING_2); + val &= ~(0x3 << 16); + val |= (0x1 << 16); + an8855_reg_write(gsw, SS_LCPLL_PWCTL_SETTING_2, val); + + an8855_reg_write(gsw, SS_LCPLL_TDC_FLT_2, 0x7a000000); + an8855_reg_write(gsw, SS_LCPLL_TDC_PCW_1, 0x7a000000); + + val = an8855_reg_read(gsw, SS_LCPLL_TDC_FLT_5); + val &= ~BIT(24); + an8855_reg_write(gsw, SS_LCPLL_TDC_FLT_5, val); + + val = an8855_reg_read(gsw, PLL_CK_CTRL_0); + val &= ~BIT(8); + an8855_reg_write(gsw, PLL_CK_CTRL_0, val); + + /* PLL - SS */ + val = an8855_reg_read(gsw, PLL_CTRL_3); + val &= ~BITS(0, 15); + an8855_reg_write(gsw, PLL_CTRL_3, val); + + val = an8855_reg_read(gsw, PLL_CTRL_4); + val &= ~BITS(0, 1); + an8855_reg_write(gsw, PLL_CTRL_4, val); + + val = an8855_reg_read(gsw, PLL_CTRL_3); + val &= ~BITS(16, 31); + an8855_reg_write(gsw, PLL_CTRL_3, val); + + /* PLL - TDC */ + val = an8855_reg_read(gsw, PLL_CK_CTRL_0); + val &= ~BIT(9); + an8855_reg_write(gsw, PLL_CK_CTRL_0, val); + + val = an8855_reg_read(gsw, RG_QP_PLL_SDM_ORD); + val |= BIT(3); + val |= BIT(4); + an8855_reg_write(gsw, RG_QP_PLL_SDM_ORD, val); + + val = an8855_reg_read(gsw, RG_QP_RX_DAC_EN); + val &= ~(0x3 << 16); + val |= (0x2 << 16); + an8855_reg_write(gsw, RG_QP_RX_DAC_EN, val); + + /* TCL Disable (only for Co-SIM) */ + val = an8855_reg_read(gsw, PON_RXFEDIG_CTRL_0); + val &= ~BIT(12); + an8855_reg_write(gsw, PON_RXFEDIG_CTRL_0, val); + + /* TX Init */ + val = an8855_reg_read(gsw, RG_QP_TX_MODE_16B_EN); + val &= ~BIT(0); + val &= ~(0xffff << 16); + val |= (0x4 << 16); + an8855_reg_write(gsw, RG_QP_TX_MODE_16B_EN, val); + + /* RX Control */ + val = an8855_reg_read(gsw, RG_QP_RXAFE_RESERVE); + val |= BIT(11); + an8855_reg_write(gsw, RG_QP_RXAFE_RESERVE, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_LPF_MJV_LIM); + val &= ~(0x3 << 4); + val |= (0x1 << 4); + an8855_reg_write(gsw, RG_QP_CDR_LPF_MJV_LIM, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_LPF_SETVALUE); + val &= ~(0xf << 25); + val |= (0x1 << 25); + val &= ~(0x7 << 29); + val |= (0x6 << 29); + an8855_reg_write(gsw, RG_QP_CDR_LPF_SETVALUE, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_PR_CKREF_DIV1); + val &= ~(0x1f << 8); + val |= (0xf << 8); + an8855_reg_write(gsw, RG_QP_CDR_PR_CKREF_DIV1, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE); + val &= ~(0x3f << 0); + val |= (0x19 << 0); + val &= ~BIT(6); + an8855_reg_write(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_FORCE_IBANDLPF_R_OFF); + val &= ~(0x7f << 6); + val |= (0x21 << 6); + val &= ~(0x3 << 16); + val |= (0x2 << 16); + val &= ~BIT(13); + an8855_reg_write(gsw, RG_QP_CDR_FORCE_IBANDLPF_R_OFF, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE); + val &= ~BIT(30); + an8855_reg_write(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_PR_CKREF_DIV1); + val &= ~(0x7 << 24); + val |= (0x4 << 24); + an8855_reg_write(gsw, RG_QP_CDR_PR_CKREF_DIV1, val); + + /* PMA (For HW Mode) */ + val = an8855_reg_read(gsw, RX_CTRL_26); + val |= BIT(23); + val &= ~BIT(24); + val |= BIT(26); + an8855_reg_write(gsw, RX_CTRL_26, val); + + val = an8855_reg_read(gsw, RX_DLY_0); + val &= ~(0xff << 0); + val |= (0x6f << 0); + val |= BITS(8, 13); + an8855_reg_write(gsw, RX_DLY_0, val); + + val = an8855_reg_read(gsw, RX_CTRL_42); + val &= ~(0x1fff << 0); + val |= (0x150 << 0); + an8855_reg_write(gsw, RX_CTRL_42, val); + + val = an8855_reg_read(gsw, RX_CTRL_2); + val &= ~(0x1fff << 16); + val |= (0x150 << 16); + an8855_reg_write(gsw, RX_CTRL_2, val); + + val = an8855_reg_read(gsw, PON_RXFEDIG_CTRL_9); + val &= ~(0x7 << 0); + val |= (0x1 << 0); + an8855_reg_write(gsw, PON_RXFEDIG_CTRL_9, val); + + val = an8855_reg_read(gsw, RX_CTRL_8); + val &= ~(0xfff << 16); + val |= (0x200 << 16); + val &= ~(0x7fff << 0); + val |= (0xfff << 0); + an8855_reg_write(gsw, RX_CTRL_8, val); + + /* Frequency memter */ + val = an8855_reg_read(gsw, RX_CTRL_5); + val &= ~(0xfffff << 10); + val |= (0x10 << 10); + an8855_reg_write(gsw, RX_CTRL_5, val); + + val = an8855_reg_read(gsw, RX_CTRL_6); + val &= ~(0xfffff << 0); + val |= (0x64 << 0); + an8855_reg_write(gsw, RX_CTRL_6, val); + + val = an8855_reg_read(gsw, RX_CTRL_7); + val &= ~(0xfffff << 0); + val |= (0x2710 << 0); + an8855_reg_write(gsw, RX_CTRL_7, val); + + val = an8855_reg_read(gsw, PLL_CTRL_0); + val |= BIT(0); + an8855_reg_write(gsw, PLL_CTRL_0, val); + + /* PCS Init */ + val = an8855_reg_read(gsw, RG_HSGMII_PCS_CTROL_1); + val &= ~BIT(30); + an8855_reg_write(gsw, RG_HSGMII_PCS_CTROL_1, val); + + /* Rate Adaption */ + val = an8855_reg_read(gsw, RATE_ADP_P0_CTRL_0); + val &= ~BIT(31); + an8855_reg_write(gsw, RATE_ADP_P0_CTRL_0, val); + + val = an8855_reg_read(gsw, RG_RATE_ADAPT_CTRL_0); + val |= BIT(0); + val |= BIT(4); + val |= BITS(26, 27); + an8855_reg_write(gsw, RG_RATE_ADAPT_CTRL_0, val); + + /* Disable AN */ + val = an8855_reg_read(gsw, SGMII_REG_AN0); + val &= ~BIT(12); + an8855_reg_write(gsw, SGMII_REG_AN0, val); + + /* Force Speed */ + val = an8855_reg_read(gsw, SGMII_STS_CTRL_0); + val |= BIT(2); + val |= BITS(4, 5); + an8855_reg_write(gsw, SGMII_STS_CTRL_0, val); + + /* bypass flow control to MAC */ + an8855_reg_write(gsw, MSG_RX_LIK_STS_0, 0x01010107); + an8855_reg_write(gsw, MSG_RX_LIK_STS_2, 0x00000EEF); + + return 0; +} + +static int an8855_sgmii_setup(struct gsw_an8855 *gsw, int mode) +{ + u32 val = 0; + + /* TX FIR - improve TX EYE */ + val = an8855_reg_read(gsw, INTF_CTRL_10); + val &= ~(0x3f << 16); + val |= BIT(21); + val &= ~(0x1f << 24); + val |= BIT(29); + an8855_reg_write(gsw, INTF_CTRL_10, val); + + val = an8855_reg_read(gsw, INTF_CTRL_11); + val &= ~(0x3f); + val |= (0xd << 0); + val |= BIT(6); + an8855_reg_write(gsw, INTF_CTRL_11, val); + + /* RX CDR - improve RX Jitter Tolerance */ + val = an8855_reg_read(gsw, RG_QP_CDR_LPF_BOT_LIM); + val &= ~(0x7 << 24); + val |= (0x6 << 24); + val &= ~(0x7 << 20); + val |= (0x6 << 20); + an8855_reg_write(gsw, RG_QP_CDR_LPF_BOT_LIM, val); + + /* PMA Init */ + /* PLL */ + val = an8855_reg_read(gsw, QP_DIG_MODE_CTRL_1); + val &= ~BITS(2, 3); + an8855_reg_write(gsw, QP_DIG_MODE_CTRL_1, val); + + /* PLL - LPF */ + val = an8855_reg_read(gsw, 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_reg_write(gsw, PLL_CTRL_2, val); + + /* PLL - ICO */ + val = an8855_reg_read(gsw, PLL_CTRL_4); + val |= BIT(2); + an8855_reg_write(gsw, PLL_CTRL_4, val); + + val = an8855_reg_read(gsw, PLL_CTRL_2); + val &= ~BIT(14); + an8855_reg_write(gsw, PLL_CTRL_2, val); + + /* PLL - CHP */ + val = an8855_reg_read(gsw, PLL_CTRL_2); + val &= ~(0xf << 16); + val |= (0x4 << 16); + an8855_reg_write(gsw, PLL_CTRL_2, val); + + /* PLL - PFD */ + val = an8855_reg_read(gsw, PLL_CTRL_2); + val &= ~(0x3 << 20); + val |= (0x1 << 20); + val &= ~(0x3 << 24); + val |= (0x1 << 24); + val &= ~BIT(26); + an8855_reg_write(gsw, PLL_CTRL_2, val); + + /* PLL - POSTDIV */ + val = an8855_reg_read(gsw, PLL_CTRL_2); + val |= BIT(22); + val &= ~BIT(27); + val &= ~BIT(28); + an8855_reg_write(gsw, PLL_CTRL_2, val); + + /* PLL - SDM */ + val = an8855_reg_read(gsw, PLL_CTRL_4); + val &= ~BITS(3, 4); + an8855_reg_write(gsw, PLL_CTRL_4, val); + + val = an8855_reg_read(gsw, PLL_CTRL_2); + val &= ~BIT(30); + an8855_reg_write(gsw, PLL_CTRL_2, val); + + val = an8855_reg_read(gsw, SS_LCPLL_PWCTL_SETTING_2); + val &= ~(0x3 << 16); + val |= (0x1 << 16); + an8855_reg_write(gsw, SS_LCPLL_PWCTL_SETTING_2, val); + + an8855_reg_write(gsw, SS_LCPLL_TDC_FLT_2, 0x48000000); + an8855_reg_write(gsw, SS_LCPLL_TDC_PCW_1, 0x48000000); + + val = an8855_reg_read(gsw, SS_LCPLL_TDC_FLT_5); + val &= ~BIT(24); + an8855_reg_write(gsw, SS_LCPLL_TDC_FLT_5, val); + + val = an8855_reg_read(gsw, PLL_CK_CTRL_0); + val &= ~BIT(8); + an8855_reg_write(gsw, PLL_CK_CTRL_0, val); + + /* PLL - SS */ + val = an8855_reg_read(gsw, PLL_CTRL_3); + val &= ~BITS(0, 15); + an8855_reg_write(gsw, PLL_CTRL_3, val); + + val = an8855_reg_read(gsw, PLL_CTRL_4); + val &= ~BITS(0, 1); + an8855_reg_write(gsw, PLL_CTRL_4, val); + + val = an8855_reg_read(gsw, PLL_CTRL_3); + val &= ~BITS(16, 31); + an8855_reg_write(gsw, PLL_CTRL_3, val); + + /* PLL - TDC */ + val = an8855_reg_read(gsw, PLL_CK_CTRL_0); + val &= ~BIT(9); + an8855_reg_write(gsw, PLL_CK_CTRL_0, val); + + val = an8855_reg_read(gsw, RG_QP_PLL_SDM_ORD); + val |= BIT(3); + val |= BIT(4); + an8855_reg_write(gsw, RG_QP_PLL_SDM_ORD, val); + + val = an8855_reg_read(gsw, RG_QP_RX_DAC_EN); + val &= ~(0x3 << 16); + val |= (0x2 << 16); + an8855_reg_write(gsw, RG_QP_RX_DAC_EN, val); + + /* PLL - TCL Disable (only for Co-SIM) */ + val = an8855_reg_read(gsw, PON_RXFEDIG_CTRL_0); + val &= ~BIT(12); + an8855_reg_write(gsw, PON_RXFEDIG_CTRL_0, val); + + /* TX Init */ + val = an8855_reg_read(gsw, RG_QP_TX_MODE_16B_EN); + val &= ~BIT(0); + val &= ~BITS(16, 31); + an8855_reg_write(gsw, RG_QP_TX_MODE_16B_EN, val); + + /* RX Init */ + val = an8855_reg_read(gsw, RG_QP_RXAFE_RESERVE); + val |= BIT(11); + an8855_reg_write(gsw, RG_QP_RXAFE_RESERVE, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_LPF_MJV_LIM); + val &= ~(0x3 << 4); + val |= (0x2 << 4); + an8855_reg_write(gsw, RG_QP_CDR_LPF_MJV_LIM, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_LPF_SETVALUE); + val &= ~(0xf << 25); + val |= (0x1 << 25); + val &= ~(0x7 << 29); + val |= (0x6 << 29); + an8855_reg_write(gsw, RG_QP_CDR_LPF_SETVALUE, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_PR_CKREF_DIV1); + val &= ~(0x1f << 8); + val |= (0xc << 8); + an8855_reg_write(gsw, RG_QP_CDR_PR_CKREF_DIV1, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE); + val &= ~(0x3f << 0); + val |= (0x19 << 0); + val &= ~BIT(6); + an8855_reg_write(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_FORCE_IBANDLPF_R_OFF); + val &= ~(0x7f << 6); + val |= (0x21 << 6); + val &= ~(0x3 << 16); + val |= (0x2 << 16); + val &= ~BIT(13); + an8855_reg_write(gsw, RG_QP_CDR_FORCE_IBANDLPF_R_OFF, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE); + val &= ~BIT(30); + an8855_reg_write(gsw, RG_QP_CDR_PR_KBAND_DIV_PCIE, val); + + val = an8855_reg_read(gsw, RG_QP_CDR_PR_CKREF_DIV1); + val &= ~(0x7 << 24); + val |= (0x4 << 24); + an8855_reg_write(gsw, RG_QP_CDR_PR_CKREF_DIV1, val); + + /* PMA (For HW Mode) */ + val = an8855_reg_read(gsw, RX_CTRL_26); + val |= BIT(23); + val &= ~BIT(24); + val |= BIT(26); + + an8855_reg_write(gsw, RX_CTRL_26, val); + + val = an8855_reg_read(gsw, RX_DLY_0); + val &= ~(0xff << 0); + val |= (0x6f << 0); + val |= BITS(8, 13); + an8855_reg_write(gsw, RX_DLY_0, val); + + val = an8855_reg_read(gsw, RX_CTRL_42); + val &= ~(0x1fff << 0); + val |= (0x150 << 0); + an8855_reg_write(gsw, RX_CTRL_42, val); + + val = an8855_reg_read(gsw, RX_CTRL_2); + val &= ~(0x1fff << 16); + val |= (0x150 << 16); + an8855_reg_write(gsw, RX_CTRL_2, val); + + val = an8855_reg_read(gsw, PON_RXFEDIG_CTRL_9); + val &= ~(0x7 << 0); + val |= (0x1 << 0); + an8855_reg_write(gsw, PON_RXFEDIG_CTRL_9, val); + + val = an8855_reg_read(gsw, RX_CTRL_8); + val &= ~(0xfff << 16); + val |= (0x200 << 16); + val &= ~(0x7fff << 0); + val |= (0xfff << 0); + an8855_reg_write(gsw, RX_CTRL_8, val); + + /* Frequency memter */ + val = an8855_reg_read(gsw, RX_CTRL_5); + val &= ~(0xfffff << 10); + val |= (0x28 << 10); + an8855_reg_write(gsw, RX_CTRL_5, val); + + val = an8855_reg_read(gsw, RX_CTRL_6); + val &= ~(0xfffff << 0); + val |= (0x64 << 0); + an8855_reg_write(gsw, RX_CTRL_6, val); + + val = an8855_reg_read(gsw, RX_CTRL_7); + val &= ~(0xfffff << 0); + val |= (0x2710 << 0); + an8855_reg_write(gsw, RX_CTRL_7, val); + + val = an8855_reg_read(gsw, PLL_CTRL_0); + val |= BIT(0); + an8855_reg_write(gsw, PLL_CTRL_0, val); + + if (mode == SGMII_MODE_FORCE) { + /* PCS Init */ + val = an8855_reg_read(gsw, QP_DIG_MODE_CTRL_0); + val &= ~BIT(0); + val &= ~BITS(4, 5); + an8855_reg_write(gsw, QP_DIG_MODE_CTRL_0, val); + + val = an8855_reg_read(gsw, RG_HSGMII_PCS_CTROL_1); + val &= ~BIT(30); + an8855_reg_write(gsw, RG_HSGMII_PCS_CTROL_1, val); + + /* Rate Adaption - GMII path config. */ + val = an8855_reg_read(gsw, RG_AN_SGMII_MODE_FORCE); + val |= BIT(0); + val &= ~BITS(4, 5); + an8855_reg_write(gsw, RG_AN_SGMII_MODE_FORCE, val); + + val = an8855_reg_read(gsw, SGMII_STS_CTRL_0); + val |= BIT(2); + val &= ~(0x3 << 4); + val |= (0x2 << 4); + an8855_reg_write(gsw, SGMII_STS_CTRL_0, val); + + val = an8855_reg_read(gsw, SGMII_REG_AN0); + val &= ~BIT(12); + an8855_reg_write(gsw, SGMII_REG_AN0, val); + + val = an8855_reg_read(gsw, PHY_RX_FORCE_CTRL_0); + val |= BIT(4); + an8855_reg_write(gsw, PHY_RX_FORCE_CTRL_0, val); + + val = an8855_reg_read(gsw, RATE_ADP_P0_CTRL_0); + val &= ~BITS(0, 3); + val |= BIT(28); + an8855_reg_write(gsw, RATE_ADP_P0_CTRL_0, val); + + val = an8855_reg_read(gsw, RG_RATE_ADAPT_CTRL_0); + val |= BIT(0); + val |= BIT(4); + val |= BITS(26, 27); + an8855_reg_write(gsw, RG_RATE_ADAPT_CTRL_0, val); + } else { + /* PCS Init */ + val = an8855_reg_read(gsw, RG_HSGMII_PCS_CTROL_1); + val &= ~BIT(30); + an8855_reg_write(gsw, RG_HSGMII_PCS_CTROL_1, val); + + /* Set AN Ability - Interrupt */ + val = an8855_reg_read(gsw, SGMII_REG_AN_FORCE_CL37); + val |= BIT(0); + an8855_reg_write(gsw, SGMII_REG_AN_FORCE_CL37, val); + + val = an8855_reg_read(gsw, SGMII_REG_AN_13); + val &= ~(0x3f << 0); + val |= (0xb << 0); + val |= BIT(8); + an8855_reg_write(gsw, SGMII_REG_AN_13, val); + + /* Rate Adaption - GMII path config. */ + val = an8855_reg_read(gsw, SGMII_REG_AN0); + val |= BIT(12); + an8855_reg_write(gsw, SGMII_REG_AN0, val); + + val = an8855_reg_read(gsw, MII_RA_AN_ENABLE); + val |= BIT(0); + an8855_reg_write(gsw, MII_RA_AN_ENABLE, val); + + val = an8855_reg_read(gsw, RATE_ADP_P0_CTRL_0); + val |= BIT(28); + an8855_reg_write(gsw, RATE_ADP_P0_CTRL_0, val); + + val = an8855_reg_read(gsw, RG_RATE_ADAPT_CTRL_0); + val |= BIT(0); + val |= BIT(4); + val |= BITS(26, 27); + an8855_reg_write(gsw, RG_RATE_ADAPT_CTRL_0, val); + + /* Only for Co-SIM */ + + /* AN Speed up (Only for Co-SIM) */ + + /* Restart AN */ + val = an8855_reg_read(gsw, SGMII_REG_AN0); + val |= BIT(9); + an8855_reg_write(gsw, SGMII_REG_AN0, val); + } + + /* bypass flow control to MAC */ + an8855_reg_write(gsw, MSG_RX_LIK_STS_0, 0x01010107); + an8855_reg_write(gsw, MSG_RX_LIK_STS_2, 0x00000EEF); + + return 0; +} + +static int an8855_set_port_rmii(struct gsw_an8855 *gsw) +{ + an8855_reg_write(gsw, RG_P5MUX_MODE, 0x301); + an8855_reg_write(gsw, RG_FORCE_CKDIR_SEL, 0x101); + an8855_reg_write(gsw, RG_SWITCH_MODE, 0x101); + an8855_reg_write(gsw, RG_FORCE_MAC5_SB, 0x1010101); + an8855_reg_write(gsw, CSR_RMII, 0x420102); + an8855_reg_write(gsw, RG_RGMII_TXCK_C, 0x1100910); + return 0; +} + +static int an8855_set_port_rgmii(struct gsw_an8855 *gsw) +{ + an8855_reg_write(gsw, RG_FORCE_MAC5_SB, 0x20101); + return 0; +} + +static int an8855_mac_port_setup(struct gsw_an8855 *gsw, u32 port, + struct an8855_port_cfg *port_cfg) +{ + u32 pmcr; + + if (port != 5) { + dev_info(gsw->dev, "port %d is not a MAC port\n", port); + return -EINVAL; + } + + if (port_cfg->enabled) { + pmcr = an8855_reg_read(gsw, PMCR(5)); + + switch (port_cfg->phy_mode) { + case PHY_INTERFACE_MODE_RMII: + pmcr &= ~FORCE_SPD; + pmcr |= FORCE_MODE | (MAC_SPD_100 << 28) | FORCE_DPX + | FORCE_LNK | FORCE_TX_FC | FORCE_RX_FC; + an8855_set_port_rmii(gsw); + break; + case PHY_INTERFACE_MODE_RGMII: + pmcr &= ~FORCE_SPD; + pmcr |= FORCE_MODE | (MAC_SPD_1000 << 28) | FORCE_DPX + | FORCE_LNK | FORCE_TX_FC | FORCE_RX_FC; + an8855_set_port_rgmii(gsw); + break; + case PHY_INTERFACE_MODE_SGMII: + if (port_cfg->force_link) { + pmcr &= ~FORCE_SPD; + pmcr |= FORCE_MODE | (MAC_SPD_1000 << 28) + | FORCE_DPX | FORCE_LNK | FORCE_TX_FC + | FORCE_RX_FC; + an8855_sgmii_setup(gsw, SGMII_MODE_FORCE); + } else + an8855_sgmii_setup(gsw, SGMII_MODE_AN); + break; + case PHY_INTERFACE_MODE_2500BASEX: + pmcr &= ~FORCE_SPD; + pmcr |= FORCE_MODE | (MAC_SPD_2500 << 28) | FORCE_DPX + | FORCE_LNK | FORCE_TX_FC | FORCE_RX_FC; + an8855_set_hsgmii_mode(gsw); + break; + default: + dev_info(gsw->dev, "%s is not supported by port %d\n", + phy_modes(port_cfg->phy_mode), port); + } + + if (port_cfg->force_link) + an8855_reg_write(gsw, PMCR(port), pmcr); + } + + return 0; +} + +static int an8855_sw_detect(struct gsw_an8855 *gsw, struct chip_rev *crev) +{ + u32 id, rev; + + id = an8855_reg_read(gsw, CHIP_ID); + rev = an8855_reg_read(gsw, CHIP_REV); + if (id == AN8855) { + if (crev) { + crev->rev = rev; + crev->name = "AN8855"; + } + + return 0; + } + + return -ENODEV; +} + +static void an8855_phy_setting(struct gsw_an8855 *gsw) +{ + 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_reg_write(gsw, RG_GPHY_AFE_PWD, 0x0); + + for (i = 0; i < AN8855_NUM_PHYS; i++) { + /* Enable HW auto downshift */ + gsw->mii_write(gsw, i, 0x1f, 0x1); + val = gsw->mii_read(gsw, i, PHY_EXT_REG_14); + val |= PHY_EN_DOWN_SHFIT; + gsw->mii_write(gsw, i, PHY_EXT_REG_14, val); + gsw->mii_write(gsw, i, 0x1f, 0x0); + + /* Enable Asymmetric Pause Capability */ + val = gsw->mii_read(gsw, i, MII_ADVERTISE); + val |= ADVERTISE_PAUSE_ASYM; + gsw->mii_write(gsw, i, MII_ADVERTISE, val); + } + + if (gsw->extSurge) { + for (i = 0; i < AN8855_NUM_PHYS; i++) { + /* Read data */ + for (j = 0; j < AN8855_WORD_SIZE; j++) { + val = an8855_reg_read(gsw, 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 = gsw->mmd_read(gsw, i, PHY_DEV1E, 0x174); + cl45_data &= ~(0x7f7f); + cl45_data |= (rsel_tx_a << 8); + cl45_data |= rsel_tx_b; + gsw->mmd_write(gsw, i, PHY_DEV1E, 0x174, cl45_data); + cl45_data = gsw->mmd_read(gsw, i, PHY_DEV1E, 0x175); + cl45_data &= ~(0x7f7f); + cl45_data |= (rsel_tx_c << 8); + cl45_data |= rsel_tx_d; + gsw->mmd_write(gsw, i, PHY_DEV1E, 0x175, cl45_data); + } + } +} + +static void an8855_eee_setting(struct gsw_an8855 *gsw, u32 port) +{ + /* Disable EEE */ + gsw->mmd_write(gsw, port, PHY_DEV07, PHY_DEV07_REG_03C, 0); +} + +static int an8855_led_set_usr_def(struct gsw_an8855 *gsw, u8 entity, + int polar, u16 on_evt, u16 blk_evt, u8 led_freq) +{ + u32 cl45_data = 0; + + if (polar == LED_HIGH) + on_evt |= LED_ON_POL; + else + on_evt &= ~LED_ON_POL; + + /* LED on event */ + gsw->mmd_write(gsw, (entity / 4), PHY_DEV1E, + PHY_SINGLE_LED_ON_CTRL(entity % 4), on_evt | LED_ON_EN); + + /* LED blink event */ + gsw->mmd_write(gsw, (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; + } + gsw->mmd_write(gsw, (entity / 4), PHY_DEV1E, + PHY_SINGLE_LED_BLK_DUR(entity % 4), cl45_data); + + gsw->mmd_write(gsw, (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 = gsw->mmd_read(gsw, (entity / 4), PHY_DEV1E, + PHY_PMA_CTRL); + cl45_data &= ~BIT(0); + cl45_data &= ~BIT(15); + gsw->mmd_write(gsw, (entity / 4), PHY_DEV1E, + PHY_PMA_CTRL, cl45_data); + + return 0; +} + +static int an8855_led_set_mode(struct gsw_an8855 *gsw, u8 mode) +{ + u16 cl45_data; + + cl45_data = gsw->mmd_read(gsw, 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_info(gsw->dev, "LED mode%d is not supported!\n", mode); + return -EINVAL; + } + gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_LED_BCR, cl45_data); + + return 0; +} + +static int an8855_led_set_state(struct gsw_an8855 *gsw, u8 entity, u8 state) +{ + u16 cl45_data = 0; + + /* Change to per port contorl */ + cl45_data = gsw->mmd_read(gsw, (entity / 4), PHY_DEV1E, + PHY_LED_CTRL_SELECT); + + if (state == 1) + cl45_data |= (1 << (entity % 4)); + else + cl45_data &= ~(1 << (entity % 4)); + gsw->mmd_write(gsw, (entity / 4), PHY_DEV1E, + PHY_LED_CTRL_SELECT, cl45_data); + + /* LED enable setting */ + cl45_data = gsw->mmd_read(gsw, (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; + + gsw->mmd_write(gsw, (entity / 4), PHY_DEV1E, + PHY_SINGLE_LED_ON_CTRL(entity % 4), cl45_data); + + return 0; +} + +static int an8855_led_init(struct gsw_an8855 *gsw) +{ + 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(gsw, AN8855_LED_MODE_USER_DEFINE); + if (ret != 0) { + dev_info(gsw->dev, "led_set_mode fail(ret:%d)!\n", ret); + return ret; + } + + for (id = 0; id < led_count; id++) { + ret = an8855_led_set_state(gsw, + led_cfg[id].phy_led_idx, led_cfg[id].en); + if (ret != 0) { + dev_info(gsw->dev, "led_set_state fail(ret:%d)!\n", ret); + return ret; + } + if (led_cfg[id].en == 1) { + ret = an8855_led_set_usr_def(gsw, + 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_info(gsw->dev, "led_set_usr_def fail!\n"); + return ret; + } + } + } + + /* Setting for System LED & Loop LED */ + an8855_reg_write(gsw, RG_GPIO_OE, 0x0); + an8855_reg_write(gsw, RG_GPIO_CTRL, 0x0); + val = 0; + an8855_reg_write(gsw, RG_GPIO_L_INV, val); + + val = 0x1001; + an8855_reg_write(gsw, RG_GPIO_CTRL, val); + val = an8855_reg_read(gsw, RG_GPIO_DATA); + val |= BITS(1, 3); + val &= ~(BIT(0)); + val &= ~(BIT(6)); + + an8855_reg_write(gsw, RG_GPIO_DATA, val); + val = an8855_reg_read(gsw, RG_GPIO_OE); + val |= 0x41; + an8855_reg_write(gsw, 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_reg_write(gsw, RG_GPIO_LED_SEL(tmp_id / 4), val); + tmp_val = an8855_reg_read(gsw, 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_reg_write(gsw, RG_GPIO_LED_MODE, val); + + /* Force clear blink pulse for per port LED */ + gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_LED_BLINK_DUR_CTRL, 0x1f); + usleep_range(1000, 5000); + gsw->mmd_write(gsw, 0, PHY_DEV1F, PHY_LED_BLINK_DUR_CTRL, 0); + + return 0; +} + +static int an8855_sw_init(struct gsw_an8855 *gsw) +{ + int i, ret = 0; + u32 val, led_count = ARRAY_SIZE(led_cfg); + int id; + + gsw->phy_base = gsw->smi_addr & AN8855_SMI_ADDR_MASK; + + gsw->mii_read = an8855_mii_read; + gsw->mii_write = an8855_mii_write; + gsw->mmd_read = an8855_mmd_read; + gsw->mmd_write = an8855_mmd_write; + + /* Force MAC link down before reset */ + an8855_reg_write(gsw, PMCR(5), FORCE_MODE); + + /* Switch soft reset */ + an8855_reg_write(gsw, SYS_CTRL, SW_SYS_RST); + usleep_range(100000, 110000); + + /* Change gphy smi address */ + if (gsw->new_smi_addr != gsw->smi_addr) { + an8855_reg_write(gsw, RG_GPHY_SMI_ADDR, gsw->new_smi_addr); + gsw->smi_addr = gsw->new_smi_addr; + gsw->phy_base = gsw->new_smi_addr; + } + + for (i = 0; i < AN8855_NUM_PHYS; i++) { + val = gsw->mii_read(gsw, i, MII_BMCR); + val |= BMCR_ISOLATE; + gsw->mii_write(gsw, i, MII_BMCR, val); + } + + /* AN8855H need to setup before switch init */ + val = an8855_reg_read(gsw, PKG_SEL); + if ((val & 0x7) == PAG_SEL_AN8855H) { + + /* Invert for LED activity change */ + val = an8855_reg_read(gsw, 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_reg_write(gsw, RG_GPIO_L_INV, (val | 0x1)); + + /* MCU NOP CMD */ + an8855_reg_write(gsw, RG_GDMP_RAM, 0x846); + an8855_reg_write(gsw, RG_GDMP_RAM + 4, 0x4a); + + /* Enable MCU */ + val = an8855_reg_read(gsw, RG_CLK_CPU_ICG); + an8855_reg_write(gsw, RG_CLK_CPU_ICG, val | MCU_ENABLE); + usleep_range(1000, 5000); + + /* Disable MCU watchdog */ + val = an8855_reg_read(gsw, RG_TIMER_CTL); + an8855_reg_write(gsw, RG_TIMER_CTL, (val & (~WDOG_ENABLE))); + + /* Configure interrupt */ + an8855_reg_write(gsw, RG_INTB_MODE, (0x1 << gsw->intr_pin)); + + /* LED settings for T830 reference board */ + ret = an8855_led_init(gsw); + if (ret < 0) { + dev_info(gsw->dev, "an8855_led_init fail. (ret=%d)\n", ret); + return ret; + } + } + + /* Adjust to reduce noise */ + for (i = 0; i < AN8855_NUM_PHYS; i++) { + gsw->mmd_write(gsw, i, PHY_DEV1E, + PHY_TX_PAIR_DLY_SEL_GBE, 0x4040); + + gsw->mmd_write(gsw, i, PHY_DEV1E, + PHY_RXADC_CTRL, 0x1010); + + gsw->mmd_write(gsw, i, PHY_DEV1E, + PHY_RXADC_REV_0, 0x100); + + gsw->mmd_write(gsw, i, PHY_DEV1E, + PHY_RXADC_REV_1, 0x100); + } + + /* Setup SERDES port 5 */ + an8855_mac_port_setup(gsw, 5, &gsw->port5_cfg); + + /* Global mac control settings */ + val = an8855_reg_read(gsw, GMACCR); + val |= (15 << MAX_RX_JUMBO_S) | RX_PKT_LEN_MAX_JUMBO; + an8855_reg_write(gsw, GMACCR, val); + + val = an8855_reg_read(gsw, CKGCR); + val &= ~(CKG_LNKDN_GLB_STOP | CKG_LNKDN_PORT_STOP); + an8855_reg_write(gsw, CKGCR, val); + + return 0; +} + +static int an8855_sw_post_init(struct gsw_an8855 *gsw) +{ + int i; + u32 val; + + for (i = 0; i < AN8855_NUM_PHYS; i++) { + val = gsw->mii_read(gsw, i, MII_BMCR); + val &= ~BMCR_ISOLATE; + gsw->mii_write(gsw, i, MII_BMCR, val); + } + + an8855_phy_setting(gsw); + + for (i = 0; i < AN8855_NUM_PHYS; i++) + an8855_eee_setting(gsw, i); + + /* PHY restart AN*/ + for (i = 0; i < AN8855_NUM_PHYS; i++) + gsw->mii_write(gsw, i, MII_BMCR, 0x1240); + + return 0; +} + +struct an8855_sw_id an8855_id = { + .model = AN8855, + .detect = an8855_sw_detect, + .init = an8855_sw_init, + .post_init = an8855_sw_post_init +}; + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Min Yao "); +MODULE_DESCRIPTION("Driver for Airoha AN8855 Gigabit Switch"); diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855.h b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855.h new file mode 100644 index 0000000000..5f84ee5bfa --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855.h @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2023 Airoha Inc. + * Author: Min Yao + */ + +#ifndef _AN8855_H_ +#define _AN8855_H_ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SWCONFIG +#include +#endif + +#include "an8855_vlan.h" + +#define AN8855_DFL_CPU_PORT 5 +#define AN8855_NUM_PHYS 5 +#define AN8855_WORD_SIZE 4 +#define AN8855_DFL_SMI_ADDR 0x1 +#define AN8855_SMI_ADDR_MASK 0x1f +#define AN8855_DFL_INTR_ID 0xd +#define AN8855_DFL_EXT_SURGE 0x0 + +#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 + +struct gsw_an8855; + +enum an8855_model { + AN8855 = 0x8855, +}; + +enum sgmii_mode { + SGMII_MODE_AN, + SGMII_MODE_FORCE, +}; + +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_cfg { + struct device_node *np; + phy_interface_t phy_mode; + u32 enabled: 1; + u32 force_link: 1; + u32 speed: 2; + u32 duplex: 1; + bool stag_on; +}; + +struct gsw_an8855 { + u32 id; + + struct device *dev; + struct mii_bus *host_bus; + u32 smi_addr; + u32 new_smi_addr; + u32 phy_base; + u32 intr_pin; + u32 extSurge; + + enum an8855_model model; + const char *name; + + struct an8855_port_cfg port5_cfg; + + int phy_link_sts; + + int irq; + int reset_pin; + struct work_struct irq_worker; + +#ifdef CONFIG_SWCONFIG + struct switch_dev swdev; + u32 cpu_port; +#endif + + int global_vlan_enable; + struct an8855_vlan_entry vlan_entries[AN8855_NUM_VLANS]; + struct an8855_port_entry port_entries[AN8855_NUM_PORTS]; + + int (*mii_read)(struct gsw_an8855 *gsw, int phy, int reg); + void (*mii_write)(struct gsw_an8855 *gsw, int phy, int reg, u16 val); + + int (*mmd_read)(struct gsw_an8855 *gsw, int addr, int devad, u16 reg); + void (*mmd_write)(struct gsw_an8855 *gsw, int addr, int devad, u16 reg, + u16 val); + + struct list_head list; +}; + +struct chip_rev { + const char *name; + u32 rev; +}; + +struct an8855_sw_id { + enum an8855_model model; + int (*detect)(struct gsw_an8855 *gsw, struct chip_rev *crev); + int (*init)(struct gsw_an8855 *gsw); + int (*post_init)(struct gsw_an8855 *gsw); +}; + +extern struct list_head an8855_devs; +extern struct an8855_sw_id an8855_id; + +struct gsw_an8855 *an8855_get_gsw(u32 id); +struct gsw_an8855 *an8855_get_first_gsw(void); +void an8855_put_gsw(void); +void an8855_lock_gsw(void); + +u32 an8855_reg_read(struct gsw_an8855 *gsw, u32 reg); +void an8855_reg_write(struct gsw_an8855 *gsw, u32 reg, u32 val); + +int an8855_mii_read(struct gsw_an8855 *gsw, int phy, int reg); +void an8855_mii_write(struct gsw_an8855 *gsw, int phy, int reg, u16 val); + +int an8855_mmd_read(struct gsw_an8855 *gsw, int addr, int devad, u16 reg); +void an8855_mmd_write(struct gsw_an8855 *gsw, int addr, int devad, u16 reg, + u16 val); + +void an8855_irq_worker(struct work_struct *work); +void an8855_irq_enable(struct gsw_an8855 *gsw); + +#endif /* _AN8855_H_ */ diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_common.c b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_common.c new file mode 100644 index 0000000000..c01b5c48c0 --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_common.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 Airoha Inc. + * Author: Min Yao + */ + +#include +#include + +#include "an8855.h" +#include "an8855_regs.h" + +void an8855_irq_enable(struct gsw_an8855 *gsw) +{ + u32 val; + int i; + + /* Record initial PHY link status */ + for (i = 0; i < AN8855_NUM_PHYS; i++) { + val = gsw->mii_read(gsw, i, MII_BMSR); + if (val & BMSR_LSTATUS) + gsw->phy_link_sts |= BIT(i); + } + + val = BIT(AN8855_NUM_PHYS) - 1; + an8855_reg_write(gsw, SYS_INT_EN, val); + + val = an8855_reg_read(gsw, INT_MASK); + val |= INT_SYS_BIT; + an8855_reg_write(gsw, INT_MASK, val); +} + +static void display_port_link_status(struct gsw_an8855 *gsw, u32 port) +{ + u32 pmsr, speed_bits; + const char *speed; + + pmsr = an8855_reg_read(gsw, PMSR(port)); + + speed_bits = (pmsr & MAC_SPD_STS_M) >> MAC_SPD_STS_S; + + switch (speed_bits) { + case MAC_SPD_10: + speed = "10Mbps"; + break; + case MAC_SPD_100: + speed = "100Mbps"; + break; + case MAC_SPD_1000: + speed = "1Gbps"; + break; + case MAC_SPD_2500: + speed = "2.5Gbps"; + break; + default: + dev_info(gsw->dev, "Invalid speed\n"); + return; + } + + if (pmsr & MAC_LNK_STS) { + dev_info(gsw->dev, "Port %d Link is Up - %s/%s\n", + port, speed, (pmsr & MAC_DPX_STS) ? "Full" : "Half"); + } else { + dev_info(gsw->dev, "Port %d Link is Down\n", port); + } +} + +void an8855_irq_worker(struct work_struct *work) +{ + struct gsw_an8855 *gsw; + u32 sts, physts, laststs; + int i; + + gsw = container_of(work, struct gsw_an8855, irq_worker); + + sts = an8855_reg_read(gsw, SYS_INT_STS); + + /* Check for changed PHY link status */ + for (i = 0; i < AN8855_NUM_PHYS; i++) { + if (!(sts & PHY_LC_INT(i))) + continue; + + laststs = gsw->phy_link_sts & BIT(i); + physts = !!(gsw->mii_read(gsw, i, MII_BMSR) & BMSR_LSTATUS); + physts <<= i; + + if (physts ^ laststs) { + gsw->phy_link_sts ^= BIT(i); + display_port_link_status(gsw, i); + } + } + + an8855_reg_write(gsw, SYS_INT_STS, sts); + + enable_irq(gsw->irq); +} diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_mdio.c b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_mdio.c new file mode 100644 index 0000000000..a4eb389837 --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_mdio.c @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023 Airoha Inc. + * Author: Min Yao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "an8855.h" +#include "an8855_swconfig.h" +#include "an8855_regs.h" +#include "an8855_nl.h" + +/* AN8855 driver version */ +#define ARHT_AN8855_SWCFG_DRIVER_VER "1.0.6" +#define ARHT_CHIP_NAME "an8855" +#define ARHT_PROC_DIR "air_sw" +#define ARHT_PROC_NODE_DEVICE "device" + +static u32 an8855_gsw_id; +struct list_head an8855_devs; +static DEFINE_MUTEX(an8855_devs_lock); +struct proc_dir_entry *proc_an8855_gsw_dir; + +static struct an8855_sw_id *an8855_sw_ids[] = { + &an8855_id, +}; + +u32 an8855_reg_read(struct gsw_an8855 *gsw, u32 reg) +{ + u32 high, low; + + mutex_lock(&gsw->host_bus->mdio_lock); + + gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x4); + gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, 0x0); + + gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x15, + ((reg >> 16) & 0xFFFF)); + gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x16, + (reg & 0xFFFF)); + + low = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr, 0x18); + high = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr, 0x17); + + gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x0); + + mutex_unlock(&gsw->host_bus->mdio_lock); + + return (high << 16) | (low & 0xffff); +} + +void an8855_reg_write(struct gsw_an8855 *gsw, u32 reg, u32 val) +{ + mutex_lock(&gsw->host_bus->mdio_lock); + + gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x4); + gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, 0x0); + + gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x11, + ((reg >> 16) & 0xFFFF)); + gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x12, + (reg & 0xFFFF)); + + gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x13, + ((val >> 16) & 0xFFFF)); + gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x14, + (val & 0xFFFF)); + + gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x0); + + mutex_unlock(&gsw->host_bus->mdio_lock); +} + +int an8855_mii_read(struct gsw_an8855 *gsw, int phy, int reg) +{ + int val; + + if (phy < AN8855_NUM_PHYS) + phy = (gsw->phy_base + phy) & AN8855_SMI_ADDR_MASK; + + mutex_lock(&gsw->host_bus->mdio_lock); + val = gsw->host_bus->read(gsw->host_bus, phy, reg); + mutex_unlock(&gsw->host_bus->mdio_lock); + + return val; +} + +void an8855_mii_write(struct gsw_an8855 *gsw, int phy, int reg, u16 val) +{ + if (phy < AN8855_NUM_PHYS) + phy = (gsw->phy_base + phy) & AN8855_SMI_ADDR_MASK; + + mutex_lock(&gsw->host_bus->mdio_lock); + gsw->host_bus->write(gsw->host_bus, phy, reg, val); + mutex_unlock(&gsw->host_bus->mdio_lock); +} + +int an8855_mmd_read(struct gsw_an8855 *gsw, int addr, int devad, u16 reg) +{ + int val; + + if (addr < AN8855_NUM_PHYS) + addr = (gsw->phy_base + addr) & AN8855_SMI_ADDR_MASK; + + mutex_lock(&gsw->host_bus->mdio_lock); + + gsw->host_bus->write(gsw->host_bus, addr, 0x0d, devad); + gsw->host_bus->write(gsw->host_bus, addr, 0x0e, reg); + gsw->host_bus->write(gsw->host_bus, addr, 0x0d, devad | (0x4000)); + val = gsw->host_bus->read(gsw->host_bus, addr, 0xe); + + mutex_unlock(&gsw->host_bus->mdio_lock); + + return val; +} + +void an8855_mmd_write(struct gsw_an8855 *gsw, int addr, int devad, u16 reg, + u16 val) +{ + if (addr < AN8855_NUM_PHYS) + addr = (gsw->phy_base + addr) & AN8855_SMI_ADDR_MASK; + + mutex_lock(&gsw->host_bus->mdio_lock); + + gsw->host_bus->write(gsw->host_bus, addr, 0x0d, devad); + gsw->host_bus->write(gsw->host_bus, addr, 0x0e, reg); + gsw->host_bus->write(gsw->host_bus, addr, 0x0d, devad | (0x4000)); + gsw->host_bus->write(gsw->host_bus, addr, 0x0e, val); + + mutex_unlock(&gsw->host_bus->mdio_lock); +} + +static inline int an8855_get_duplex(const struct device_node *np) +{ + return of_property_read_bool(np, "full-duplex"); +} + +static void an8855_load_port_cfg(struct gsw_an8855 *gsw) +{ + struct device_node *port_np; + struct device_node *fixed_link_node; + struct an8855_port_cfg *port_cfg; + u32 port; +#if (KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE) + int ret; + +#endif + + for_each_child_of_node(gsw->dev->of_node, port_np) { + if (!of_device_is_compatible(port_np, "airoha,an8855-port")) + continue; + + if (!of_device_is_available(port_np)) + continue; + + if (of_property_read_u32(port_np, "reg", &port)) + continue; + + switch (port) { + case 5: + port_cfg = &gsw->port5_cfg; + break; + default: + continue; + } + + if (port_cfg->enabled) { + dev_info(gsw->dev, "duplicated node for port%d\n", + port_cfg->phy_mode); + continue; + } + + port_cfg->np = port_np; +#if (KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE) + ret = of_get_phy_mode(port_np, &port_cfg->phy_mode); + if (ret < 0) { +#else + port_cfg->phy_mode = of_get_phy_mode(port_np); + if (port_cfg->phy_mode < 0) { +#endif + dev_info(gsw->dev, "incorrect phy-mode %d\n", port); + continue; + } + + fixed_link_node = of_get_child_by_name(port_np, "fixed-link"); + if (fixed_link_node) { + u32 speed; + + port_cfg->force_link = 1; + port_cfg->duplex = an8855_get_duplex(fixed_link_node); + + if (of_property_read_u32(fixed_link_node, "speed", + &speed)) { + speed = 0; + continue; + } + + of_node_put(fixed_link_node); + + switch (speed) { + case 10: + port_cfg->speed = MAC_SPD_10; + break; + case 100: + port_cfg->speed = MAC_SPD_100; + break; + case 1000: + port_cfg->speed = MAC_SPD_1000; + break; + case 2500: + port_cfg->speed = MAC_SPD_2500; + break; + + default: + dev_info(gsw->dev, "incorrect speed %d\n", + speed); + continue; + } + } + + port_cfg->stag_on = + of_property_read_bool(port_cfg->np, "airoha,stag-on"); + port_cfg->enabled = 1; + } +} + +static void an8855_add_gsw(struct gsw_an8855 *gsw) +{ + mutex_lock(&an8855_devs_lock); + gsw->id = an8855_gsw_id++; + INIT_LIST_HEAD(&gsw->list); + list_add_tail(&gsw->list, &an8855_devs); + mutex_unlock(&an8855_devs_lock); +} + +static void an8855_remove_gsw(struct gsw_an8855 *gsw) +{ + mutex_lock(&an8855_devs_lock); + list_del(&gsw->list); + mutex_unlock(&an8855_devs_lock); +} + +struct gsw_an8855 *an8855_get_gsw(u32 id) +{ + struct gsw_an8855 *dev; + + mutex_lock(&an8855_devs_lock); + + list_for_each_entry(dev, &an8855_devs, list) { + if (dev->id == id) + return dev; + } + + mutex_unlock(&an8855_devs_lock); + + return NULL; +} + +struct gsw_an8855 *an8855_get_first_gsw(void) +{ + struct gsw_an8855 *dev; + + mutex_lock(&an8855_devs_lock); + + list_for_each_entry(dev, &an8855_devs, list) + return dev; + + mutex_unlock(&an8855_devs_lock); + + return NULL; +} + +void an8855_put_gsw(void) +{ + mutex_unlock(&an8855_devs_lock); +} + +void an8855_lock_gsw(void) +{ + mutex_lock(&an8855_devs_lock); +} + +static int an8855_hw_reset(struct gsw_an8855 *gsw) +{ + struct device_node *np = gsw->dev->of_node; + int ret; + + gsw->reset_pin = of_get_named_gpio(np, "reset-gpios", 0); + if (gsw->reset_pin < 0) { + dev_info(gsw->dev, "No reset pin of switch\n"); + return 0; + } + + ret = devm_gpio_request(gsw->dev, gsw->reset_pin, "an8855-reset"); + if (ret) { + dev_info(gsw->dev, "Failed to request gpio %d\n", + gsw->reset_pin); + return ret; + } + + gpio_direction_output(gsw->reset_pin, 0); + gpio_set_value(gsw->reset_pin, 0); + usleep_range(100000, 150000); + gpio_set_value(gsw->reset_pin, 1); + usleep_range(100000, 150000); + + return 0; +} + +static irqreturn_t an8855_irq_handler(int irq, void *dev) +{ + struct gsw_an8855 *gsw = dev; + + disable_irq_nosync(gsw->irq); + + schedule_work(&gsw->irq_worker); + + return IRQ_HANDLED; +} + +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); +} + +#if (KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE) +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, +}; +#else +static const struct file_operations an8855_proc_device_fops = { + .owner = THIS_MODULE, + .open = an8855_proc_device_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +static int an8855_proc_device_init(struct gsw_an8855 *gsw) +{ + if (!proc_an8855_gsw_dir) + proc_an8855_gsw_dir = proc_mkdir(ARHT_PROC_DIR, 0); + + proc_create(ARHT_PROC_NODE_DEVICE, 0400, proc_an8855_gsw_dir, + &an8855_proc_device_fops); + + return 0; +} + +static void an8855_proc_device_exit(void) +{ + remove_proc_entry(ARHT_PROC_NODE_DEVICE, 0); +} + +static int an8855_probe(struct platform_device *pdev) +{ + struct gsw_an8855 *gsw; + struct an8855_sw_id *sw; + struct device_node *np = pdev->dev.of_node; + struct device_node *mdio; + struct mii_bus *mdio_bus; + int ret = -EINVAL; + struct chip_rev rev; + struct an8855_mapping *map; + int i; + + mdio = of_parse_phandle(np, "airoha,mdio", 0); + if (!mdio) + return -EINVAL; + + mdio_bus = of_mdio_find_bus(mdio); + if (!mdio_bus) + return -EPROBE_DEFER; + + gsw = devm_kzalloc(&pdev->dev, sizeof(struct gsw_an8855), GFP_KERNEL); + if (!gsw) + return -ENOMEM; + + gsw->host_bus = mdio_bus; + gsw->dev = &pdev->dev; + + dev_info(gsw->dev, "AN8855 Driver Version=%s\n", + ARHT_AN8855_SWCFG_DRIVER_VER); + + /* Switch hard reset */ + if (an8855_hw_reset(gsw)) { + dev_info(&pdev->dev, "reset switch fail.\n"); + goto fail; + } + + /* Fetch the SMI address first */ + gsw->smi_addr = AN8855_DFL_SMI_ADDR; + if (of_property_read_u32(np, "airoha,smi-addr", &gsw->new_smi_addr)) + gsw->new_smi_addr = AN8855_DFL_SMI_ADDR; + + /* Assign AN8855 interrupt pin */ + if (of_property_read_u32(np, "airoha,intr", &gsw->intr_pin)) + gsw->intr_pin = AN8855_DFL_INTR_ID; + + /* AN8855 surge enhancement */ + if (of_property_read_u32(np, "airoha,extSurge", &gsw->extSurge)) + gsw->extSurge = AN8855_DFL_EXT_SURGE; + + /* Get LAN/WAN port mapping */ + map = an8855_find_mapping(np); + if (map) { + an8855_apply_mapping(gsw, map); + gsw->global_vlan_enable = 1; + dev_info(gsw->dev, "LAN/WAN VLAN setting=%s\n", map->name); + } + + /* Load MAC port configurations */ + an8855_load_port_cfg(gsw); + + /* Check for valid switch and then initialize */ + an8855_gsw_id = 0; + for (i = 0; i < ARRAY_SIZE(an8855_sw_ids); i++) { + if (!an8855_sw_ids[i]->detect(gsw, &rev)) { + sw = an8855_sw_ids[i]; + + gsw->name = rev.name; + gsw->model = sw->model; + + dev_info(gsw->dev, "Switch is Airoha %s rev %d", + gsw->name, rev.rev); + + /* Initialize the switch */ + ret = sw->init(gsw); + if (ret) + goto fail; + + break; + } + } + + if (i >= ARRAY_SIZE(an8855_sw_ids)) { + dev_err(gsw->dev, "No an8855 switch found\n"); + goto fail; + } + + gsw->irq = platform_get_irq(pdev, 0); + if (gsw->irq >= 0) { + ret = devm_request_irq(gsw->dev, gsw->irq, an8855_irq_handler, + 0, dev_name(gsw->dev), gsw); + if (ret) { + dev_err(gsw->dev, "Failed to request irq %d\n", + gsw->irq); + goto fail; + } + + INIT_WORK(&gsw->irq_worker, an8855_irq_worker); + } + + platform_set_drvdata(pdev, gsw); + + an8855_add_gsw(gsw); + + an8855_gsw_nl_init(); + + an8855_proc_device_init(gsw); + + an8855_swconfig_init(gsw); + + if (sw->post_init) + sw->post_init(gsw); + + if (gsw->irq >= 0) + an8855_irq_enable(gsw); + + return 0; + +fail: + devm_kfree(&pdev->dev, gsw); + + return ret; +} + +static int an8855_remove(struct platform_device *pdev) +{ + struct gsw_an8855 *gsw = platform_get_drvdata(pdev); + + if (gsw->irq >= 0) + cancel_work_sync(&gsw->irq_worker); + + if (gsw->reset_pin >= 0) + devm_gpio_free(&pdev->dev, gsw->reset_pin); + +#ifdef CONFIG_SWCONFIG + an8855_swconfig_destroy(gsw); +#endif + + an8855_proc_device_exit(); + + an8855_gsw_nl_exit(); + + an8855_remove_gsw(gsw); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id an8855_ids[] = { + {.compatible = "airoha,an8855"}, + {}, +}; + +MODULE_DEVICE_TABLE(of, an8855_ids); + +static struct platform_driver an8855_driver = { + .probe = an8855_probe, + .remove = an8855_remove, + .driver = { + .name = "an8855", + .of_match_table = an8855_ids, + }, +}; + +static int __init an8855_init(void) +{ + int ret; + + INIT_LIST_HEAD(&an8855_devs); + ret = platform_driver_register(&an8855_driver); + + return ret; +} + +module_init(an8855_init); + +static void __exit an8855_exit(void) +{ + platform_driver_unregister(&an8855_driver); +} + +module_exit(an8855_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Min Yao "); +MODULE_VERSION(ARHT_AN8855_SWCFG_DRIVER_VER); +MODULE_DESCRIPTION("Driver for Airoha AN8855 Gigabit Switch"); diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_nl.c b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_nl.c new file mode 100644 index 0000000000..f5af6d3fcf --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_nl.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023 Airoha Inc. + * Author: Min Yao + */ + +#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, struct gsw_an8855 *gsw); + u32 nr_required_attrs; + const enum an8855_attr *required_attrs; +}; + +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, +// .policy = an8855_nl_cmd_policy, + .flags = GENL_ADMIN_PERM, + }, { + .cmd = AN8855_CMD_READ, + .doit = an8855_nl_response, +// .policy = an8855_nl_cmd_policy, + .flags = GENL_ADMIN_PERM, + }, { + .cmd = AN8855_CMD_WRITE, + .doit = an8855_nl_response, +// .policy = an8855_nl_cmd_policy, + .flags = GENL_ADMIN_PERM, + }, +}; + +static struct genl_family an8855_nl_family = { + .name = AN8855_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_list_devs(char *buff, int size) +{ + struct gsw_an8855 *gsw; + int len, total = 0; + char buf[80]; + + memset(buff, 0, size); + + an8855_lock_gsw(); + + list_for_each_entry(gsw, &an8855_devs, list) { + len = snprintf(buf, sizeof(buf), + "id: %d, model: %s, node: %s\n", + gsw->id, gsw->name, gsw->dev->of_node->name); + if (len == strlen(buf)) { + if (size - total > 0) + strncat(buff, buf, size - total); + total += len; + } + } + + an8855_put_gsw(); + + return total; +} + +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 struct gsw_an8855 *an8855_nl_parse_find_gsw(struct genl_info *info) +{ + struct gsw_an8855 *gsw; + struct nlattr *na; + int gsw_id; + + na = info->attrs[AN8855_ATTR_TYPE_DEV_ID]; + if (na) { + gsw_id = nla_get_s32(na); + if (gsw_id >= 0) + gsw = an8855_get_gsw(gsw_id); + else + gsw = an8855_get_first_gsw(); + } else { + gsw = an8855_get_first_gsw(); + } + + return gsw; +} + +static int an8855_nl_get_swdevs(struct genl_info *info, struct gsw_an8855 *gsw) +{ + struct sk_buff *rep_skb = NULL; + char dev_info[512]; + int ret; + + ret = an8855_nl_list_devs(dev_info, sizeof(dev_info) - 1); + if (!ret) { + pr_info("No switch registered\n"); + return -EINVAL; + } + + ret = an8855_nl_prepare_reply(info, AN8855_CMD_REPLY, &rep_skb); + if (ret < 0) + goto err; + + ret = nla_put_string(rep_skb, AN8855_ATTR_TYPE_MESG, dev_info); + 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_read(struct genl_info *info, struct gsw_an8855 *gsw) +{ + 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 = gsw->mii_read(gsw, phy, reg); + else + value = gsw->mmd_read(gsw, phy, devad, reg); + } else { + value = an8855_reg_read(gsw, 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 gsw_an8855 *gsw) +{ + 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) + gsw->mii_write(gsw, phy, reg, value); + else + gsw->mmd_write(gsw, phy, devad, reg, value); + } else { + an8855_reg_write(gsw, 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_REQUEST, + .require_dev = false, + .process = an8855_nl_get_swdevs + }, { + .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; + struct gsw_an8855 *gsw = 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; + } + + if (cmditem->require_dev) { + gsw = an8855_nl_parse_find_gsw(info); + if (!gsw) { + pr_info("an8855-nl: failed to find switch dev\n"); + return -EINVAL; + } + } + + ret = cmditem->process(info, gsw); + + an8855_put_gsw(); + + return ret; +} + +int an8855_gsw_nl_init(void) +{ + int ret; + + ret = genl_register_family(&an8855_nl_family); + if (ret) { + pr_info("an8855-nl: genl_register_family_with_ops failed\n"); + return ret; + } + + return 0; +} + +void an8855_gsw_nl_exit(void) +{ + genl_unregister_family(&an8855_nl_family); +} diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_nl.h b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_nl.h new file mode 100644 index 0000000000..b00df75403 --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/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_GENL_NAME "an8855" +#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_gsw_nl_init(void); +void an8855_gsw_nl_exit(void); +#endif /* __KERNEL__ */ + +#endif /* _AN8855_NL_H_ */ diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_regs.h b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_regs.h new file mode 100644 index 0000000000..32790a1bfa --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_regs.h @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2023 Airoha Inc. + * Author: Min Yao + */ + +#ifndef _AN8855_REGS_H_ +#define _AN8855_REGS_H_ + +#include + +#define BITS(m, n) (~(BIT(m) - 1) & ((BIT(n) - 1) | BIT(n))) + +/* Values of Egress TAG Control */ +#define ETAG_CTRL_UNTAG 0 +#define ETAG_CTRL_TAG 2 +#define ETAG_CTRL_SWAP 1 +#define ETAG_CTRL_STACK 3 + +#define VTCR 0x10200600 +#define VAWD0 0x10200604 +#define VAWD1 0x10200608 +#define VARD0 0x10200618 +#define VARD1 0x1020061C + +/* Fields of VTCR */ +#define VTCR_BUSY BIT(31) +#define VTCR_FUNC_S 12 +#define VTCR_FUNC_M 0xf000 +#define VTCR_VID_S 0 +#define VTCR_VID_M 0xfff + +/* Values of VTCR_FUNC */ +#define VTCR_READ_VLAN_ENTRY 0 +#define VTCR_WRITE_VLAN_ENTRY 1 + +/* VLAN entry fields */ +#define IVL_MAC BIT(5) +#define EG_CON BIT(11) +#define VTAG_EN BIT(10) +#define PORT_MEM_S 26 +#define PORT_MEM_M 0xfc000000 +#define VENTRY_VALID BIT(0) +#define ETAG_M 0x3fff000 +#define PORT_ETAG_S(p) (((p) * 2) + 12) +#define PORT_ETAG_M 0x03 + +#define PORT_CTRL_BASE 0x10208000 +#define PORT_CTRL_PORT_OFFSET 0x200 +#define PORT_CTRL_REG(p, r) (PORT_CTRL_BASE + \ + (p) * PORT_CTRL_PORT_OFFSET + (r)) +#define PCR(p) PORT_CTRL_REG(p, 0x04) +#define PVC(p) PORT_CTRL_REG(p, 0x10) +#define PORTMATRIX(p) PORT_CTRL_REG(p, 0x44) +#define PVID(p) PORT_CTRL_REG(p, 0x48) + +#define GRP_PORT_VID_M 0xfff + +/* Values of PORT_VLAN */ +#define PORT_MATRIX_MODE 0 +#define FALLBACK_MODE 1 +#define CHECK_MODE 2 +#define SECURITY_MODE 3 + +/* Fields of PVC */ +#define STAG_VPID_S 16 +#define STAG_VPID_M 0xffff0000 +#define VLAN_ATTR_S 6 +#define VLAN_ATTR_M 0xc0 +#define PVC_STAG_REPLACE BIT(11) +#define PVC_PORT_STAG BIT(5) + +/* Values of VLAN_ATTR */ +#define VA_USER_PORT 0 +#define VA_STACK_PORT 1 +#define VA_TRANSLATION_PORT 2 +#define VA_TRANSPARENT_PORT 3 + +#define PORT_MATRIX_M ((1 << AN8855_NUM_PORTS) - 1) + +#define PORT_MAC_CTRL_BASE 0x10210000 +#define PORT_MAC_CTRL_PORT_OFFSET 0x200 +#define PORT_MAC_CTRL_REG(p, r) (PORT_MAC_CTRL_BASE + \ + (p) * PORT_MAC_CTRL_PORT_OFFSET + (r)) +#define PMCR(p) PORT_MAC_CTRL_REG(p, 0x00) +#define PMSR(p) PORT_MAC_CTRL_REG(p, 0x10) + +#define GMACCR (PORT_MAC_CTRL_BASE + 0x3e00) + +#define MAX_RX_JUMBO_S 4 +#define MAX_RX_JUMBO_M 0xf0 +#define MAX_RX_PKT_LEN_S 0 +#define MAX_RX_PKT_LEN_M 0x3 + +/* Values of MAX_RX_PKT_LEN */ +#define RX_PKT_LEN_1518 0 +#define RX_PKT_LEN_1536 1 +#define RX_PKT_LEN_1522 2 +#define RX_PKT_LEN_MAX_JUMBO 3 + +/* Fields of PMSR */ +#define EEE1G_STS BIT(7) +#define EEE100_STS BIT(6) +#define RX_FC_STS BIT(5) +#define TX_FC_STS BIT(4) +#define MAC_SPD_STS_S 28 +#define MAC_SPD_STS_M 0x70000000 +#define MAC_DPX_STS BIT(25) +#define MAC_LNK_STS BIT(24) + +/* Values of MAC_SPD_STS */ +#define MAC_SPD_10 0 +#define MAC_SPD_100 1 +#define MAC_SPD_1000 2 +#define MAC_SPD_2500 3 + +#define MIB_COUNTER_BASE 0x10214000 +#define MIB_COUNTER_PORT_OFFSET 0x200 +#define MIB_COUNTER_REG(p, r) (MIB_COUNTER_BASE + \ + (p) * MIB_COUNTER_PORT_OFFSET + (r)) +#define STATS_TDPC 0x00 +#define STATS_TCRC 0x04 +#define STATS_TUPC 0x08 +#define STATS_TMPC 0x0C +#define STATS_TBPC 0x10 +#define STATS_TCEC 0x14 +#define STATS_TSCEC 0x18 +#define STATS_TMCEC 0x1C +#define STATS_TDEC 0x20 +#define STATS_TLCEC 0x24 +#define STATS_TXCEC 0x28 +#define STATS_TPPC 0x2C +#define STATS_TL64PC 0x30 +#define STATS_TL65PC 0x34 +#define STATS_TL128PC 0x38 +#define STATS_TL256PC 0x3C +#define STATS_TL512PC 0x40 +#define STATS_TL1024PC 0x44 +#define STATS_TL1519PC 0x48 +#define STATS_TOC 0x4C +#define STATS_TODPC 0x54 +#define STATS_TOC2 0x58 + +#define STATS_RDPC 0x80 +#define STATS_RFPC 0x84 +#define STATS_RUPC 0x88 +#define STATS_RMPC 0x8C +#define STATS_RBPC 0x90 +#define STATS_RAEPC 0x94 +#define STATS_RCEPC 0x98 +#define STATS_RUSPC 0x9C +#define STATS_RFEPC 0xA0 +#define STATS_ROSPC 0xA4 +#define STATS_RJEPC 0xA8 +#define STATS_RPPC 0xAC +#define STATS_RL64PC 0xB0 +#define STATS_RL65PC 0xB4 +#define STATS_RL128PC 0xB8 +#define STATS_RL256PC 0xBC +#define STATS_RL512PC 0xC0 +#define STATS_RL1024PC 0xC4 +#define STATS_RL1519PC 0xC8 +#define STATS_ROC 0xCC +#define STATS_RDPC_CTRL 0xD4 +#define STATS_RDPC_ING 0xD8 +#define STATS_RDPC_ARL 0xDC +#define STATS_RDPC_FC 0xE0 +#define STATS_RDPC_WRED 0xE4 +#define STATS_RDPC_MIR 0xE8 +#define STATS_ROC2 0xEC +#define STATS_RSFSPC 0xF4 +#define STATS_RSFTPC 0xF8 +#define STATS_RXCDPC 0xFC + +#define RG_CLK_CPU_ICG 0x10005034 +#define MCU_ENABLE BIT(3) + +#define RG_TIMER_CTL 0x1000a100 +#define WDOG_ENABLE BIT(25) + +#define SYS_CTRL 0x100050C0 +#define SW_SYS_RST BIT(31) + +#define INT_MASK 0x100050F0 +#define INT_SYS_BIT BIT(15) + +#define SYS_INT_EN 0x1021C010 +#define SYS_INT_STS 0x1021C014 +#define PHY_LC_INT(p) BIT(p) + +#define CKGCR 0x10213E1C +#define CKG_LNKDN_GLB_STOP 0x01 +#define CKG_LNKDN_PORT_STOP 0x02 + +#define PKG_SEL 0x10000094 +#define PAG_SEL_AN8855H 0x2 +#endif /* _AN8855_REGS_H_ */ diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_swconfig.c b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_swconfig.c new file mode 100644 index 0000000000..d083da0916 --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_swconfig.c @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023 Airoha Inc. + * Author: Min Yao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "an8855.h" +#include "an8855_swconfig.h" +#include "an8855_regs.h" + +#define AN8855_PORT_MIB_TXB_ID 19 /* TxByte */ +#define AN8855_PORT_MIB_RXB_ID 40 /* RxByte */ + +#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; +}; + +static const struct an8855_mib_desc an8855_mibs[] = { + MIB_DESC(1, STATS_TDPC, "TxDrop"), + MIB_DESC(1, STATS_TCRC, "TxCRC"), + MIB_DESC(1, STATS_TUPC, "TxUni"), + MIB_DESC(1, STATS_TMPC, "TxMulti"), + MIB_DESC(1, STATS_TBPC, "TxBroad"), + MIB_DESC(1, STATS_TCEC, "TxCollision"), + MIB_DESC(1, STATS_TSCEC, "TxSingleCol"), + MIB_DESC(1, STATS_TMCEC, "TxMultiCol"), + MIB_DESC(1, STATS_TDEC, "TxDefer"), + MIB_DESC(1, STATS_TLCEC, "TxLateCol"), + MIB_DESC(1, STATS_TXCEC, "TxExcCol"), + MIB_DESC(1, STATS_TPPC, "TxPause"), + MIB_DESC(1, STATS_TL64PC, "Tx64Byte"), + MIB_DESC(1, STATS_TL65PC, "Tx65Byte"), + MIB_DESC(1, STATS_TL128PC, "Tx128Byte"), + MIB_DESC(1, STATS_TL256PC, "Tx256Byte"), + MIB_DESC(1, STATS_TL512PC, "Tx512Byte"), + MIB_DESC(1, STATS_TL1024PC, "Tx1024Byte"), + MIB_DESC(1, STATS_TL1519PC, "Tx1519Byte"), + MIB_DESC(2, STATS_TOC, "TxByte"), + MIB_DESC(1, STATS_TODPC, "TxOverSize"), + MIB_DESC(2, STATS_TOC2, "SecondaryTxByte"), + MIB_DESC(1, STATS_RDPC, "RxDrop"), + MIB_DESC(1, STATS_RFPC, "RxFiltered"), + MIB_DESC(1, STATS_RUPC, "RxUni"), + MIB_DESC(1, STATS_RMPC, "RxMulti"), + MIB_DESC(1, STATS_RBPC, "RxBroad"), + MIB_DESC(1, STATS_RAEPC, "RxAlignErr"), + MIB_DESC(1, STATS_RCEPC, "RxCRC"), + MIB_DESC(1, STATS_RUSPC, "RxUnderSize"), + MIB_DESC(1, STATS_RFEPC, "RxFragment"), + MIB_DESC(1, STATS_ROSPC, "RxOverSize"), + MIB_DESC(1, STATS_RJEPC, "RxJabber"), + MIB_DESC(1, STATS_RPPC, "RxPause"), + MIB_DESC(1, STATS_RL64PC, "Rx64Byte"), + MIB_DESC(1, STATS_RL65PC, "Rx65Byte"), + MIB_DESC(1, STATS_RL128PC, "Rx128Byte"), + MIB_DESC(1, STATS_RL256PC, "Rx256Byte"), + MIB_DESC(1, STATS_RL512PC, "Rx512Byte"), + MIB_DESC(1, STATS_RL1024PC, "Rx1024Byte"), + MIB_DESC(2, STATS_ROC, "RxByte"), + MIB_DESC(1, STATS_RDPC_CTRL, "RxCtrlDrop"), + MIB_DESC(1, STATS_RDPC_ING, "RxIngDrop"), + MIB_DESC(1, STATS_RDPC_ARL, "RxARLDrop"), + MIB_DESC(1, STATS_RDPC_FC, "RxFCDrop"), + MIB_DESC(1, STATS_RDPC_WRED, "RxWREDDrop"), + MIB_DESC(1, STATS_RDPC_MIR, "RxMIRDrop"), + MIB_DESC(2, STATS_ROC2, "SecondaryRxByte"), + MIB_DESC(1, STATS_RSFSPC, "RxsFlowSampling"), + MIB_DESC(1, STATS_RSFTPC, "RxsFlowTotal"), + MIB_DESC(1, STATS_RXCDPC, "RxPortDrop"), +}; + +enum { + /* Global attributes. */ + AN8855_ATTR_ENABLE_VLAN, +}; + +static int an8855_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + + val->value.i = gsw->global_vlan_enable; + + return 0; +} + +static int an8855_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + + gsw->global_vlan_enable = val->value.i != 0; + + return 0; +} + +static int an8855_get_port_pvid(struct switch_dev *dev, int port, int *val) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + + if (port < 0 || port >= AN8855_NUM_PORTS) + return -EINVAL; + + *val = an8855_reg_read(gsw, PVID(port)); + *val &= GRP_PORT_VID_M; + + return 0; +} + +static int an8855_set_port_pvid(struct switch_dev *dev, int port, int pvid) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + + if (port < 0 || port >= AN8855_NUM_PORTS) + return -EINVAL; + + if (pvid < AN8855_MIN_VID || pvid > AN8855_MAX_VID) + return -EINVAL; + + gsw->port_entries[port].pvid = pvid; + + return 0; +} + +static int an8855_get_vlan_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + u32 member; + u32 etags; + int i; + + val->len = 0; + + if (val->port_vlan >= AN8855_NUM_VLANS) + return -EINVAL; + + an8855_vlan_ctrl(gsw, VTCR_READ_VLAN_ENTRY, val->port_vlan); + + member = an8855_reg_read(gsw, VARD0); + member &= PORT_MEM_M; + member >>= PORT_MEM_S; + member |= ((an8855_reg_read(gsw, VARD1) & 0x1) << 6); + + etags = an8855_reg_read(gsw, VARD0) & ETAG_M; + + for (i = 0; i < AN8855_NUM_PORTS; i++) { + struct switch_port *p; + int etag; + + if (!(member & BIT(i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + + etag = (etags >> PORT_ETAG_S(i)) & PORT_ETAG_M; + + if (etag == ETAG_CTRL_TAG) + p->flags |= BIT(SWITCH_PORT_FLAG_TAGGED); + else if (etag != ETAG_CTRL_UNTAG) + dev_info(gsw->dev, + "vlan egress tag control neither untag nor tag.\n"); + } + + return 0; +} + +static int an8855_set_vlan_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + u8 member = 0; + u8 etags = 0; + int i; + + if (val->port_vlan >= AN8855_NUM_VLANS || + val->len > AN8855_NUM_PORTS) + return -EINVAL; + + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + + if (p->id >= AN8855_NUM_PORTS) + return -EINVAL; + + member |= BIT(p->id); + + if (p->flags & BIT(SWITCH_PORT_FLAG_TAGGED)) + etags |= BIT(p->id); + } + + gsw->vlan_entries[val->port_vlan].member = member; + gsw->vlan_entries[val->port_vlan].etags = etags; + + return 0; +} + +static int an8855_set_vid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + int vlan; + u16 vid; + + vlan = val->port_vlan; + vid = (u16)val->value.i; + + if (vlan < 0 || vlan >= AN8855_NUM_VLANS) + return -EINVAL; + + if (vid > AN8855_MAX_VID) + return -EINVAL; + + gsw->vlan_entries[vlan].vid = vid; + return 0; +} + +static int an8855_get_vid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + val->value.i = val->port_vlan; + return 0; +} + +static int an8855_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + u32 speed, pmsr; + + if (port < 0 || port >= AN8855_NUM_PORTS) + return -EINVAL; + + pmsr = an8855_reg_read(gsw, PMSR(port)); + + link->link = pmsr & MAC_LNK_STS; + link->duplex = pmsr & MAC_DPX_STS; + speed = (pmsr & MAC_SPD_STS_M) >> MAC_SPD_STS_S; + + switch (speed) { + case MAC_SPD_10: + link->speed = SWITCH_PORT_SPEED_10; + break; + case MAC_SPD_100: + link->speed = SWITCH_PORT_SPEED_100; + break; + case MAC_SPD_1000: + link->speed = SWITCH_PORT_SPEED_1000; + break; + case MAC_SPD_2500: + link->speed = SWITCH_PORT_SPEED_2500; + break; + } + + return 0; +} + +static int an8855_set_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ +#ifndef MODULE + if (port >= AN8855_NUM_PHYS) + return -EINVAL; + + return switch_generic_set_link(dev, port, link); +#else + return -ENOTSUPP; +#endif +} + +static u64 get_mib_counter(struct gsw_an8855 *gsw, int i, int port) +{ + unsigned int offset; + u64 lo = 0, hi = 0, hi2 = 0; + + if (i >= 0) { + offset = an8855_mibs[i].offset; + + if (an8855_mibs[i].size == 1) + return an8855_reg_read(gsw, MIB_COUNTER_REG(port, offset)); + + do { + hi = an8855_reg_read(gsw, MIB_COUNTER_REG(port, offset + 4)); + lo = an8855_reg_read(gsw, MIB_COUNTER_REG(port, offset)); + hi2 = an8855_reg_read(gsw, MIB_COUNTER_REG(port, offset + 4)); + } while (hi2 != hi); + } + + return (hi << 32) | lo; +} + +static int an8855_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + static char buf[4096]; + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + int i, len = 0; + + if (val->port_vlan >= AN8855_NUM_PORTS) + return -EINVAL; + + len += snprintf(buf + len, sizeof(buf) - len, + "Port %d MIB counters\n", val->port_vlan); + + for (i = 0; i < ARRAY_SIZE(an8855_mibs); ++i) { + u64 counter; + + len += snprintf(buf + len, sizeof(buf) - len, + "%-11s: ", an8855_mibs[i].name); + counter = get_mib_counter(gsw, i, val->port_vlan); + len += snprintf(buf + len, sizeof(buf) - len, "%llu\n", + counter); + } + + val->value.s = buf; + val->len = len; + return 0; +} + +static int an8855_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + + if (port < 0 || port >= AN8855_NUM_PORTS) + return -EINVAL; + + stats->tx_bytes = get_mib_counter(gsw, AN8855_PORT_MIB_TXB_ID, port); + stats->rx_bytes = get_mib_counter(gsw, AN8855_PORT_MIB_RXB_ID, port); + + return 0; +} + +static void an8855_port_isolation(struct gsw_an8855 *gsw) +{ + int i; + + for (i = 0; i < AN8855_NUM_PORTS; i++) + an8855_reg_write(gsw, PORTMATRIX(i), + BIT(gsw->cpu_port)); + + an8855_reg_write(gsw, PORTMATRIX(gsw->cpu_port), PORT_MATRIX_M); + + for (i = 0; i < AN8855_NUM_PORTS; i++) { + u32 pvc_mode = 0x9100 << STAG_VPID_S; + + if (gsw->port5_cfg.stag_on && i == 5) + pvc_mode |= PVC_PORT_STAG | PVC_STAG_REPLACE; + else + pvc_mode |= (VA_TRANSPARENT_PORT << VLAN_ATTR_S); + + an8855_reg_write(gsw, PVC(i), pvc_mode); + } +} + +static int an8855_apply_config(struct switch_dev *dev) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + + if (!gsw->global_vlan_enable) { + an8855_port_isolation(gsw); + return 0; + } + + an8855_apply_vlan_config(gsw); + + return 0; +} + +static int an8855_reset_switch(struct switch_dev *dev) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + int i; + + memset(gsw->port_entries, 0, sizeof(gsw->port_entries)); + memset(gsw->vlan_entries, 0, sizeof(gsw->vlan_entries)); + + /* set default vid of each vlan to the same number of vlan, so the vid + * won't need be set explicitly. + */ + for (i = 0; i < AN8855_NUM_VLANS; i++) + gsw->vlan_entries[i].vid = i; + + return 0; +} + +static int an8855_phy_read16(struct switch_dev *dev, int addr, u8 reg, + u16 *value) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + + *value = gsw->mii_read(gsw, addr, reg); + + return 0; +} + +static int an8855_phy_write16(struct switch_dev *dev, int addr, u8 reg, + u16 value) +{ + struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev); + + gsw->mii_write(gsw, addr, reg, value); + + return 0; +} + +static const struct switch_attr an8855_global[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "VLAN mode (1:enabled)", + .max = 1, + .id = AN8855_ATTR_ENABLE_VLAN, + .get = an8855_get_vlan_enable, + .set = an8855_set_vlan_enable, + } +}; + +static const struct switch_attr an8855_port[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .get = an8855_get_port_mib, + .set = NULL, + }, +}; + +static const struct switch_attr an8855_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID (0-4094)", + .set = an8855_set_vid, + .get = an8855_get_vid, + .max = 4094, + }, +}; + +static const struct switch_dev_ops an8855_swdev_ops = { + .attr_global = { + .attr = an8855_global, + .n_attr = ARRAY_SIZE(an8855_global), + }, + .attr_port = { + .attr = an8855_port, + .n_attr = ARRAY_SIZE(an8855_port), + }, + .attr_vlan = { + .attr = an8855_vlan, + .n_attr = ARRAY_SIZE(an8855_vlan), + }, + .get_vlan_ports = an8855_get_vlan_ports, + .set_vlan_ports = an8855_set_vlan_ports, + .get_port_pvid = an8855_get_port_pvid, + .set_port_pvid = an8855_set_port_pvid, + .get_port_link = an8855_get_port_link, + .set_port_link = an8855_set_port_link, + .get_port_stats = an8855_get_port_stats, + .apply_config = an8855_apply_config, + .reset_switch = an8855_reset_switch, + .phy_read16 = an8855_phy_read16, + .phy_write16 = an8855_phy_write16, +}; + +int an8855_swconfig_init(struct gsw_an8855 *gsw) +{ + struct device_node *np = gsw->dev->of_node; + struct switch_dev *swdev; + int ret; + + if (of_property_read_u32(np, "airoha,cpuport", &gsw->cpu_port)) + gsw->cpu_port = AN8855_DFL_CPU_PORT; + + swdev = &gsw->swdev; + + swdev->name = gsw->name; + swdev->alias = gsw->name; + swdev->cpu_port = gsw->cpu_port; + swdev->ports = AN8855_NUM_PORTS; + swdev->vlans = AN8855_NUM_VLANS; + swdev->ops = &an8855_swdev_ops; + + ret = register_switch(swdev, NULL); + if (ret) { + dev_notice(gsw->dev, "Failed to register switch %s\n", + swdev->name); + return ret; + } + + an8855_apply_config(swdev); + + return 0; +} + +void an8855_swconfig_destroy(struct gsw_an8855 *gsw) +{ + unregister_switch(&gsw->swdev); +} diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_swconfig.h b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_swconfig.h new file mode 100644 index 0000000000..4c8f2bf6f2 --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_swconfig.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023 Airoha Inc. + * Author: Min Yao + */ + +#ifndef _AN8855_SWCONFIG_H_ +#define _AN8855_SWCONFIG_H_ + +#ifdef CONFIG_SWCONFIG +#include +#include "an8855.h" + +int an8855_swconfig_init(struct gsw_an8855 *gsw); +void an8855_swconfig_destroy(struct gsw_an8855 *gsw); +#else +static inline int an8855_swconfig_init(struct gsw_an8855 *gsw) +{ + an8855_apply_vlan_config(gsw); + + return 0; +} + +static inline void an8855_swconfig_destroy(struct gsw_an8855 *gsw) +{ +} +#endif + +#endif /* _AN8855_SWCONFIG_H_ */ diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_vlan.c b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_vlan.c new file mode 100644 index 0000000000..1a1d751fc8 --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_vlan.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023 Airoha Inc. + */ + +#include "an8855.h" +#include "an8855_regs.h" + +struct an8855_mapping an8855_def_mapping[] = { + { + .name = "llllw", + .pvids = { 1, 1, 1, 1, 2, 1 }, + .members = { 0, 0x2f, 0x30 }, + .etags = { 0, 0, 0 }, + .vids = { 0, 1, 2 }, + }, { + .name = "wllll", + .pvids = { 2, 1, 1, 1, 1, 1 }, + .members = { 0, 0x3e, 0x21 }, + .etags = { 0, 0, 0 }, + .vids = { 0, 1, 2 }, + }, { + .name = "lwlll", + .pvids = { 1, 2, 1, 1, 1, 1 }, + .members = { 0, 0x3d, 0x22 }, + .etags = { 0, 0, 0 }, + .vids = { 0, 1, 2 }, + }, { + .name = "lllll", + .pvids = { 1, 1, 1, 1, 1, 1 }, + .members = { 0, 0x3f }, + .etags = { 0, 0 }, + .vids = { 0, 1 }, + }, +}; + +void an8855_vlan_ctrl(struct gsw_an8855 *gsw, u32 cmd, u32 val) +{ + int i; + + an8855_reg_write(gsw, VTCR, + VTCR_BUSY | ((cmd << VTCR_FUNC_S) & VTCR_FUNC_M) | + (val & VTCR_VID_M)); + + for (i = 0; i < 300; i++) { + u32 val = an8855_reg_read(gsw, VTCR); + + if ((val & VTCR_BUSY) == 0) + break; + + usleep_range(1000, 1100); + } + + if (i == 300) + dev_info(gsw->dev, "vtcr timeout\n"); +} + +static void an8855_write_vlan_entry(struct gsw_an8855 *gsw, int vlan, u16 vid, + u8 ports, u8 etags) +{ + int port; + u32 val; + + /* vlan port membership */ + if (ports) { + val = IVL_MAC | VTAG_EN | VENTRY_VALID + | ((ports << PORT_MEM_S) & PORT_MEM_M); + /* egress mode */ + for (port = 0; port < AN8855_NUM_PORTS; port++) { + if (etags & BIT(port)) + val |= (ETAG_CTRL_TAG << PORT_ETAG_S(port)); + else + val |= (ETAG_CTRL_UNTAG << PORT_ETAG_S(port)); + } + an8855_reg_write(gsw, VAWD0, val); + } else { + an8855_reg_write(gsw, VAWD0, 0); + } + + if (ports & 0x40) + an8855_reg_write(gsw, VAWD1, 0x1); + else + an8855_reg_write(gsw, VAWD1, 0x0); + + /* write to vlan table */ + an8855_vlan_ctrl(gsw, VTCR_WRITE_VLAN_ENTRY, vid); +} + +void an8855_apply_vlan_config(struct gsw_an8855 *gsw) +{ + int i, j; + u8 tag_ports; + u8 untag_ports; + u32 val; + + /* set all ports as security mode */ + for (i = 0; i < AN8855_NUM_PORTS; i++) { + val = an8855_reg_read(gsw, PCR(i)); + an8855_reg_write(gsw, PCR(i), val | SECURITY_MODE); + an8855_reg_write(gsw, PORTMATRIX(i), PORT_MATRIX_M); + } + + /* check if a port is used in tag/untag vlan egress mode */ + tag_ports = 0; + untag_ports = 0; + + for (i = 0; i < AN8855_NUM_VLANS; i++) { + u8 member = gsw->vlan_entries[i].member; + u8 etags = gsw->vlan_entries[i].etags; + + if (!member) + continue; + + for (j = 0; j < AN8855_NUM_PORTS; j++) { + if (!(member & BIT(j))) + continue; + + if (etags & BIT(j)) + tag_ports |= 1u << j; + else + untag_ports |= 1u << j; + } + } + + /* set all untag-only ports as transparent and the rest as user port */ + for (i = 0; i < AN8855_NUM_PORTS; i++) { + u32 pvc_mode = 0x9100 << STAG_VPID_S; + + if (untag_ports & BIT(i) && !(tag_ports & BIT(i))) + pvc_mode = (0x9100 << STAG_VPID_S) | + (VA_TRANSPARENT_PORT << VLAN_ATTR_S); + + if (gsw->port5_cfg.stag_on && i == 5) + pvc_mode = (u32)((0x9100 << STAG_VPID_S) | PVC_PORT_STAG + | PVC_STAG_REPLACE); + + an8855_reg_write(gsw, PVC(i), pvc_mode); + } + + /* first clear the switch vlan table */ + for (i = 0; i < AN8855_NUM_VLANS; i++) + an8855_write_vlan_entry(gsw, i, i, 0, 0); + + /* now program only vlans with members to avoid + * clobbering remapped entries in later iterations + */ + for (i = 0; i < AN8855_NUM_VLANS; i++) { + u16 vid = gsw->vlan_entries[i].vid; + u8 member = gsw->vlan_entries[i].member; + u8 etags = gsw->vlan_entries[i].etags; + + if (member) + an8855_write_vlan_entry(gsw, i, vid, member, etags); + } + + /* Port Default PVID */ + for (i = 0; i < AN8855_NUM_PORTS; i++) { + int vlan = gsw->port_entries[i].pvid; + u16 pvid = 0; + u32 val; + + if ((vlan >= 0) && (vlan < AN8855_NUM_VLANS) + && (gsw->vlan_entries[vlan].member)) + pvid = gsw->vlan_entries[vlan].vid; + + val = an8855_reg_read(gsw, PVID(i)); + val &= ~GRP_PORT_VID_M; + val |= pvid; + an8855_reg_write(gsw, PVID(i), val); + } +} + +struct an8855_mapping *an8855_find_mapping(struct device_node *np) +{ + const char *map; + int i; + + if (of_property_read_string(np, "airoha,portmap", &map)) + return NULL; + + for (i = 0; i < ARRAY_SIZE(an8855_def_mapping); i++) + if (!strcmp(map, an8855_def_mapping[i].name)) + return &an8855_def_mapping[i]; + + return NULL; +} + +void an8855_apply_mapping(struct gsw_an8855 *gsw, struct an8855_mapping *map) +{ + int i = 0; + + for (i = 0; i < AN8855_NUM_PORTS; i++) + gsw->port_entries[i].pvid = map->pvids[i]; + + for (i = 0; i < AN8855_NUM_VLANS; i++) { + gsw->vlan_entries[i].member = map->members[i]; + gsw->vlan_entries[i].etags = map->etags[i]; + gsw->vlan_entries[i].vid = map->vids[i]; + } +} diff --git a/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_vlan.h b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_vlan.h new file mode 100644 index 0000000000..d2cd68ac79 --- /dev/null +++ b/target/linux/mediatek/files-5.4/drivers/net/phy/airoha/an8855/an8855_vlan.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023 Airoha Inc. + */ + +#ifndef _AN8855_VLAN_H_ +#define _AN8855_VLAN_H_ + +#define AN8855_NUM_PORTS 6 +#define AN8855_NUM_VLANS 4095 +#define AN8855_MAX_VID 4095 +#define AN8855_MIN_VID 0 + +struct gsw_an8855; + +struct an8855_port_entry { + u16 pvid; +}; + +struct an8855_vlan_entry { + u16 vid; + u8 member; + u8 etags; +}; + +struct an8855_mapping { + char *name; + u16 pvids[AN8855_NUM_PORTS]; + u8 members[AN8855_NUM_VLANS]; + u8 etags[AN8855_NUM_VLANS]; + u16 vids[AN8855_NUM_VLANS]; +}; + +extern struct an8855_mapping an8855_defaults[]; + +void an8855_vlan_ctrl(struct gsw_an8855 *gsw, u32 cmd, u32 val); +void an8855_apply_vlan_config(struct gsw_an8855 *gsw); +struct an8855_mapping *an8855_find_mapping(struct device_node *np); +void an8855_apply_mapping(struct gsw_an8855 *gsw, struct an8855_mapping *map); +#endif /* _AN8855_VLAN_H_ */ diff --git a/target/linux/mediatek/files-5.4/net/dsa/tag_arht.c b/target/linux/mediatek/files-5.4/net/dsa/tag_arht.c new file mode 100644 index 0000000000..60ca9861d5 --- /dev/null +++ b/target/linux/mediatek/files-5.4/net/dsa/tag_arht.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha DSA Tag support + * Copyright (C) 2023 Min Yao + */ + +#include +#include + +#include "dsa_priv.h" + +#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; + unsigned char *dest = eth_hdr(skb)->h_dest; + + /* 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, + struct packet_type *pt) +{ + 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 int air_tag_flow_dissect(const struct sk_buff *skb, __be16 *proto, + int *offset) +{ + *offset = 4; + *proto = ((__be16 *)skb->data)[1]; + + return 0; +} + +static const struct dsa_device_ops air_netdev_ops = { + .name = "air", + .proto = DSA_TAG_PROTO_ARHT, + .xmit = air_tag_xmit, + .rcv = air_tag_rcv, + .flow_dissect = air_tag_flow_dissect, + .overhead = AIR_HDR_LEN, +}; + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_AIR); + +module_dsa_tag_driver(air_netdev_ops); diff --git a/target/linux/mediatek/mt7981/config-5.4 b/target/linux/mediatek/mt7981/config-5.4 index 290ade3bb4..9da9732381 100644 --- a/target/linux/mediatek/mt7981/config-5.4 +++ b/target/linux/mediatek/mt7981/config-5.4 @@ -3,6 +3,7 @@ CONFIG_AHCI_MTK=y # CONFIG_AIROHA_EN8801SC_PHY is not set # CONFIG_AIROHA_EN8801S_PHY is not set # CONFIG_AIR_EN8811H_PHY is not set +CONFIG_AN8855_GSW=y CONFIG_ARCH_CLOCKSOURCE_DATA=y CONFIG_ARCH_DMA_ADDR_T_64BIT=y CONFIG_ARCH_KEEP_MEMBLOCK=y @@ -335,7 +336,9 @@ CONFIG_NETFILTER_XT_NAT=m CONFIG_NETFILTER_XT_TARGET_MASQUERADE=m CONFIG_NET_DEVLINK=y CONFIG_NET_DSA=y +# CONFIG_NET_DSA_AN8855 is not set CONFIG_NET_DSA_MT7530=y +# CONFIG_NET_DSA_TAG_AIROHA is not set CONFIG_NET_DSA_TAG_MTK=y CONFIG_NET_FLOW_LIMIT=y # CONFIG_NET_MEDIATEK_HNAT is not set diff --git a/target/linux/mediatek/mt7986/config-5.4 b/target/linux/mediatek/mt7986/config-5.4 index 85000e0851..419911233c 100644 --- a/target/linux/mediatek/mt7986/config-5.4 +++ b/target/linux/mediatek/mt7986/config-5.4 @@ -3,6 +3,7 @@ CONFIG_AHCI_MTK=y # CONFIG_AIROHA_EN8801SC_PHY is not set # CONFIG_AIROHA_EN8801S_PHY is not set # CONFIG_AIR_EN8811H_PHY is not set +# CONFIG_AN8855_GSW is not set CONFIG_ARCH_CLOCKSOURCE_DATA=y CONFIG_ARCH_DMA_ADDR_T_64BIT=y CONFIG_ARCH_KEEP_MEMBLOCK=y @@ -335,7 +336,9 @@ CONFIG_NETFILTER_XT_NAT=m CONFIG_NETFILTER_XT_TARGET_MASQUERADE=m CONFIG_NET_DEVLINK=y CONFIG_NET_DSA=y +# CONFIG_NET_DSA_AN8855 is not set CONFIG_NET_DSA_MT7530=y +# CONFIG_NET_DSA_TAG_AIROHA is not set CONFIG_NET_DSA_TAG_MTK=y CONFIG_NET_FLOW_LIMIT=y # CONFIG_NET_MEDIATEK_HNAT is not set diff --git a/target/linux/mediatek/patches-5.4/999-2739-drivers_net_dsa_add_an8855.patch b/target/linux/mediatek/patches-5.4/999-2739-drivers_net_dsa_add_an8855.patch new file mode 100644 index 0000000000..d089c964af --- /dev/null +++ b/target/linux/mediatek/patches-5.4/999-2739-drivers_net_dsa_add_an8855.patch @@ -0,0 +1,25 @@ +Index: linux-5.4.238/drivers/net/dsa/Kconfig +=================================================================== +--- linux-5.4.238.orig/drivers/net/dsa/Kconfig 2023-12-26 15:31:49.427259000 +0800 ++++ linux-5.4.238/drivers/net/dsa/Kconfig 2023-12-26 15:46:52.655226000 +0800 +@@ -48,6 +48,8 @@ + This enables support for the Marvell 88E6060 ethernet switch + chip. + ++source "drivers/net/dsa/airoha/an8855/Kconfig" ++ + source "drivers/net/dsa/microchip/Kconfig" + + source "drivers/net/dsa/mv88e6xxx/Kconfig" +Index: linux-5.4.238/drivers/net/dsa/Makefile +=================================================================== +--- linux-5.4.238.orig/drivers/net/dsa/Makefile 2023-12-26 15:32:08.081306000 +0800 ++++ linux-5.4.238/drivers/net/dsa/Makefile 2023-12-26 15:47:59.858217000 +0800 +@@ -18,6 +18,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 += microchip/ + obj-y += mv88e6xxx/ diff --git a/target/linux/mediatek/patches-5.4/999-2739-drivers_net_phy_add_an8855_gsw.patch b/target/linux/mediatek/patches-5.4/999-2739-drivers_net_phy_add_an8855_gsw.patch new file mode 100644 index 0000000000..58220b3618 --- /dev/null +++ b/target/linux/mediatek/patches-5.4/999-2739-drivers_net_phy_add_an8855_gsw.patch @@ -0,0 +1,24 @@ +Index: linux-5.4.238/drivers/net/phy/Kconfig +=================================================================== +--- linux-5.4.238.orig/drivers/net/phy/Kconfig 2023-12-19 12:20:34.714805000 +0800 ++++ linux-5.4.238/drivers/net/phy/Kconfig 2023-12-25 10:11:11.328554000 +0800 +@@ -337,6 +337,8 @@ + + source "drivers/net/phy/mtk/mt753x/Kconfig" + ++source "drivers/net/phy/airoha/an8855/Kconfig" ++ + comment "MII PHY device drivers" + + config SFP +Index: linux-5.4.238/drivers/net/phy/Makefile +=================================================================== +--- linux-5.4.238.orig/drivers/net/phy/Makefile 2023-12-19 12:20:34.718809000 +0800 ++++ linux-5.4.238/drivers/net/phy/Makefile 2023-12-25 10:13:11.891535000 +0800 +@@ -121,5 +121,6 @@ + obj-$(CONFIG_VITESSE_PHY) += vitesse.o + obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o + obj-$(CONFIG_MT753X_GSW) += mtk/mt753x/ ++obj-$(CONFIG_AN8855_GSW) += airoha/an8855/ + obj-$(CONFIG_RTL8367S_GSW) += rtk/ + diff --git a/target/linux/mediatek/patches-5.4/999-2739-net_dsa_add_tag_arht.patch b/target/linux/mediatek/patches-5.4/999-2739-net_dsa_add_tag_arht.patch new file mode 100644 index 0000000000..ca36a0ce1b --- /dev/null +++ b/target/linux/mediatek/patches-5.4/999-2739-net_dsa_add_tag_arht.patch @@ -0,0 +1,46 @@ +Index: linux-5.4.238/net/dsa/Makefile +=================================================================== +--- linux-5.4.238.orig/net/dsa/Makefile 2023-12-09 09:43:04.335694000 +0800 ++++ linux-5.4.238/net/dsa/Makefile 2023-12-09 10:24:27.672514000 +0800 +@@ -16,3 +16,4 @@ + obj-$(CONFIG_NET_DSA_TAG_QCA) += tag_qca.o + obj-$(CONFIG_NET_DSA_TAG_SJA1105) += tag_sja1105.o + obj-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o ++obj-$(CONFIG_NET_DSA_TAG_AIROHA) += tag_arht.o +Index: linux-5.4.238/net/dsa/Kconfig +=================================================================== +--- linux-5.4.238.orig/net/dsa/Kconfig 2023-12-09 09:43:04.332694000 +0800 ++++ linux-5.4.238/net/dsa/Kconfig 2023-12-09 10:26:13.596504000 +0800 +@@ -74,6 +74,12 @@ + Say Y or M if you want to enable support for tagging frames for + Mediatek switches. + ++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_KSZ + tristate "Tag driver for Microchip 8795/9477/9893 families of switches" + help +Index: linux-5.4.238/include/net/dsa.h +=================================================================== +--- linux-5.4.238.orig/include/net/dsa.h 2023-12-09 09:43:17.940694000 +0800 ++++ linux-5.4.238/include/net/dsa.h 2023-12-09 10:30:06.432504000 +0800 +@@ -43,6 +43,7 @@ + #define DSA_TAG_PROTO_SJA1105_VALUE 13 + #define DSA_TAG_PROTO_KSZ8795_VALUE 14 + #define DSA_TAG_PROTO_RTL4_A_VALUE 17 ++#define DSA_TAG_PROTO_ARHT_VALUE 28 + + enum dsa_tag_protocol { + DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE, +@@ -61,6 +62,7 @@ + DSA_TAG_PROTO_SJA1105 = DSA_TAG_PROTO_SJA1105_VALUE, + DSA_TAG_PROTO_KSZ8795 = DSA_TAG_PROTO_KSZ8795_VALUE, + DSA_TAG_PROTO_RTL4_A = DSA_TAG_PROTO_RTL4_A_VALUE, ++ DSA_TAG_PROTO_ARHT = DSA_TAG_PROTO_ARHT_VALUE, + }; + + struct packet_type;