diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index c57a0262fb64..b85c50d43a72 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -367,6 +367,15 @@ config XILINX_GMII2RGMII the Reduced Gigabit Media Independent Interface(RGMII) between Ethernet physical media devices and the Gigabit Ethernet controller. +config QCA8337_SWITCH + tristate "Drivers for QTI Atheros QCA8337 switch" + help + This enables support for the QTI Atheros QCA8337 Ethernet + switch. This driver support switch funtionality over SGMII + interface. + Add downstream qca8337 driver + Support the emac driver + endif # PHYLIB config MICREL_KS8995MA diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index f7138d3c896b..c7b6b65e0d90 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -88,3 +88,4 @@ obj-$(CONFIG_STE10XP) += ste10Xp.o obj-$(CONFIG_TERANETICS_PHY) += teranetics.o obj-$(CONFIG_VITESSE_PHY) += vitesse.o obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o +obj-$(CONFIG_QCA8337_SWITCH) += qca8337.o diff --git a/drivers/net/phy/qca8337.c b/drivers/net/phy/qca8337.c new file mode 100644 index 000000000000..13230982afec --- /dev/null +++ b/drivers/net/phy/qca8337.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (c) 2014, 2015, 2017, The Linux Foundation. All rights reserved. + * Copyright (C) 2009 Felix Fietkau + * Copyright (C) 2011-2012 Gabor Juhos + * Copyright (c) 2016 John Crispin john@phrozen.org + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * Author: Matus Ujhelyi + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* QCA8337 Switch driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline void split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) +{ + regaddr >>= 1; + *r1 = regaddr & 0x1e; + + regaddr >>= 5; + *r2 = regaddr & 0x7; + + regaddr >>= 3; + *page = regaddr & 0x1ff; +} + +u32 qca8337_read(struct qca8337_priv *priv, u32 reg) +{ + struct phy_device *phy = priv->phy; + struct mii_bus *bus = phy->mdio.bus; + u16 r1, r2, page; + u16 lo, hi; + + mutex_lock(&bus->mdio_lock); + + split_addr(reg, &r1, &r2, &page); + + bus->write(bus, 0x18, 0, page); + usleep_range(1000, 2000); /* wait for the page switch to propagate */ + lo = bus->read(bus, 0x10 | r2, r1); + hi = bus->read(bus, 0x10 | r2, r1 + 1); + + mutex_unlock(&bus->mdio_lock); + + return (hi << 16) | lo; +} +EXPORT_SYMBOL_GPL(qca8337_read); + +void qca8337_write(struct qca8337_priv *priv, u32 reg, u32 val) +{ + struct phy_device *phy = priv->phy; + struct mii_bus *bus = phy->mdio.bus; + u16 r1, r2, r3; + u16 lo, hi; + + mutex_lock(&bus->mdio_lock); + + split_addr(reg, &r1, &r2, &r3); + lo = val & 0xffff; + hi = (u16)(val >> 16); + + bus->write(bus, 0x18, 0, r3); + usleep_range(1000, 2000); /* wait for the page switch to propagate */ + bus->write(bus, 0x10 | r2, r1, lo); + bus->write(bus, 0x10 | r2, r1 + 1, hi); + + mutex_unlock(&bus->mdio_lock); +} +EXPORT_SYMBOL_GPL(qca8337_write); + +static u32 +qca8337_rmw(struct qca8337_priv *priv, u32 reg, u32 mask, u32 val) +{ + u32 ret; + + ret = priv->ops->read(priv, reg); + ret &= ~mask; + ret |= val; + priv->ops->write(priv, reg, ret); + return ret; +} + +static void +qca8337_reg_set(struct qca8337_priv *priv, u32 reg, u32 val) +{ + qca8337_rmw(priv, reg, 0, val); +} + +static void qca8337_reset_switch(struct qca8337_priv *priv) +{ + u32 val = 0; + int count = 0; + + qca8337_reg_set(priv, QCA8337_REG_MASK_CTRL, QCA8337_CTRL_RESET); + + /*Need wait so reset done*/ + for (count = 0; count < 100; count++) { + usleep_range(5000, 10000); + + val = priv->ops->read(priv, QCA8337_REG_MASK_CTRL); + if (!val && !(val & QCA8337_CTRL_RESET)) + break; + } +} + +static void +qca8337_port_set_status(struct qca8337_priv *priv) +{ + qca8337_write(priv, QCA8337_REG_PORT_STATUS(0), + (QCA8337_PORT_SPEED_1000M | QCA8337_PORT_STATUS_TXMAC | + QCA8337_PORT_STATUS_RXMAC | QCA8337_PORT_STATUS_TXFLOW | + QCA8337_PORT_STATUS_RXFLOW | QCA8337_PORT_STATUS_DUPLEX)); + + qca8337_write(priv, QCA8337_REG_PORT_STATUS(6), + (QCA8337_PORT_SPEED_1000M | QCA8337_PORT_STATUS_TXMAC | + QCA8337_PORT_STATUS_RXMAC | QCA8337_PORT_STATUS_TXFLOW | + QCA8337_PORT_STATUS_RXFLOW | QCA8337_PORT_STATUS_DUPLEX)); +} + +static int +qca8337_busy_wait(struct qca8337_priv *priv, u32 reg, u32 mask) +{ + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(20); + + /* loop until the busy flag has cleared */ + do { + u32 val = priv->ops->read(priv, reg); + int busy = val & mask; + + if (!busy) + break; + cond_resched(); + } while (!time_after_eq(jiffies, timeout)); + + return time_after_eq(jiffies, timeout); +} + +static void +qca8337_mib_init(struct qca8337_priv *priv) +{ + qca8337_reg_set(priv, QCA8337_REG_MIB, + QCA8337_MIB_FLUSH | QCA8337_MIB_BUSY); + qca8337_busy_wait(priv, QCA8337_REG_MIB, QCA8337_MIB_BUSY); + qca8337_reg_set(priv, QCA8337_REG_MIB, QCA8337_MIB_CPU_KEEP); + priv->ops->write(priv, QCA8337_REG_MODULE_EN, QCA8337_MODULE_EN_MIB); +} + +static void qca8337_vlan_config(struct qca8337_priv *priv) +{ + priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(0), 0x0014007e); + priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(0), 0x10001); + + priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(1), 0x0014007d); + priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(1), 0x10001); + + priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(2), 0x0014007b); + priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(2), 0x10001); + + priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(3), 0x00140077); + priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(3), 0x10001); + + priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(4), 0x0014006f); + priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(4), 0x10001); + + priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(5), 0x0014005f); + priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(5), 0x10001); + + priv->ops->write(priv, QCA8337_REG_PORT_LOOKUP(6), 0x0014001e); + priv->ops->write(priv, QCA8337_REG_PORT_VLAN0(6), 0x10001); +} + +static int qca8337_hw_init(struct qca8337_priv *priv) +{ + int i; + + /* set pad control for cpu port */ + qca8337_write(priv, QCA8337_REG_PAD0_CTRL, QCA8337_PAD_SGMII_EN); + + qca8337_write(priv, QCA8337_REG_PAD5_CTRL, + QCA8337_PAD_RGMII_RXCLK_DELAY_EN); + + qca8337_write(priv, QCA8337_REG_PAD6_CTRL, + (QCA8337_PAD_RGMII_EN | QCA8337_PAD_RGMII_RXCLK_DELAY_EN | + (0x1 << QCA8337_PAD_RGMII_TXCLK_DELAY_SEL_S) | + (0x2 << QCA8337_PAD_RGMII_RXCLK_DELAY_SEL_S))); + + /* Enable CPU Port */ + qca8337_reg_set(priv, QCA8337_REG_GLOBAL_FW_CTRL0, + QCA8337_GLOBAL_FW_CTRL0_CPU_PORT_EN); + + qca8337_port_set_status(priv); + + /* Enable MIB counters */ + qca8337_mib_init(priv); + + /* Disable QCA header mode on the cpu port */ + priv->ops->write(priv, QCA8337_REG_PORT_HEADER(priv->cpu_port), 0); + + /* Disable forwarding by default on all ports */ + for (i = 0; i < priv->ports; i++) + qca8337_rmw(priv, QCA8337_REG_PORT_LOOKUP(i), + QCA8337_PORT_LOOKUP_MEMBER, 0); + + qca8337_write(priv, QCA8337_REG_GLOBAL_FW_CTRL1, + (QCA8337_IGMP_JOIN_LEAVE_DPALL | QCA8337_BROAD_DPALL | + QCA8337_MULTI_FLOOD_DPALL | QCA8337_UNI_FLOOD_DPALL)); + + /* Setup connection between CPU port & user ports */ + qca8337_vlan_config(priv); + + /* Disable AZ */ + priv->ops->write(priv, QCA8337_REG_EEE_CTRL, QCA8337_EEE_CTRL_DISABLE); + return 0; +} + +static void qca8337_reg_init_lan(struct qca8337_priv *priv) +{ + priv->ops->write(priv, QCA8337_REG_POWER_ON_STRIP, + QCA8337_REG_POS_VAL); + priv->ops->write(priv, QCA8337_MAC_PWR_SEL, + QCA8337_MAC_PWR_SEL_VAL); + priv->ops->write(priv, QCA8337_SGMII_CTRL_REG, + QCA8337_SGMII_CTRL_VAL); +} + +static void +qca8337_read_port_link(struct qca8337_priv *priv, int port, + struct port_link_info *port_link) +{ + u32 status; + u32 speed; + + memset(port_link, '\0', sizeof(*port_link)); + + status = priv->ops->read(priv, QCA8337_REG_PORT_STATUS(port)); + + port_link->aneg = !!(status & QCA8337_PORT_STATUS_LINK_AUTO); + if (port_link->aneg || port != priv->cpu_port) { + port_link->link = !!(status & QCA8337_PORT_STATUS_LINK_UP); + if (!port_link->link) + return; + } else { + port_link->link = true; + } + + port_link->duplex = !!(status & QCA8337_PORT_STATUS_DUPLEX); + port_link->tx_flow = !!(status & QCA8337_PORT_STATUS_TXFLOW); + port_link->rx_flow = !!(status & QCA8337_PORT_STATUS_RXFLOW); + + speed = (status & QCA8337_PORT_STATUS_SPEED) >> + QCA8337_PORT_STATUS_SPEED_S; + + switch (speed) { + case QCA8337_PORT_SPEED_10M: + port_link->speed = SPEED_10; + break; + case QCA8337_PORT_SPEED_100M: + port_link->speed = SPEED_100; + break; + case QCA8337_PORT_SPEED_1000M: + port_link->speed = SPEED_1000; + break; + default: + port_link->speed = SPEED_UNKNOWN; + break; + } +} + +static void qca8337_phy_enable(struct phy_device *phydev) +{ + int phyid = 0; + ushort phy_val; + struct mii_bus *bus; + struct qca8337_priv *priv = phydev->priv; + + bus = priv->phy->mdio.bus; + + if (phydev->autoneg == AUTONEG_ENABLE) { + int port; + + for (port = 1; port < priv->ports - 1; port++) + qca8337_write(priv, QCA8337_REG_PORT_STATUS(port), + 0x1280); + + for (phyid = 0; phyid < priv->num_phy ; phyid++) { + /*enable phy prefer multi-port mode*/ + phy_val = mdiobus_read(bus, phyid, MII_CTRL1000); + phy_val |= (ADVERTISE_MULTI_PORT_PREFER | + ADVERTISE_1000FULL); + mdiobus_write(bus, phyid, MII_CTRL1000, phy_val); + + /*enable extended next page. 0:enable, 1:disable*/ + phy_val = mdiobus_read(bus, phyid, MII_ADVERTISE); + phy_val &= (~(ADVERTISE_RESV)); + mdiobus_write(bus, phyid, MII_ADVERTISE, phy_val); + + /*Phy power up*/ + mdiobus_write(bus, phyid, MII_BMCR, (BMCR_RESET | + BMCR_ANENABLE)); + /* wait for the page switch to propagate */ + usleep_range(100, 200); + } + } else { + int port; + u32 status = 0; + + linkmode_and(phydev->advertising, phydev->advertising, phydev->supported); + + for (port = 1; port < priv->ports - 1; port++) { + status = 0; + status |= phydev->duplex ? + QCA8337_PORT_STATUS_DUPLEX : 0; + status |= (linkmode_test_bit(ADVERTISED_Asym_Pause, phydev->advertising)) ? + QCA8337_PORT_STATUS_TXFLOW : 0; + status |= (linkmode_test_bit(ADVERTISED_Pause, phydev->advertising)) ? + QCA8337_PORT_STATUS_RXFLOW : 0; + + if (phydev->speed == SPEED_1000) + status |= QCA8337_PORT_SPEED_1000M; + else if (phydev->speed == SPEED_100) + status |= QCA8337_PORT_SPEED_100M; + else if (phydev->speed == SPEED_10) + status |= QCA8337_PORT_SPEED_10M; + + qca8337_write(priv, QCA8337_REG_PORT_STATUS(port), + status); + /* wait for the page switch to propagate */ + usleep_range(100, 200); + + status |= QCA8337_PORT_STATUS_TXMAC | + QCA8337_PORT_STATUS_RXMAC; + qca8337_write(priv, QCA8337_REG_PORT_STATUS(port), + status); + } + + for (phyid = 0; phyid < priv->num_phy ; phyid++) { + phydev->drv->phy_id = phyid; + genphy_setup_forced(phydev); + } + + for (phyid = 0; phyid < priv->num_phy ; phyid++) { + phydev->drv->phy_id = phyid; + genphy_update_link(phydev); + + if (phydev->link) + break; + } + } +} + +static int qca8337_config_aneg(struct phy_device *phydev) +{ + qca8337_phy_enable(phydev); + + return 0; +} + +static int qca8337_read_status(struct phy_device *phydev) +{ + struct qca8337_priv *priv = phydev->priv; + struct port_link_info port_link; + int i, port_status = 0; + int speed = -1, duplex = 0; + + for (i = 1; i < priv->ports - 1; i++) { + qca8337_read_port_link(priv, i, &port_link); + + if (port_link.link) { + speed = (speed < port_link.speed) ? + port_link.speed : speed; + duplex = (duplex < port_link.duplex) ? + port_link.duplex : duplex; + port_status |= 1 << i; + } + } + + qca8337_read_port_link(priv, priv->cpu_port, &port_link); + phydev->link = (port_status) ? !!port_link.link : 0; + phydev->speed = speed; + phydev->duplex = duplex; + + return 0; +} + +static int qca8337_aneg_done(struct phy_device *phydev) +{ + int phyid = 0; + int retval = 0; + int aneg_status = 0; + struct qca8337_priv *priv = phydev->priv; + struct mii_bus *bus = priv->phy->mdio.bus; + + for (phyid = 0; phyid < priv->num_phy ; phyid++) { + retval = mdiobus_read(bus, phyid, MII_BMSR); + if (retval < 0) + return retval; + + (retval & BMSR_ANEGCOMPLETE) ? + (aneg_status |= 1 << phyid) : + (aneg_status |= 0 << phyid); + } + return aneg_status; +} + +static int +qca8337_regmap_read(void *ctx, uint32_t reg, uint32_t *val) +{ + struct qca8337_priv *priv = (struct qca8337_priv *)ctx; + + if (!priv->phy->link) + return -EPERM; + + *val = priv->ops->read(priv, reg); + return 0; +} + +static int +qca8337_regmap_write(void *ctx, uint32_t reg, uint32_t val) +{ + struct qca8337_priv *priv = (struct qca8337_priv *)ctx; + + if (!priv->phy->link) + return -EPERM; + + priv->ops->write(priv, reg, val); + return 0; +} + +static const struct regmap_range qca8337_readable_ranges[] = { + regmap_reg_range(0x0000, 0x00e4), /* Global control registers */ + regmap_reg_range(0x0100, 0x0168), /* EEE control registers */ + regmap_reg_range(0x0200, 0x0270), /* Parser control registers */ + regmap_reg_range(0x0400, 0x0454), /* ACL control registers */ + regmap_reg_range(0x0600, 0x0718), /* Lookup control registers */ + regmap_reg_range(0x0800, 0x0b70), /* QM control registers */ + regmap_reg_range(0x0c00, 0x0c80), /* PKT edit control registers */ + regmap_reg_range(0x0e00, 0x0e98), /* L3 */ + regmap_reg_range(0x1000, 0x10ac), /* MIB - Port0 */ + regmap_reg_range(0x1100, 0x11ac), /* MIB - Port1 */ + regmap_reg_range(0x1200, 0x12ac), /* MIB - Port2 */ + regmap_reg_range(0x1300, 0x13ac), /* MIB - Port3 */ + regmap_reg_range(0x1400, 0x14ac), /* MIB - Port4 */ + regmap_reg_range(0x1500, 0x15ac), /* MIB - Port5 */ + regmap_reg_range(0x1600, 0x16ac), /* MIB - Port6 */ +}; + +static const struct regmap_access_table qca8337_readable_table = { + .yes_ranges = qca8337_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(qca8337_readable_ranges), +}; + +static struct regmap_config qca8337_regmap_config = { + .reg_bits = 16, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x16ac, /* end MIB - Port6 range */ + .reg_read = qca8337_regmap_read, + .reg_write = qca8337_regmap_write, + .rd_table = &qca8337_readable_table, +}; + +static int qca8337_config_init(struct phy_device *phydev) +{ + struct qca8337_priv *priv = phydev->priv; + int ret = 0; + + /*Software reset*/ + priv->ops->reset_switch(priv); + /* Add delay to settle reset */ + usleep_range(100, 200); + + ret = priv->ops->hw_init(priv); + if (ret) + return ret; + + qca8337_reg_init_lan(priv); + return 0; +} + +static struct qca8337_switch_ops switch_ops = { + .hw_init = qca8337_hw_init, + .reset_switch = qca8337_reset_switch, + .read = qca8337_read, + .write = qca8337_write, +}; + +static int qca8337_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct qca8337_priv *priv = NULL; + u32 val = 0; + u16 id = 0; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->phy = phydev; + priv->dev = &phydev->mdio.dev; + priv->cpu_port = QCA8337_CPU_PORT; + priv->vlans = QCA8337_MAX_VLANS; + priv->ports = QCA8337_NUM_PORTS; + priv->num_phy = QCA8337_NUM_PHYS; + priv->ops = &switch_ops; + + /* Setup the register mapping */ + priv->regmap = devm_regmap_init(priv->dev, NULL, priv, + &qca8337_regmap_config); + if (IS_ERR(priv->regmap)) + pr_warn("regmap initialization failed\n"); + + /* read the switches ID register */ + val = qca8337_read(priv, QCA8337_REG_MASK_CTRL); + id = val & (QCA8337_CTRL_REVISION | QCA8337_CTRL_VERSION); + + priv->chip_ver = (id & QCA8337_CTRL_VERSION) >> QCA8337_CTRL_VERSION_S; + priv->chip_rev = (id & QCA8337_CTRL_REVISION); + + if (priv->chip_ver != QCA8337_ID_QCA8337) { + dev_err(dev, "qca8337: unknown Atheros device\n"); + dev_err(dev, "[ver=%d, rev=%d, phy_id=%04x%04x]\n", + priv->chip_ver, priv->chip_rev, + mdiobus_read(priv->phy->mdio.bus, priv->phy->drv->phy_id, 2), + mdiobus_read(priv->phy->mdio.bus, priv->phy->drv->phy_id, 3)); + + return -ENODEV; + } + + dev_dbg(dev, "qca8337: Switch probed successfully "); + dev_dbg(dev, "[ver=%d, rev=%d, phy_id=%04x%04x]\n", + priv->chip_ver, priv->chip_rev, + mdiobus_read(priv->phy->mdio.bus, priv->phy->drv->phy_id, 2), + mdiobus_read(priv->phy->mdio.bus, priv->phy->drv->phy_id, 3)); + + phydev->priv = priv; + return 0; +} + +static void qca8337_remove(struct phy_device *phydev) +{ + struct qca8337_priv *priv = phydev->priv; + + if (!priv) + return; +} + +static struct phy_driver qca8337_driver = { + .phy_id = QCA8337_PHY_ID, + .name = "Atheros QCA8337", + .phy_id_mask = 0xffffffef, + .probe = qca8337_probe, + .config_init = qca8337_config_init, + .features = PHY_GBIT_FEATURES, + .flags = PHY_IS_INTERNAL, + .config_aneg = qca8337_config_aneg, + .read_status = qca8337_read_status, + .aneg_done = qca8337_aneg_done, + .remove = qca8337_remove, +}; + +static int __init qca8337_init(void) +{ + return phy_driver_register(&qca8337_driver, THIS_MODULE); +} + +static void __exit qca8337_exit(void) +{ + phy_driver_unregister(&qca8337_driver); +} + +module_init(qca8337_init); +module_exit(qca8337_exit); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:qca8337"); diff --git a/include/linux/qca8337.h b/include/linux/qca8337.h new file mode 100644 index 000000000000..9c4f256f83c0 --- /dev/null +++ b/include/linux/qca8337.h @@ -0,0 +1,313 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __QCA8337_H__ +#define __QCA8337_H__ + +#define BITS(_s, _n) (((1UL << (_n)) - 1) << (_s)) + +#define QCA8337_PHY_ID 0x004dd036 +#define ATH8030_PHY_ID 0x004dd076 +#define ATH8031_PHY_ID 0x004dd074 +#define ATH8035_PHY_ID 0x004dd072 +#define QCA8337_ID_QCA8337 0x13 +#define QCA8337_NUM_PORTS 7 +/* Make sure that port0 is the cpu port */ +#define QCA8337_CPU_PORT 0 +/* size of the vlan table */ +#define QCA8337_MAX_VLANS 128 +#define QCA8337_NUM_PHYS 5 + +#define ADVERTISE_MULTI_PORT_PREFER 0x0400 + +#define QCA8337_AT803X_INTR_ENABLE 0x12 +#define QCA8337_AT803X_INTR_STATUS 0x13 +#define QCA8337_AT803X_SMART_SPEED 0x14 +#define QCA8337_AT803X_LED_CONTROL 0x18 +#define QCA8337_AT803X_WOL_ENABLE 0x01 +#define QCA8337_AT803X_DEVICE_ADDR 0x03 +#define QCA8337_AT803X_LOC_MAC_ADDR_0_15_OFFSET 0x804C +#define QCA8337_AT803X_LOC_MAC_ADDR_16_31_OFFSET 0x804B +#define QCA8337_AT803X_LOC_MAC_ADDR_32_47_OFFSET 0x804A +#define QCA8337_AT803X_MMD_ACCESS_CONTROL 0x0D +#define QCA8337_AT803X_MMD_ACCESS_CONTROL_DATA 0x0E +#define QCA8337_AT803X_FUNC_DATA 0x4003 +#define QCA8337_AT803X_INER 0x0012 +#define QCA8337_AT803X_INER_INIT 0xec00 +#define QCA8337_AT803X_INSR 0x0013 +#define QCA8337_AT803X_DEBUG_ADDR 0x1D +#define QCA8337_AT803X_DEBUG_DATA 0x1E +#define QCA8337_AT803X_DEBUG_SYSTEM_MODE_CTRL 0x05 +#define QCA8337_AT803X_DEBUG_RGMII_TX_CLK_DLY BIT(8) + +/* MASK_CTRL */ +#define QCA8337_REG_MASK_CTRL 0x0000 +#define QCA8337_CTRL_REVISION BITS(0, 8) +#define QCA8337_CTRL_REVISION_S 0 +#define QCA8337_CTRL_VERSION BITS(8, 8) +#define QCA8337_CTRL_VERSION_S 8 +#define QCA8337_CTRL_RESET BIT(31) + +/* PORT0/1_PAD_CTRL */ +#define QCA8337_REG_PAD0_CTRL 0x004 +#define QCA8337_REG_PAD5_CTRL 0x008 +#define QCA8337_REG_PAD6_CTRL 0x00c +#define QCA8337_PAD_MAC_MII_RXCLK_SEL BIT(0) +#define QCA8337_PAD_MAC_MII_TXCLK_SEL BIT(1) +#define QCA8337_PAD_MAC_MII_EN BIT(2) +#define QCA8337_PAD_MAC_GMII_RXCLK_SEL BIT(4) +#define QCA8337_PAD_MAC_GMII_TXCLK_SEL BIT(5) +#define QCA8337_PAD_MAC_GMII_EN BIT(6) +#define QCA8337_PAD_SGMII_EN BIT(7) +#define QCA8337_PAD_PHY_MII_RXCLK_SEL BIT(8) +#define QCA8337_PAD_PHY_MII_TXCLK_SEL BIT(9) +#define QCA8337_PAD_PHY_MII_EN BIT(10) +#define QCA8337_PAD_PHY_GMII_PIPE_RXCLK_SEL BIT(11) +#define QCA8337_PAD_PHY_GMII_RXCLK_SEL BIT(12) +#define QCA8337_PAD_PHY_GMII_TXCLK_SEL BIT(13) +#define QCA8337_PAD_PHY_GMII_EN BIT(14) +#define QCA8337_PAD_PHYX_GMII_EN BIT(16) +#define QCA8337_PAD_PHYX_RGMII_EN BIT(17) +#define QCA8337_PAD_PHYX_MII_EN BIT(18) +#define QCA8337_PAD_RGMII_RXCLK_DELAY_SEL BITS(20, 2) +#define QCA8337_PAD_RGMII_RXCLK_DELAY_SEL_S 20 +#define QCA8337_PAD_RGMII_TXCLK_DELAY_SEL BITS(22, 2) +#define QCA8337_PAD_RGMII_TXCLK_DELAY_SEL_S 22 +#define QCA8337_PAD_RGMII_RXCLK_DELAY_EN BIT(24) +#define QCA8337_PAD_RGMII_TXCLK_DELAY_EN BIT(25) +#define QCA8337_PAD_RGMII_EN BIT(26) + +/* PORT_STATUS */ +#define QCA8337_REG_PORT_STATUS(_i) (0x07c + (_i) * 4) +#define QCA8337_PORT_STATUS_SPEED BITS(0, 2) +#define QCA8337_PORT_STATUS_SPEED_S 0 +#define QCA8337_PORT_STATUS_TXMAC BIT(2) +#define QCA8337_PORT_STATUS_RXMAC BIT(3) +#define QCA8337_PORT_STATUS_TXFLOW BIT(4) +#define QCA8337_PORT_STATUS_RXFLOW BIT(5) +#define QCA8337_PORT_STATUS_DUPLEX BIT(6) +#define QCA8337_PORT_STATUS_LINK_UP BIT(8) +#define QCA8337_PORT_STATUS_LINK_AUTO BIT(9) +#define QCA8337_PORT_STATUS_LINK_PAUSE BIT(10) + +/* GLOBAL_FW_CTRL0 */ +#define QCA8337_REG_GLOBAL_FW_CTRL0 0x620 +#define QCA8337_GLOBAL_FW_CTRL0_CPU_PORT_EN BIT(10) + +/* GLOBAL_FW_CTRL1 */ +#define QCA8337_REG_GLOBAL_FW_CTRL1 0x624 +#define QCA8337_IGMP_JN_L_DP_SH 24 +#define QCA8337_BROAD_DP_SHIFT 16 +#define QCA8337_MULTI_FLOOD_DP_SH 8 +#define QCA8337_UNI_FLOOD_DP_SHIFT 0 +#define QCA8337_IGMP_JOIN_LEAVE_DPALL (0x7f << QCA8337_IGMP_JN_L_DP_SH) +#define QCA8337_BROAD_DPALL (0x7f << QCA8337_BROAD_DP_SHIFT) +#define QCA8337_MULTI_FLOOD_DPALL (0x7f << QCA8337_MULTI_FLOOD_DP_SH) +#define QCA8337_UNI_FLOOD_DPALL (0x7f << QCA8337_UNI_FLOOD_DP_SHIFT) + +/* PWS_REG (POWER_ON_STRIP) */ +#define QCA8337_REG_POWER_ON_STRIP 0x010 +#define QCA8337_REG_POS_VAL 0x261320 +#define QCA8337_PWS_POWER_ON_SEL BIT(31) +#define QCA8337_PWS_LED_OPEN_EN BIT(24) +#define QCA8337_PWS_SERDES_AEN BIT(7) + +/* MAC_PWR_SEL*/ +#define QCA8337_MAC_PWR_SEL 0x0e4 +#define QCA8337_MAC_PWR_SEL_VAL 0xaa545 + +/* SGMII_CTRL */ +#define QCA8337_SGMII_CTRL_REG 0x0e0 +#define QCA8337_SGMII_CTRL_VAL 0xc74164de +#define QCA8337_SGMII_CTRL_MODE_CTRL BITS(22, 2) +#define QCA8337_SGMII_CTRL_MODE_CTRL_S 22 +#define QCA8337_SGMII_EN_LCKDT BIT(0) +#define QCA8337_SGMII_EN_PLL BIT(1) +#define QCA8337_SGMII_EN_RX BIT(2) +#define QCA8337_SGMII_EN_TX BIT(3) +#define QCA8337_SGMII_EN_SD BIT(4) +#define QCA8337_SGMII_BW_HIGH BIT(6) +#define QCA8337_SGMII_SEL_CLK125M BIT(7) +#define QCA8337_SGMII_TXDR_CTRL_600mV BIT(10) +#define QCA8337_SGMII_CDR_BW_8 BIT(13) +#define QCA8337_SGMII_DIS_AUTO_LPI_25M BIT(16) +#define QCA8337_SGMII_MODE_CTRL_SGMII_PHY BIT(22) +#define QCA8337_SGMII_PAUSE_SG_TX_EN_25M BIT(24) +#define QCA8337_SGMII_ASYM_PAUSE_25M BIT(25) +#define QCA8337_SGMII_PAUSE_25M BIT(26) +#define QCA8337_SGMII_HALF_DUPLEX_25M BIT(30) +#define QCA8337_SGMII_FULL_DUPLEX_25M BIT(31) + +/* PORT_LOOKUP_CTRL */ +#define QCA8337_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc) +#define QCA8337_PORT_LOOKUP_MEMBER BITS(0, 7) +#define QCA8337_PORT_LOOKUP_IN_MODE BITS(8, 2) +#define QCA8337_PORT_LOOKUP_IN_MODE_S 8 +#define QCA8337_PORT_LOOKUP_STATE BITS(16, 3) +#define QCA8337_PORT_LOOKUP_STATE_S 16 +#define QCA8337_PORT_LOOKUP_LEARN BIT(20) + +/* PORT_VLAN_CTRL0 */ +#define QCA8337_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8) +#define QCA8337_PORT_VLAN0_DEF_SVID BITS(0, 12) +#define QCA8337_PORT_VLAN0_DEF_SVID_S 0 +#define QCA8337_PORT_VLAN0_DEF_CVID BITS(16, 12) +#define QCA8337_PORT_VLAN0_DEF_CVID_S 16 + +/* PORT_VLAN_CTRL1 */ +#define QCA8337_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8) +#define QCA8337_PORT_VLAN1_PORT_VLAN_PROP BIT(6) +#define QCA8337_PORT_VLAN1_OUT_MODE BITS(12, 2) +#define QCA8337_PORT_VLAN1_OUT_MODE_S 12 +#define QCA8337_PORT_VLAN1_OUT_MODE_UNMOD 0 +#define QCA8337_PORT_VLAN1_OUT_MODE_UNTAG 1 +#define QCA8337_PORT_VLAN1_OUT_MODE_TAG 2 +#define QCA8337_PORT_VLAN1_OUT_MODE_UNTOUCH 3 + +/* MODULE_EN */ +#define QCA8337_REG_MODULE_EN 0x030 +#define QCA8337_MODULE_EN_MIB BIT(0) + +/* MIB */ +#define QCA8337_REG_MIB 0x034 +#define QCA8337_MIB_FLUSH BIT(24) +#define QCA8337_MIB_CPU_KEEP BIT(20) +#define QCA8337_MIB_BUSY BIT(17) + +/* PORT_HEADER_CTRL */ +#define QCA8337_REG_PORT_HEADER(_i) (0x09c + (_i) * 4) +#define QCA8337_PORT_HDR_CTRL_RX_S 2 +#define QCA8337_PORT_HDR_CTRL_TX_S 0 +#define QCA8337_PORT_HDR_CTRL_ALL 2 + +/* EEE_CTRL */ +#define QCA8337_REG_EEE_CTRL 0x100 +#define QCA8337_EEE_CTRL_DISABLE 0x0 /*EEE disable*/ + +/* VTU_FUNC_REG0 */ +#define QCA8337_REG_VTU_FUNC0 0x0610 +#define QCA8337_VTU_FUNC0_EG_MODE BITS(4, 14) +#define QCA8337_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2) +#define QCA8337_VTU_FUNC0_EG_MODE_KEEP 0 +#define QCA8337_VTU_FUNC0_EG_MODE_UNTAG 1 +#define QCA8337_VTU_FUNC0_EG_MODE_TAG 2 +#define QCA8337_VTU_FUNC0_EG_MODE_NOT 3 +#define QCA8337_VTU_FUNC0_IVL BIT(19) +#define QCA8337_VTU_FUNC0_VALID BIT(20) + +/* VTU_FUNC_REG1 */ +#define QCA8337_REG_VTU_FUNC1 0x0614 +#define QCA8337_VTU_FUNC1_OP BITS(0, 3) +#define QCA8337_VTU_FUNC1_OP_NOOP 0 +#define QCA8337_VTU_FUNC1_OP_FLUSH 1 +#define QCA8337_VTU_FUNC1_OP_LOAD 2 +#define QCA8337_VTU_FUNC1_OP_PURGE 3 +#define QCA8337_VTU_FUNC1_OP_REMOVE_PORT 4 +#define QCA8337_VTU_FUNC1_OP_GET_NEXT 5 +#define QCA8337_VTU_FUNC1_OP_GET_ONE 6 +#define QCA8337_VTU_FUNC1_FULL BIT(4) +#define QCA8337_VTU_FUNC1_PORT BIT(8, 4) +#define QCA8337_VTU_FUNC1_PORT_S 8 +#define QCA8337_VTU_FUNC1_VID BIT(16, 12) +#define QCA8337_VTU_FUNC1_VID_S 16 +#define QCA8337_VTU_FUNC1_BUSY BIT(31) + +#define QCA8337_REG_ATU_FUNC 0x60c +#define QCA8337_ATU_FUNC_BUSY BIT(31) +#define QCA8337_ATU_FUNC_OP_GET_NEXT 0x6 +#define QCA8337_REG_ATU_DATA0 0x600 +#define QCA8337_REG_ATU_DATA1 0x604 +#define QCA8337_REG_ATU_DATA2 0x608 + +#define QCA8337_GLOBAL_INT1 0x0024 +#define QCA8337_GLOBAL_INT1_MASK 0x002c + +/* port speed */ +enum { + QCA8337_PORT_SPEED_10M = 0, + QCA8337_PORT_SPEED_100M = 1, + QCA8337_PORT_SPEED_1000M = 2, + QCA8337_PORT_SPEED_ERR = 3, +}; + +/* ingress 802.1q mode */ +enum { + QCA8337_IN_PORT_ONLY = 0, + QCA8337_IN_PORT_FALLBACK = 1, + QCA8337_IN_VLAN_ONLY = 2, + QCA8337_IN_SECURE = 3 +}; + +/* egress 802.1q mode */ +enum { + QCA8337_OUT_KEEP = 0, + QCA8337_OUT_STRIP_VLAN = 1, + QCA8337_OUT_ADD_VLAN = 2 +}; + +/* port forwarding state */ +enum { + QCA8337_PORT_STATE_DISABLED = 0, + QCA8337_PORT_STATE_BLOCK = 1, + QCA8337_PORT_STATE_LISTEN = 2, + QCA8337_PORT_STATE_LEARN = 3, + QCA8337_PORT_STATE_FORWARD = 4 +}; + +struct qca8337_priv; + +struct qca8337_switch_ops { + int (*hw_init)(struct qca8337_priv *priv); + void (*reset_switch)(struct qca8337_priv *priv); + + /* Switch internal register read/write function */ + u32 (*read)(struct qca8337_priv *priv, u32 reg); + void (*write)(struct qca8337_priv *priv, u32 reg, u32 val); +}; + +struct port_link_info { + bool link; + int speed; + int duplex; + int aneg; + int rx_flow; + int tx_flow; +}; + +struct qca8337_priv { + struct device *dev; + struct phy_device *phy; + u8 chip_ver; + u8 chip_rev; + u8 cpu_port; + u8 ports; + u16 vlans; + u8 num_phy; + u32 old_port_status; + char buf[2048]; + + struct qca8337_switch_ops *ops; + struct regmap *regmap; +}; + +struct qca8337_mib_desc { + unsigned int size; + unsigned int offset; + const char *name; +}; + +void qca8337_check(void); + +u32 qca8337_read(struct qca8337_priv *priv, u32 reg); +void qca8337_write(struct qca8337_priv *priv, u32 reg, u32 val); +#endif /*__QCA8337_H__*/