From b1f21329d4358e74864f17eedd0f887e5ad2a816 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Mon, 27 Dec 2021 15:11:18 +0100 Subject: [PATCH] ipq40xx: add DSA switch driver Qualcomm IPQ40xx SoC-s have a variant of QCA8337N switch built-in. It shares most of the stuff with its external counterpart, however it is modified for the SoC. Namely, it doesn't have second CPU port (Port 6), so it has 6 ports instead of 7. It also has no built-in PHY-s but rather requires external PSGMII based companion PHY-s (QCA8072 and QCA8075) for which it first needs to carry out calibration before using them. PSGMII has a SoC built-in PHY that is used to connect to the PHY-s which unfortunately requires some magic values as the datasheet doesnt document the bits that are being set or the register at all. Since its built-in it is MMIO like other peripherals and doesn't have its own MDIO bus but depends on the SoC provided one. CPU connection is at Port 0 and it uses some kind of a internal connection and no traditional RGMII/SGMII. It also doesn't use in-band tagging like other qca8k switches so a shinfo based tagger is used. This is based on the current OpenWrt qca8k version that has been imported from generic target. Signed-off-by: Robert Marko --- .../files/drivers/net/dsa/qca/qca8k-ipq4019.c | 1457 ++++------------- .../files/drivers/net/dsa/qca/qca8k-ipq4019.h | 102 +- ...comm-IPQ4019-built-in-switch-support.patch | 53 + .../706-arm-dts-ipq4019-add-switch-node.patch | 98 ++ ...707-dt-bindings-net-add-QCA807x-PHY.patch} | 0 ...-net-phy-Add-Qualcom-QCA807x-driver.patch} | 0 ...-arm-dts-ipq4019-QCA807x-properties.patch} | 2 +- ...comm-IPQ4019-built-in-switch-support.patch | 56 + .../706-arm-dts-ipq4019-add-switch-node.patch | 98 ++ ...707-dt-bindings-net-add-QCA807x-PHY.patch} | 0 ...-net-phy-Add-Qualcom-QCA807x-driver.patch} | 0 ...-arm-dts-ipq4019-QCA807x-properties.patch} | 2 +- 12 files changed, 678 insertions(+), 1190 deletions(-) create mode 100644 target/linux/ipq40xx/patches-5.10/705-net-dsa-add-Qualcomm-IPQ4019-built-in-switch-support.patch create mode 100644 target/linux/ipq40xx/patches-5.10/706-arm-dts-ipq4019-add-switch-node.patch rename target/linux/ipq40xx/patches-5.10/{706-dt-bindings-net-add-QCA807x-PHY.patch => 707-dt-bindings-net-add-QCA807x-PHY.patch} (100%) rename target/linux/ipq40xx/patches-5.10/{707-net-phy-Add-Qualcom-QCA807x-driver.patch => 708-net-phy-Add-Qualcom-QCA807x-driver.patch} (100%) rename target/linux/ipq40xx/patches-5.10/{708-arm-dts-ipq4019-QCA807x-properties.patch => 709-arm-dts-ipq4019-QCA807x-properties.patch} (98%) create mode 100644 target/linux/ipq40xx/patches-5.15/705-net-dsa-add-Qualcomm-IPQ4019-built-in-switch-support.patch create mode 100644 target/linux/ipq40xx/patches-5.15/706-arm-dts-ipq4019-add-switch-node.patch rename target/linux/ipq40xx/patches-5.15/{706-dt-bindings-net-add-QCA807x-PHY.patch => 707-dt-bindings-net-add-QCA807x-PHY.patch} (100%) rename target/linux/ipq40xx/patches-5.15/{707-net-phy-Add-Qualcom-QCA807x-driver.patch => 708-net-phy-Add-Qualcom-QCA807x-driver.patch} (100%) rename target/linux/ipq40xx/patches-5.15/{708-arm-dts-ipq4019-QCA807x-properties.patch => 709-arm-dts-ipq4019-QCA807x-properties.patch} (98%) diff --git a/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.c b/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.c index fc8579d952..4d79426205 100644 --- a/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.c +++ b/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.c @@ -1,25 +1,28 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2009 Felix Fietkau - * Copyright (C) 2011-2012 Gabor Juhos + * Copyright (C) 2011-2012, 2020-2021 Gabor Juhos * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved. * Copyright (c) 2016 John Crispin + * Copyright (c) 2021 Robert Marko */ +#include +#include +#include +#include +#include #include -#include #include -#include -#include #include +#include #include -#include -#include +#include #include -#include -#include +#include +#include -#include "qca8k.h" +#include "qca8k-ipq4019.h" #define MIB_DESC(_s, _o, _n) \ { \ @@ -68,186 +71,38 @@ static const struct qca8k_mib_desc ar8327_mib[] = { MIB_DESC(1, 0x9c, "TxExcDefer"), MIB_DESC(1, 0xa0, "TxDefer"), MIB_DESC(1, 0xa4, "TxLateCol"), + MIB_DESC(1, 0xa8, "RXUnicast"), + MIB_DESC(1, 0xac, "TXunicast"), }; -/* The 32bit switch registers are accessed indirectly. To achieve this we need - * to set the page of the register. Track the last page that was set to reduce - * mdio writes - */ -static u16 qca8k_current_page = 0xffff; - -static void -qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) -{ - regaddr >>= 1; - *r1 = regaddr & 0x1e; - - regaddr >>= 5; - *r2 = regaddr & 0x7; - - regaddr >>= 3; - *page = regaddr & 0x3ff; -} - -static int -qca8k_mii_read32(struct mii_bus *bus, int phy_id, u32 regnum, u32 *val) -{ - int ret; - - ret = bus->read(bus, phy_id, regnum); - if (ret >= 0) { - *val = ret; - ret = bus->read(bus, phy_id, regnum + 1); - *val |= ret << 16; - } - - if (ret < 0) { - dev_err_ratelimited(&bus->dev, - "failed to read qca8k 32bit register\n"); - *val = 0; - return ret; - } - - return 0; -} - -static void -qca8k_mii_write32(struct mii_bus *bus, int phy_id, u32 regnum, u32 val) -{ - u16 lo, hi; - int ret; - - lo = val & 0xffff; - hi = (u16)(val >> 16); - - ret = bus->write(bus, phy_id, regnum, lo); - if (ret >= 0) - ret = bus->write(bus, phy_id, regnum + 1, hi); - if (ret < 0) - dev_err_ratelimited(&bus->dev, - "failed to write qca8k 32bit register\n"); -} - -static int -qca8k_set_page(struct mii_bus *bus, u16 page) -{ - int ret; - - if (page == qca8k_current_page) - return 0; - - ret = bus->write(bus, 0x18, 0, page); - if (ret < 0) { - dev_err_ratelimited(&bus->dev, - "failed to set qca8k page\n"); - return ret; - } - - qca8k_current_page = page; - usleep_range(1000, 2000); - return 0; -} - static int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val) { - struct mii_bus *bus = priv->bus; - u16 r1, r2, page; - int ret; - - qca8k_split_addr(reg, &r1, &r2, &page); - - mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); - - ret = qca8k_set_page(bus, page); - if (ret < 0) - goto exit; - - ret = qca8k_mii_read32(bus, 0x10 | r2, r1, val); - -exit: - mutex_unlock(&bus->mdio_lock); - return ret; + return regmap_read(priv->regmap, reg, val); } static int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val) { - struct mii_bus *bus = priv->bus; - u16 r1, r2, page; - int ret; - - qca8k_split_addr(reg, &r1, &r2, &page); - - mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); - - ret = qca8k_set_page(bus, page); - if (ret < 0) - goto exit; - - qca8k_mii_write32(bus, 0x10 | r2, r1, val); - -exit: - mutex_unlock(&bus->mdio_lock); - return ret; + return regmap_write(priv->regmap, reg, val); } static int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val) { - struct mii_bus *bus = priv->bus; - u16 r1, r2, page; - u32 val; - int ret; - - qca8k_split_addr(reg, &r1, &r2, &page); - - mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); - - ret = qca8k_set_page(bus, page); - if (ret < 0) - goto exit; - - ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val); - if (ret < 0) - goto exit; - - val &= ~mask; - val |= write_val; - qca8k_mii_write32(bus, 0x10 | r2, r1, val); - -exit: - mutex_unlock(&bus->mdio_lock); - - return ret; + return regmap_update_bits(priv->regmap, reg, mask, write_val); } static int qca8k_reg_set(struct qca8k_priv *priv, u32 reg, u32 val) { - return qca8k_rmw(priv, reg, 0, val); + return regmap_set_bits(priv->regmap, reg, val); } static int qca8k_reg_clear(struct qca8k_priv *priv, u32 reg, u32 val) { - return qca8k_rmw(priv, reg, val, 0); -} - -static int -qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val) -{ - struct qca8k_priv *priv = (struct qca8k_priv *)ctx; - - return qca8k_read(priv, reg, val); -} - -static int -qca8k_regmap_write(void *ctx, uint32_t reg, uint32_t val) -{ - struct qca8k_priv *priv = (struct qca8k_priv *)ctx; - - return qca8k_write(priv, reg, val); + return regmap_clear_bits(priv->regmap, reg, val); } static const struct regmap_range qca8k_readable_ranges[] = { @@ -274,33 +129,31 @@ static const struct regmap_access_table qca8k_readable_table = { .n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges), }; -static struct regmap_config qca8k_regmap_config = { - .reg_bits = 16, +static struct regmap_config qca8k_ipq4019_regmap_config = { + .reg_bits = 32, .val_bits = 32, .reg_stride = 4, .max_register = 0x16ac, /* end MIB - Port6 range */ - .reg_read = qca8k_regmap_read, - .reg_write = qca8k_regmap_write, .rd_table = &qca8k_readable_table, }; +static struct regmap_config qca8k_ipq4019_psgmii_phy_regmap_config = { + .name = "psgmii-phy", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x7fc, +}; + static int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask) { - int ret, ret1; u32 val; - ret = read_poll_timeout(qca8k_read, ret1, !(val & mask), - 0, QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false, - priv, reg, &val); - - /* Check if qca8k_read has failed for a different reason - * before returning -ETIMEDOUT - */ - if (ret < 0 && ret1 < 0) - return ret1; - - return ret; + return regmap_read_poll_timeout(priv->regmap, reg, val, + !(val & mask), + 0, + QCA8K_BUSY_WAIT_TIMEOUT); } static int @@ -595,8 +448,11 @@ qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable) { u32 mask = QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC; - /* Port 0 and 6 have no internal PHY */ - if (port > 0 && port < 6) + /* Port 0 is internally connected to the CPU + * TODO: Probably check for RGMII as well if it doesnt work + * in RGMII mode. + */ + if (port > QCA8K_CPU_PORT) mask |= QCA8K_PORT_STATUS_LINK_AUTO; if (enable) @@ -605,467 +461,56 @@ qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable) qca8k_reg_clear(priv, QCA8K_REG_PORT_STATUS(port), mask); } -static u32 -qca8k_port_to_phy(int port) -{ - /* From Andrew Lunn: - * Port 0 has no internal phy. - * Port 1 has an internal PHY at MDIO address 0. - * Port 2 has an internal PHY at MDIO address 1. - * ... - * Port 5 has an internal PHY at MDIO address 4. - * Port 6 has no internal PHY. - */ - - return port - 1; -} - -static int -qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask) -{ - u16 r1, r2, page; - u32 val; - int ret, ret1; - - qca8k_split_addr(reg, &r1, &r2, &page); - - ret = read_poll_timeout(qca8k_mii_read32, ret1, !(val & mask), 0, - QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false, - bus, 0x10 | r2, r1, &val); - - /* Check if qca8k_read has failed for a different reason - * before returnting -ETIMEDOUT - */ - if (ret < 0 && ret1 < 0) - return ret1; - - return ret; -} - -static int -qca8k_mdio_write(struct mii_bus *bus, int phy, int regnum, u16 data) -{ - u16 r1, r2, page; - u32 val; - int ret; - - if (regnum >= QCA8K_MDIO_MASTER_MAX_REG) - return -EINVAL; - - val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN | - QCA8K_MDIO_MASTER_WRITE | QCA8K_MDIO_MASTER_PHY_ADDR(phy) | - QCA8K_MDIO_MASTER_REG_ADDR(regnum) | - QCA8K_MDIO_MASTER_DATA(data); - - qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page); - - mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); - - ret = qca8k_set_page(bus, page); - if (ret) - goto exit; - - qca8k_mii_write32(bus, 0x10 | r2, r1, val); - - ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL, - QCA8K_MDIO_MASTER_BUSY); - -exit: - /* even if the busy_wait timeouts try to clear the MASTER_EN */ - qca8k_mii_write32(bus, 0x10 | r2, r1, 0); - - mutex_unlock(&bus->mdio_lock); - - return ret; -} - -static int -qca8k_mdio_read(struct mii_bus *bus, int phy, int regnum) -{ - u16 r1, r2, page; - u32 val; - int ret; - - if (regnum >= QCA8K_MDIO_MASTER_MAX_REG) - return -EINVAL; - - val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN | - QCA8K_MDIO_MASTER_READ | QCA8K_MDIO_MASTER_PHY_ADDR(phy) | - QCA8K_MDIO_MASTER_REG_ADDR(regnum); - - qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page); - - mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED); - - ret = qca8k_set_page(bus, page); - if (ret) - goto exit; - - qca8k_mii_write32(bus, 0x10 | r2, r1, val); - - ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL, - QCA8K_MDIO_MASTER_BUSY); - if (ret) - goto exit; - - ret = qca8k_mii_read32(bus, 0x10 | r2, r1, &val); - -exit: - /* even if the busy_wait timeouts try to clear the MASTER_EN */ - qca8k_mii_write32(bus, 0x10 | r2, r1, 0); - - mutex_unlock(&bus->mdio_lock); - - if (ret >= 0) - ret = val & QCA8K_MDIO_MASTER_DATA_MASK; - - return ret; -} - -static int -qca8k_internal_mdio_write(struct mii_bus *slave_bus, int phy, int regnum, u16 data) -{ - struct qca8k_priv *priv = slave_bus->priv; - struct mii_bus *bus = priv->bus; - - return qca8k_mdio_write(bus, phy, regnum, data); -} - -static int -qca8k_internal_mdio_read(struct mii_bus *slave_bus, int phy, int regnum) -{ - struct qca8k_priv *priv = slave_bus->priv; - struct mii_bus *bus = priv->bus; - - return qca8k_mdio_read(bus, phy, regnum); -} - -static int -qca8k_phy_write(struct dsa_switch *ds, int port, int regnum, u16 data) -{ - struct qca8k_priv *priv = ds->priv; - - /* Check if the legacy mapping should be used and the - * port is not correctly mapped to the right PHY in the - * devicetree - */ - if (priv->legacy_phy_port_mapping) - port = qca8k_port_to_phy(port) % PHY_MAX_ADDR; - - return qca8k_mdio_write(priv->bus, port, regnum, data); -} - static int -qca8k_phy_read(struct dsa_switch *ds, int port, int regnum) +qca8k_setup_port(struct dsa_switch *ds, int port) { - struct qca8k_priv *priv = ds->priv; - int ret; - - /* Check if the legacy mapping should be used and the - * port is not correctly mapped to the right PHY in the - * devicetree - */ - if (priv->legacy_phy_port_mapping) - port = qca8k_port_to_phy(port) % PHY_MAX_ADDR; - - ret = qca8k_mdio_read(priv->bus, port, regnum); - - if (ret < 0) - return 0xffff; - - return ret; -} - -static int -qca8k_mdio_register(struct qca8k_priv *priv, struct device_node *mdio) -{ - struct dsa_switch *ds = priv->ds; - struct mii_bus *bus; - - bus = devm_mdiobus_alloc(ds->dev); - - if (!bus) - return -ENOMEM; - - bus->priv = (void *)priv; - bus->name = "qca8k slave mii"; - bus->read = qca8k_internal_mdio_read; - bus->write = qca8k_internal_mdio_write; - snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d", - ds->index); - - bus->parent = ds->dev; - bus->phy_mask = ~ds->phys_mii_mask; - - ds->slave_mii_bus = bus; - - return devm_of_mdiobus_register(priv->dev, bus, mdio); -} - -static int -qca8k_setup_mdio_bus(struct qca8k_priv *priv) -{ - u32 internal_mdio_mask = 0, external_mdio_mask = 0, reg; - struct device_node *ports, *port, *mdio; - phy_interface_t mode; - int err; - - ports = of_get_child_by_name(priv->dev->of_node, "ports"); - if (!ports) - ports = of_get_child_by_name(priv->dev->of_node, "ethernet-ports"); - - if (!ports) - return -EINVAL; - - for_each_available_child_of_node(ports, port) { - err = of_property_read_u32(port, "reg", ®); - if (err) { - of_node_put(port); - of_node_put(ports); - return err; - } - - if (!dsa_is_user_port(priv->ds, reg)) - continue; - - of_get_phy_mode(port, &mode); - - if (of_property_read_bool(port, "phy-handle") && - mode != PHY_INTERFACE_MODE_INTERNAL) - external_mdio_mask |= BIT(reg); - else - internal_mdio_mask |= BIT(reg); - } - - of_node_put(ports); - if (!external_mdio_mask && !internal_mdio_mask) { - dev_err(priv->dev, "no PHYs are defined.\n"); - return -EINVAL; - } - - /* The QCA8K_MDIO_MASTER_EN Bit, which grants access to PHYs through - * the MDIO_MASTER register also _disconnects_ the external MDC - * passthrough to the internal PHYs. It's not possible to use both - * configurations at the same time! - * - * Because this came up during the review process: - * If the external mdio-bus driver is capable magically disabling - * the QCA8K_MDIO_MASTER_EN and mutex/spin-locking out the qca8k's - * accessors for the time being, it would be possible to pull this - * off. - */ - if (!!external_mdio_mask && !!internal_mdio_mask) { - dev_err(priv->dev, "either internal or external mdio bus configuration is supported.\n"); - return -EINVAL; - } - - if (external_mdio_mask) { - /* Make sure to disable the internal mdio bus in cases - * a dt-overlay and driver reload changed the configuration - */ - - return qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL, - QCA8K_MDIO_MASTER_EN); - } - - /* Check if the devicetree declare the port:phy mapping */ - mdio = of_get_child_by_name(priv->dev->of_node, "mdio"); - if (of_device_is_available(mdio)) { - err = qca8k_mdio_register(priv, mdio); - if (err) - of_node_put(mdio); - - return err; - } - - /* If a mapping can't be found the legacy mapping is used, - * using the qca8k_port_to_phy function - */ - priv->legacy_phy_port_mapping = true; - priv->ops.phy_read = qca8k_phy_read; - priv->ops.phy_write = qca8k_phy_write; - - return 0; -} - -static int -qca8k_setup_mac_pwr_sel(struct qca8k_priv *priv) -{ - u32 mask = 0; - int ret = 0; - - /* SoC specific settings for ipq8064. - * If more device require this consider adding - * a dedicated binding. - */ - if (of_machine_is_compatible("qcom,ipq8064")) - mask |= QCA8K_MAC_PWR_RGMII0_1_8V; - - /* SoC specific settings for ipq8065 */ - if (of_machine_is_compatible("qcom,ipq8065")) - mask |= QCA8K_MAC_PWR_RGMII1_1_8V; - - if (mask) { - ret = qca8k_rmw(priv, QCA8K_REG_MAC_PWR_SEL, - QCA8K_MAC_PWR_RGMII0_1_8V | - QCA8K_MAC_PWR_RGMII1_1_8V, - mask); - } - - return ret; -} - -static int qca8k_find_cpu_port(struct dsa_switch *ds) -{ - struct qca8k_priv *priv = ds->priv; - - /* Find the connected cpu port. Valid port are 0 or 6 */ - if (dsa_is_cpu_port(ds, 0)) - return 0; - - dev_dbg(priv->dev, "port 0 is not the CPU port. Checking port 6"); - - if (dsa_is_cpu_port(ds, 6)) - return 6; - - return -EINVAL; -} - -static int -qca8k_setup_of_pws_reg(struct qca8k_priv *priv) -{ - struct device_node *node = priv->dev->of_node; - const struct qca8k_match_data *data; - u32 val = 0; + struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; int ret; - /* QCA8327 require to set to the correct mode. - * His bigger brother QCA8328 have the 172 pin layout. - * Should be applied by default but we set this just to make sure. - */ - if (priv->switch_id == QCA8K_ID_QCA8327) { - data = of_device_get_match_data(priv->dev); - - /* Set the correct package of 148 pin for QCA8327 */ - if (data->reduced_package) - val |= QCA8327_PWS_PACKAGE148_EN; - - ret = qca8k_rmw(priv, QCA8K_REG_PWS, QCA8327_PWS_PACKAGE148_EN, - val); + /* CPU port gets connected to all user ports of the switch */ + if (dsa_is_cpu_port(ds, port)) { + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(QCA8K_CPU_PORT), + QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds)); if (ret) return ret; - } - - if (of_property_read_bool(node, "qca,ignore-power-on-sel")) - val |= QCA8K_PWS_POWER_ON_SEL; - - if (of_property_read_bool(node, "qca,led-open-drain")) { - if (!(val & QCA8K_PWS_POWER_ON_SEL)) { - dev_err(priv->dev, "qca,led-open-drain require qca,ignore-power-on-sel to be set."); - return -EINVAL; - } - val |= QCA8K_PWS_LED_OPEN_EN_CSR; + /* Disable CPU ARP Auto-learning by default */ + ret = qca8k_reg_clear(priv, QCA8K_PORT_LOOKUP_CTRL(QCA8K_CPU_PORT), + QCA8K_PORT_LOOKUP_LEARN); + if (ret) + return ret; } - return qca8k_rmw(priv, QCA8K_REG_PWS, - QCA8K_PWS_LED_OPEN_EN_CSR | QCA8K_PWS_POWER_ON_SEL, - val); -} - -static int -qca8k_parse_port_config(struct qca8k_priv *priv) -{ - int port, cpu_port_index = -1, ret; - struct device_node *port_dn; - phy_interface_t mode; - struct dsa_port *dp; - u32 delay; - - /* We have 2 CPU port. Check them */ - for (port = 0; port < QCA8K_NUM_PORTS && cpu_port_index < QCA8K_NUM_CPU_PORTS; port++) { - /* Skip every other port */ - if (port != 0 && port != 6) - continue; - - dp = dsa_to_port(priv->ds, port); - port_dn = dp->dn; - cpu_port_index++; + /* Individual user ports get connected to CPU port only */ + if (dsa_is_user_port(ds, port)) { + int shift = 16 * (port % 2); - if (!of_device_is_available(port_dn)) - continue; + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), + QCA8K_PORT_LOOKUP_MEMBER, + BIT(QCA8K_CPU_PORT)); + if (ret) + return ret; - ret = of_get_phy_mode(port_dn, &mode); + /* Enable ARP Auto-learning by default */ + ret = qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(port), + QCA8K_PORT_LOOKUP_LEARN); if (ret) - continue; + return ret; - switch (mode) { - case PHY_INTERFACE_MODE_RGMII: - case PHY_INTERFACE_MODE_RGMII_ID: - case PHY_INTERFACE_MODE_RGMII_TXID: - case PHY_INTERFACE_MODE_RGMII_RXID: - case PHY_INTERFACE_MODE_SGMII: - delay = 0; - - if (!of_property_read_u32(port_dn, "tx-internal-delay-ps", &delay)) - /* Switch regs accept value in ns, convert ps to ns */ - delay = delay / 1000; - else if (mode == PHY_INTERFACE_MODE_RGMII_ID || - mode == PHY_INTERFACE_MODE_RGMII_TXID) - delay = 1; - - if (delay > QCA8K_MAX_DELAY) { - dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value"); - delay = 3; - } - - priv->ports_config.rgmii_tx_delay[cpu_port_index] = delay; - - delay = 0; - - if (!of_property_read_u32(port_dn, "rx-internal-delay-ps", &delay)) - /* Switch regs accept value in ns, convert ps to ns */ - delay = delay / 1000; - else if (mode == PHY_INTERFACE_MODE_RGMII_ID || - mode == PHY_INTERFACE_MODE_RGMII_RXID) - delay = 2; - - if (delay > QCA8K_MAX_DELAY) { - dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value"); - delay = 3; - } - - priv->ports_config.rgmii_rx_delay[cpu_port_index] = delay; - - /* Skip sgmii parsing for rgmii* mode */ - if (mode == PHY_INTERFACE_MODE_RGMII || - mode == PHY_INTERFACE_MODE_RGMII_ID || - mode == PHY_INTERFACE_MODE_RGMII_TXID || - mode == PHY_INTERFACE_MODE_RGMII_RXID) - break; - - if (of_property_read_bool(port_dn, "qca,sgmii-txclk-falling-edge")) - priv->ports_config.sgmii_tx_clk_falling_edge = true; - - if (of_property_read_bool(port_dn, "qca,sgmii-rxclk-falling-edge")) - priv->ports_config.sgmii_rx_clk_falling_edge = true; - - if (of_property_read_bool(port_dn, "qca,sgmii-enable-pll")) { - priv->ports_config.sgmii_enable_pll = true; - - if (priv->switch_id == QCA8K_ID_QCA8327) { - dev_err(priv->dev, "SGMII PLL should NOT be enabled for qca8327. Aborting enabling"); - priv->ports_config.sgmii_enable_pll = false; - } - - if (priv->switch_revision < 2) - dev_warn(priv->dev, "SGMII PLL should NOT be enabled for qca8337 with revision 2 or more."); - } + /* For port based vlans to work we need to set the + * default egress vid + */ + ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port), + 0xfff << shift, + QCA8K_PORT_VID_DEF << shift); + if (ret) + return ret; - break; - default: - continue; - } + ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port), + QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) | + QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF)); + if (ret) + return ret; } return 0; @@ -1075,40 +520,14 @@ static int qca8k_setup(struct dsa_switch *ds) { struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - int cpu_port, ret, i; - u32 mask; + int ret, i; - cpu_port = qca8k_find_cpu_port(ds); - if (cpu_port < 0) { - dev_err(priv->dev, "No cpu port configured in both cpu port0 and port6"); - return cpu_port; + /* Make sure that port 0 is the cpu port */ + if (!dsa_is_cpu_port(ds, 0)) { + dev_err(priv->dev, "port 0 is not the CPU port"); + return -EINVAL; } - /* Parse CPU port config to be later used in phy_link mac_config */ - ret = qca8k_parse_port_config(priv); - if (ret) - return ret; - - mutex_init(&priv->reg_mutex); - - /* Start by setting up the register mapping */ - priv->regmap = devm_regmap_init(ds->dev, NULL, priv, - &qca8k_regmap_config); - if (IS_ERR(priv->regmap)) - dev_warn(priv->dev, "regmap initialization failed"); - - ret = qca8k_setup_mdio_bus(priv); - if (ret) - return ret; - - ret = qca8k_setup_of_pws_reg(priv); - if (ret) - return ret; - - ret = qca8k_setup_mac_pwr_sel(priv); - if (ret) - return ret; - /* Enable CPU Port */ ret = qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0, QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN); @@ -1120,149 +539,53 @@ qca8k_setup(struct dsa_switch *ds) /* Enable MIB counters */ ret = qca8k_mib_init(priv); if (ret) - dev_warn(priv->dev, "mib init failed"); + dev_warn(priv->dev, "MIB init failed"); + + /* Enable QCA header mode on the cpu port */ + ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(QCA8K_CPU_PORT), + QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S | + QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S); + if (ret) { + dev_err(priv->dev, "failed enabling QCA header mode"); + return ret; + } - /* Initial setup of all ports */ + /* Disable forwarding by default on all ports */ for (i = 0; i < QCA8K_NUM_PORTS; i++) { - /* Disable forwarding by default on all ports */ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i), QCA8K_PORT_LOOKUP_MEMBER, 0); if (ret) return ret; - - /* Enable QCA header mode on all cpu ports */ - if (dsa_is_cpu_port(ds, i)) { - ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(i), - QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S | - QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S); - if (ret) { - dev_err(priv->dev, "failed enabling QCA header mode"); - return ret; - } - } - - /* Disable MAC by default on all user ports */ - if (dsa_is_user_port(ds, i)) - qca8k_port_set_status(priv, i, 0); } - /* Forward all unknown frames to CPU port for Linux processing - * Notice that in multi-cpu config only one port should be set - * for igmp, unknown, multicast and broadcast packet - */ + /* Disable MAC by default on all ports */ + for (i = 1; i < QCA8K_NUM_PORTS; i++) + qca8k_port_set_status(priv, i, 0); + + /* Forward all unknown frames to CPU port for Linux processing */ ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1, - BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S | - BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S | - BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S | - BIT(cpu_port) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S); + BIT(QCA8K_CPU_PORT) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S | + BIT(QCA8K_CPU_PORT) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S | + BIT(QCA8K_CPU_PORT) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S | + BIT(QCA8K_CPU_PORT) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S); if (ret) return ret; - /* Setup connection between CPU port & user ports - * Configure specific switch configuration for ports - */ + /* Setup connection between CPU port & user ports */ for (i = 0; i < QCA8K_NUM_PORTS; i++) { - /* CPU port gets connected to all user ports of the switch */ - if (dsa_is_cpu_port(ds, i)) { - ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i), - QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds)); - if (ret) - return ret; - } - - /* Individual user ports get connected to CPU port only */ - if (dsa_is_user_port(ds, i)) { - int shift = 16 * (i % 2); - - ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i), - QCA8K_PORT_LOOKUP_MEMBER, - BIT(cpu_port)); - if (ret) - return ret; - - /* Enable ARP Auto-learning by default */ - ret = qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(i), - QCA8K_PORT_LOOKUP_LEARN); - if (ret) - return ret; - - /* For port based vlans to work we need to set the - * default egress vid - */ - ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i), - 0xfff << shift, - QCA8K_PORT_VID_DEF << shift); - if (ret) - return ret; - - ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i), - QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) | - QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF)); - if (ret) - return ret; - } - - /* The port 5 of the qca8337 have some problem in flood condition. The - * original legacy driver had some specific buffer and priority settings - * for the different port suggested by the QCA switch team. Add this - * missing settings to improve switch stability under load condition. - * This problem is limited to qca8337 and other qca8k switch are not affected. - */ - if (priv->switch_id == QCA8K_ID_QCA8337) { - switch (i) { - /* The 2 CPU port and port 5 requires some different - * priority than any other ports. - */ - case 0: - case 5: - case 6: - mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) | - QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) | - QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x4) | - QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x4) | - QCA8K_PORT_HOL_CTRL0_EG_PRI4(0x6) | - QCA8K_PORT_HOL_CTRL0_EG_PRI5(0x8) | - QCA8K_PORT_HOL_CTRL0_EG_PORT(0x1e); - break; - default: - mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) | - QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) | - QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x6) | - QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x8) | - QCA8K_PORT_HOL_CTRL0_EG_PORT(0x19); - } - qca8k_write(priv, QCA8K_REG_PORT_HOL_CTRL0(i), mask); - - mask = QCA8K_PORT_HOL_CTRL1_ING(0x6) | - QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN | - QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN | - QCA8K_PORT_HOL_CTRL1_WRED_EN; - qca8k_rmw(priv, QCA8K_REG_PORT_HOL_CTRL1(i), - QCA8K_PORT_HOL_CTRL1_ING_BUF | - QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN | - QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN | - QCA8K_PORT_HOL_CTRL1_WRED_EN, - mask); - } - - /* Set initial MTU for every port. - * We have only have a general MTU setting. So track - * every port and set the max across all port. - */ - priv->port_mtu[i] = ETH_FRAME_LEN + ETH_FCS_LEN; - } - - /* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */ - if (priv->switch_id == QCA8K_ID_QCA8327) { - mask = QCA8K_GLOBAL_FC_GOL_XON_THRES(288) | - QCA8K_GLOBAL_FC_GOL_XOFF_THRES(496); - qca8k_rmw(priv, QCA8K_REG_GLOBAL_FC_THRESH, - QCA8K_GLOBAL_FC_GOL_XON_THRES_S | - QCA8K_GLOBAL_FC_GOL_XOFF_THRES_S, - mask); + ret = qca8k_setup_port(ds, i); + if (ret) + return ret; } /* Setup our port MTUs to match power on defaults */ + for (i = 0; i < QCA8K_NUM_PORTS; i++) + /* Set per port MTU to 1500 as the MTU change function + * will add the overhead and if its set to 1518 then it + * will apply the overhead again and we will end up with + * MTU of 1536 instead of 1518 + */ + priv->port_mtu[i] = ETH_DATA_LEN; ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN); if (ret) dev_warn(priv->dev, "failed setting MTU settings"); @@ -1273,48 +596,87 @@ qca8k_setup(struct dsa_switch *ds) /* We don't have interrupts for link changes, so we need to poll */ ds->pcs_poll = true; + /* CPU port HW learning doesnt work correctly, so let DSA handle it */ + ds->assisted_learning_on_cpu_port = true; + return 0; } -static void -qca8k_mac_config_setup_internal_delay(struct qca8k_priv *priv, int cpu_port_index, - u32 reg) +static int psgmii_vco_calibrate(struct dsa_switch *ds) { - u32 delay, val = 0; - int ret; + struct qca8k_priv *priv = ds->priv; + int val, ret; - /* Delay can be declared in 3 different way. - * Mode to rgmii and internal-delay standard binding defined - * rgmii-id or rgmii-tx/rx phy mode set. - * The parse logic set a delay different than 0 only when one - * of the 3 different way is used. In all other case delay is - * not enabled. With ID or TX/RXID delay is enabled and set - * to the default and recommended value. - */ - if (priv->ports_config.rgmii_tx_delay[cpu_port_index]) { - delay = priv->ports_config.rgmii_tx_delay[cpu_port_index]; + if (!priv->psgmii_ethphy) { + dev_err(ds->dev, "PSGMII eth PHY missing, calibration failed!\n"); + return -ENODEV; + } - val |= QCA8K_PORT_PAD_RGMII_TX_DELAY(delay) | - QCA8K_PORT_PAD_RGMII_TX_DELAY_EN; + /* Fix PSGMII RX 20bit */ + ret = phy_write(priv->psgmii_ethphy, MII_BMCR, 0x5b); + /* Reset PSGMII PHY */ + ret = phy_write(priv->psgmii_ethphy, MII_BMCR, 0x1b); + /* Release reset */ + ret = phy_write(priv->psgmii_ethphy, MII_BMCR, 0x5b); + + /* Poll for VCO PLL calibration finish */ + ret = phy_read_mmd_poll_timeout(priv->psgmii_ethphy, + MDIO_MMD_PMAPMD, + 0x28, val, + (val & BIT(0)), + 10000, 1000000, + false); + if (ret) { + dev_err(ds->dev, "QCA807x PSGMII VCO calibration PLL not ready\n"); + return ret; } - if (priv->ports_config.rgmii_rx_delay[cpu_port_index]) { - delay = priv->ports_config.rgmii_rx_delay[cpu_port_index]; + /* Freeze PSGMII RX CDR */ + ret = phy_write(priv->psgmii_ethphy, MII_RESV2, 0x2230); - val |= QCA8K_PORT_PAD_RGMII_RX_DELAY(delay) | - QCA8K_PORT_PAD_RGMII_RX_DELAY_EN; + /* Start PSGMIIPHY VCO PLL calibration */ + ret = regmap_set_bits(priv->psgmii, + PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_1, + PSGMIIPHY_REG_PLL_VCO_CALIB_RESTART); + + /* Poll for PSGMIIPHY PLL calibration finish */ + ret = regmap_read_poll_timeout(priv->psgmii, + PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_2, + val, val & PSGMIIPHY_REG_PLL_VCO_CALIB_READY, + 10000, 1000000); + if (ret) { + dev_err(ds->dev, "PSGMIIPHY VCO calibration PLL not ready\n"); + return ret; } - /* Set RGMII delay based on the selected values */ - ret = qca8k_rmw(priv, reg, - QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK | - QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK | - QCA8K_PORT_PAD_RGMII_TX_DELAY_EN | - QCA8K_PORT_PAD_RGMII_RX_DELAY_EN, - val); - if (ret) - dev_err(priv->dev, "Failed to set internal delay for CPU port%d", - cpu_port_index == QCA8K_CPU_PORT0 ? 0 : 6); + /* Release PSGMII RX CDR */ + ret = phy_write(priv->psgmii_ethphy, MII_RESV2, 0x3230); + + /* Release PSGMII RX 20bit */ + ret = phy_write(priv->psgmii_ethphy, MII_BMCR, 0x5f); + + return ret; +} + +static int ipq4019_psgmii_configure(struct dsa_switch *ds) +{ + struct qca8k_priv *priv = ds->priv; + int ret; + + if (!priv->psgmii_calibrated) { + ret = psgmii_vco_calibrate(ds); + + ret = regmap_clear_bits(priv->psgmii, PSGMIIPHY_MODE_CONTROL, + PSGMIIPHY_MODE_ATHR_CSCO_MODE_25M); + ret = regmap_write(priv->psgmii, PSGMIIPHY_TX_CONTROL, + PSGMIIPHY_TX_CONTROL_MAGIC_VALUE); + + priv->psgmii_calibrated = true; + + return ret; + } + + return 0; } static void @@ -1322,141 +684,33 @@ qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode, const struct phylink_link_state *state) { struct qca8k_priv *priv = ds->priv; - int cpu_port_index, ret; - u32 reg, val; switch (port) { - case 0: /* 1st CPU port */ - if (state->interface != PHY_INTERFACE_MODE_RGMII && - state->interface != PHY_INTERFACE_MODE_RGMII_ID && - state->interface != PHY_INTERFACE_MODE_RGMII_TXID && - state->interface != PHY_INTERFACE_MODE_RGMII_RXID && - state->interface != PHY_INTERFACE_MODE_SGMII) - return; - - reg = QCA8K_REG_PORT0_PAD_CTRL; - cpu_port_index = QCA8K_CPU_PORT0; - break; + case 0: + /* CPU port, no configuration needed */ + return; case 1: case 2: case 3: + if (state->interface == PHY_INTERFACE_MODE_PSGMII) + if (ipq4019_psgmii_configure(ds)) + dev_err(ds->dev, "PSGMII configuration failed!\n"); + return; case 4: case 5: - /* Internal PHY, nothing to do */ - return; - case 6: /* 2nd CPU port / external PHY */ - if (state->interface != PHY_INTERFACE_MODE_RGMII && - state->interface != PHY_INTERFACE_MODE_RGMII_ID && - state->interface != PHY_INTERFACE_MODE_RGMII_TXID && - state->interface != PHY_INTERFACE_MODE_RGMII_RXID && - state->interface != PHY_INTERFACE_MODE_SGMII && - state->interface != PHY_INTERFACE_MODE_1000BASEX) - return; - - reg = QCA8K_REG_PORT6_PAD_CTRL; - cpu_port_index = QCA8K_CPU_PORT6; - break; - default: - dev_err(ds->dev, "%s: unsupported port: %i\n", __func__, port); - return; - } - - if (port != 6 && phylink_autoneg_inband(mode)) { - dev_err(ds->dev, "%s: in-band negotiation unsupported\n", - __func__); - return; - } - - switch (state->interface) { - case PHY_INTERFACE_MODE_RGMII: - case PHY_INTERFACE_MODE_RGMII_ID: - case PHY_INTERFACE_MODE_RGMII_TXID: - case PHY_INTERFACE_MODE_RGMII_RXID: - qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN); - - /* Configure rgmii delay */ - qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg); - - /* QCA8337 requires to set rgmii rx delay for all ports. - * This is enabled through PORT5_PAD_CTRL for all ports, - * rather than individual port registers. - */ - if (priv->switch_id == QCA8K_ID_QCA8337) - qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL, - QCA8K_PORT_PAD_RGMII_RX_DELAY_EN); - break; - case PHY_INTERFACE_MODE_SGMII: - case PHY_INTERFACE_MODE_1000BASEX: - /* Enable SGMII on the port */ - qca8k_write(priv, reg, QCA8K_PORT_PAD_SGMII_EN); - - /* Enable/disable SerDes auto-negotiation as necessary */ - ret = qca8k_read(priv, QCA8K_REG_PWS, &val); - if (ret) - return; - if (phylink_autoneg_inband(mode)) - val &= ~QCA8K_PWS_SERDES_AEN_DIS; - else - val |= QCA8K_PWS_SERDES_AEN_DIS; - qca8k_write(priv, QCA8K_REG_PWS, val); - - /* Configure the SGMII parameters */ - ret = qca8k_read(priv, QCA8K_REG_SGMII_CTRL, &val); - if (ret) - return; - - val |= QCA8K_SGMII_EN_SD; - - if (priv->ports_config.sgmii_enable_pll) - val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX | - QCA8K_SGMII_EN_TX; - - if (dsa_is_cpu_port(ds, port)) { - /* CPU port, we're talking to the CPU MAC, be a PHY */ - val &= ~QCA8K_SGMII_MODE_CTRL_MASK; - val |= QCA8K_SGMII_MODE_CTRL_PHY; - } else if (state->interface == PHY_INTERFACE_MODE_SGMII) { - val &= ~QCA8K_SGMII_MODE_CTRL_MASK; - val |= QCA8K_SGMII_MODE_CTRL_MAC; - } else if (state->interface == PHY_INTERFACE_MODE_1000BASEX) { - val &= ~QCA8K_SGMII_MODE_CTRL_MASK; - val |= QCA8K_SGMII_MODE_CTRL_BASEX; + if (state->interface == PHY_INTERFACE_MODE_RGMII || + state->interface == PHY_INTERFACE_MODE_RGMII_ID || + state->interface == PHY_INTERFACE_MODE_RGMII_RXID || + state->interface == PHY_INTERFACE_MODE_RGMII_TXID) { + qca8k_reg_set(priv, QCA8K_REG_RGMII_CTRL, QCA8K_RGMII_CTRL_CLK); } - qca8k_write(priv, QCA8K_REG_SGMII_CTRL, val); - - /* For qca8327/qca8328/qca8334/qca8338 sgmii is unique and - * falling edge is set writing in the PORT0 PAD reg - */ - if (priv->switch_id == QCA8K_ID_QCA8327 || - priv->switch_id == QCA8K_ID_QCA8337) - reg = QCA8K_REG_PORT0_PAD_CTRL; - - val = 0; - - /* SGMII Clock phase configuration */ - if (priv->ports_config.sgmii_rx_clk_falling_edge) - val |= QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE; - - if (priv->ports_config.sgmii_tx_clk_falling_edge) - val |= QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE; - - if (val) - ret = qca8k_rmw(priv, reg, - QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE | - QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE, - val); - - /* From original code is reported port instability as SGMII also - * require delay set. Apply advised values here or take them from DT. - */ - if (state->interface == PHY_INTERFACE_MODE_SGMII) - qca8k_mac_config_setup_internal_delay(priv, cpu_port_index, reg); - - break; + if (state->interface == PHY_INTERFACE_MODE_PSGMII) + if (ipq4019_psgmii_configure(ds)) + dev_err(ds->dev, "PSGMII configuration failed!\n"); + return; default: - dev_err(ds->dev, "xMII mode %s not supported for port %d\n", - phy_modes(state->interface), port); + dev_err(ds->dev, "%s: unsupported port: %i\n", __func__, port); return; } } @@ -1469,59 +723,49 @@ qca8k_phylink_validate(struct dsa_switch *ds, int port, __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; switch (port) { - case 0: /* 1st CPU port */ - if (state->interface != PHY_INTERFACE_MODE_NA && - state->interface != PHY_INTERFACE_MODE_RGMII && - state->interface != PHY_INTERFACE_MODE_RGMII_ID && - state->interface != PHY_INTERFACE_MODE_RGMII_TXID && - state->interface != PHY_INTERFACE_MODE_RGMII_RXID && - state->interface != PHY_INTERFACE_MODE_SGMII) + case 0: /* CPU port */ + if (state->interface != PHY_INTERFACE_MODE_INTERNAL) goto unsupported; break; case 1: case 2: case 3: - case 4: - case 5: - /* Internal PHY */ - if (state->interface != PHY_INTERFACE_MODE_NA && - state->interface != PHY_INTERFACE_MODE_GMII && - state->interface != PHY_INTERFACE_MODE_INTERNAL) + /* Only PSGMII mode is supported */ + if (state->interface != PHY_INTERFACE_MODE_PSGMII) goto unsupported; break; - case 6: /* 2nd CPU port / external PHY */ - if (state->interface != PHY_INTERFACE_MODE_NA && + case 4: + case 5: + /* PSGMII and RGMII modes are supported */ + if (state->interface != PHY_INTERFACE_MODE_PSGMII && state->interface != PHY_INTERFACE_MODE_RGMII && state->interface != PHY_INTERFACE_MODE_RGMII_ID && - state->interface != PHY_INTERFACE_MODE_RGMII_TXID && state->interface != PHY_INTERFACE_MODE_RGMII_RXID && - state->interface != PHY_INTERFACE_MODE_SGMII && - state->interface != PHY_INTERFACE_MODE_1000BASEX) + state->interface != PHY_INTERFACE_MODE_RGMII_TXID) goto unsupported; break; default: unsupported: + dev_warn(ds->dev, "interface '%s' (%d) on port %d is not supported\n", + phy_modes(state->interface), state->interface, port); linkmode_zero(supported); return; } - phylink_set_port_modes(mask); - phylink_set(mask, Autoneg); - - phylink_set(mask, 1000baseT_Full); - phylink_set(mask, 10baseT_Half); - phylink_set(mask, 10baseT_Full); - phylink_set(mask, 100baseT_Half); - phylink_set(mask, 100baseT_Full); + if (port == 0) { + phylink_set_port_modes(mask); - if (state->interface == PHY_INTERFACE_MODE_1000BASEX) - phylink_set(mask, 1000baseX_Full); + phylink_set(mask, 1000baseT_Full); - phylink_set(mask, Pause); - phylink_set(mask, Asym_Pause); + phylink_set(mask, Pause); + phylink_set(mask, Asym_Pause); - linkmode_and(supported, supported, mask); - linkmode_and(state->advertising, state->advertising, mask); + linkmode_and(supported, supported, mask); + linkmode_and(state->advertising, state->advertising, mask); + } else { + /* Simply copy what PHYs tell us */ + linkmode_copy(state->advertising, supported); + } } static int @@ -1895,14 +1139,22 @@ qca8k_port_fdb_dump(struct dsa_switch *ds, int port, return 0; } +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0) static int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering, struct switchdev_trans *trans) +#else +static int +qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering, + struct netlink_ext_ack *extack) +#endif { struct qca8k_priv *priv = ds->priv; +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0) if (switchdev_trans_ph_prepare(trans)) return 0; +#endif if (vlan_filtering) { qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), @@ -1917,39 +1169,70 @@ qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering, return 0; } +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0) static int qca8k_port_vlan_prepare(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan) { return 0; } +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0) static void qca8k_port_vlan_add(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan) +#else +static int +qca8k_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) +#endif { bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; struct qca8k_priv *priv = ds->priv; int ret = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0) u16 vid; for (vid = vlan->vid_begin; vid <= vlan->vid_end && !ret; ++vid) ret = qca8k_vlan_add(priv, port, vid, untagged); - +#else + ret = qca8k_vlan_add(priv, port, vlan->vid, untagged); +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0) if (ret) dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret); +#else + if (ret) { + dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret); + return ret; + } +#endif if (pvid) { int shift = 16 * (port % 2); qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port), +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0) 0xfff << shift, vlan->vid_end << shift); +#else + 0xfff << shift, vlan->vid << shift); +#endif qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port), +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0) QCA8K_PORT_VLAN_CVID(vlan->vid_end) | QCA8K_PORT_VLAN_SVID(vlan->vid_end)); +#else + QCA8K_PORT_VLAN_CVID(vlan->vid) | + QCA8K_PORT_VLAN_SVID(vlan->vid)); +#endif } +#if LINUX_VERSION_CODE > KERNEL_VERSION(5,12,0) + return 0; +#endif } static int @@ -1958,38 +1241,25 @@ qca8k_port_vlan_del(struct dsa_switch *ds, int port, { struct qca8k_priv *priv = ds->priv; int ret = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0) u16 vid; for (vid = vlan->vid_begin; vid <= vlan->vid_end && !ret; ++vid) ret = qca8k_vlan_del(priv, port, vid); - +#else + ret = qca8k_vlan_del(priv, port, vlan->vid); +#endif if (ret) dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret); return ret; } -static u32 qca8k_get_phy_flags(struct dsa_switch *ds, int port) -{ - struct qca8k_priv *priv = ds->priv; - - /* Communicate to the phy internal driver the switch revision. - * Based on the switch revision different values needs to be - * set to the dbg and mmd reg on the phy. - * The first 2 bit are used to communicate the switch revision - * to the phy driver. - */ - if (port > 0 && port < 6) - return priv->switch_revision; - - return 0; -} - static enum dsa_tag_protocol qca8k_get_tag_protocol(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp) { - return DSA_TAG_PROTO_QCA; + return DSA_TAG_PROTO_IPQ4019; } static const struct dsa_switch_ops qca8k_switch_ops = { @@ -2011,7 +1281,9 @@ static const struct dsa_switch_ops qca8k_switch_ops = { .port_fdb_del = qca8k_port_fdb_del, .port_fdb_dump = qca8k_port_fdb_dump, .port_vlan_filtering = qca8k_port_vlan_filtering, +#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0) .port_vlan_prepare = qca8k_port_vlan_prepare, +#endif .port_vlan_add = qca8k_port_vlan_add, .port_vlan_del = qca8k_port_vlan_del, .phylink_validate = qca8k_phylink_validate, @@ -2019,172 +1291,127 @@ static const struct dsa_switch_ops qca8k_switch_ops = { .phylink_mac_config = qca8k_phylink_mac_config, .phylink_mac_link_down = qca8k_phylink_mac_link_down, .phylink_mac_link_up = qca8k_phylink_mac_link_up, - .get_phy_flags = qca8k_get_phy_flags, }; -static int qca8k_read_switch_id(struct qca8k_priv *priv) -{ - const struct qca8k_match_data *data; - u32 val; - u8 id; - int ret; - - /* get the switches ID from the compatible */ - data = of_device_get_match_data(priv->dev); - if (!data) - return -ENODEV; - - ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val); - if (ret < 0) - return -ENODEV; - - id = QCA8K_MASK_CTRL_DEVICE_ID(val & QCA8K_MASK_CTRL_DEVICE_ID_MASK); - if (id != data->id) { - dev_err(priv->dev, "Switch id detected %x but expected %x", id, data->id); - return -ENODEV; - } - - priv->switch_id = id; - - /* Save revision to communicate to the internal PHY driver */ - priv->switch_revision = (val & QCA8K_MASK_CTRL_REV_ID_MASK); - - return 0; -} - static int -qca8k_sw_probe(struct mdio_device *mdiodev) +qca8k_ipq4019_probe(struct platform_device *pdev) { struct qca8k_priv *priv; + void __iomem *base, *psgmii; + struct device_node *np = pdev->dev.of_node, *mdio_np, *psgmii_ethphy_np; int ret; - /* allocate the private data struct so that we can probe the switches - * ID register - */ - priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL); + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; - priv->bus = mdiodev->bus; - priv->dev = &mdiodev->dev; + priv->dev = &pdev->dev; - priv->reset_gpio = devm_gpiod_get_optional(priv->dev, "reset", - GPIOD_ASIS); - if (IS_ERR(priv->reset_gpio)) - return PTR_ERR(priv->reset_gpio); + base = devm_platform_ioremap_resource_byname(pdev, "base"); + if (IS_ERR(base)) + return PTR_ERR(base); - if (priv->reset_gpio) { - gpiod_set_value_cansleep(priv->reset_gpio, 1); - /* The active low duration must be greater than 10 ms - * and checkpatch.pl wants 20 ms. - */ - msleep(20); - gpiod_set_value_cansleep(priv->reset_gpio, 0); + priv->regmap = devm_regmap_init_mmio(priv->dev, base, + &qca8k_ipq4019_regmap_config); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err(priv->dev, "base regmap initialization failed, %d\n", ret); + return ret; } - /* Check the detected switch id */ - ret = qca8k_read_switch_id(priv); - if (ret) + psgmii = devm_platform_ioremap_resource_byname(pdev, "psgmii_phy"); + if (IS_ERR(psgmii)) + return PTR_ERR(psgmii); + + priv->psgmii = devm_regmap_init_mmio(priv->dev, psgmii, + &qca8k_ipq4019_psgmii_phy_regmap_config); + if (IS_ERR(priv->psgmii)) { + ret = PTR_ERR(priv->psgmii); + dev_err(priv->dev, "PSGMII regmap initialization failed, %d\n", ret); return ret; + } + + mdio_np = of_parse_phandle(np, "mdio", 0); + if (!mdio_np) { + dev_err(&pdev->dev, "unable to get MDIO bus phandle\n"); + of_node_put(mdio_np); + return -EINVAL; + } + + priv->bus = of_mdio_find_bus(mdio_np); + of_node_put(mdio_np); + if (!priv->bus) { + dev_err(&pdev->dev, "unable to find MDIO bus\n"); + return -EPROBE_DEFER; + } - priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL); + psgmii_ethphy_np = of_parse_phandle(np, "psgmii-ethphy", 0); + if (!psgmii_ethphy_np) { + dev_dbg(&pdev->dev, "unable to get PSGMII eth PHY phandle\n"); + of_node_put(psgmii_ethphy_np); + } + + if (psgmii_ethphy_np) { + priv->psgmii_ethphy = of_phy_find_device(psgmii_ethphy_np); + of_node_put(psgmii_ethphy_np); + if (!priv->psgmii_ethphy) { + dev_err(&pdev->dev, "unable to get PSGMII eth PHY\n"); + return -ENODEV; + } + } + + priv->ds = devm_kzalloc(priv->dev, sizeof(*priv->ds), GFP_KERNEL); if (!priv->ds) return -ENOMEM; - priv->ds->dev = &mdiodev->dev; + priv->ds->dev = priv->dev; priv->ds->num_ports = QCA8K_NUM_PORTS; - priv->ds->configure_vlan_while_not_filtering = true; priv->ds->priv = priv; priv->ops = qca8k_switch_ops; priv->ds->ops = &priv->ops; + mutex_init(&priv->reg_mutex); - dev_set_drvdata(&mdiodev->dev, priv); + platform_set_drvdata(pdev, priv); return dsa_register_switch(priv->ds); } -static void -qca8k_sw_remove(struct mdio_device *mdiodev) +static int +qca8k_ipq4019_remove(struct platform_device *pdev) { - struct qca8k_priv *priv = dev_get_drvdata(&mdiodev->dev); + struct qca8k_priv *priv = dev_get_drvdata(&pdev->dev); int i; + if (!priv) + return 0; + for (i = 0; i < QCA8K_NUM_PORTS; i++) qca8k_port_set_status(priv, i, 0); dsa_unregister_switch(priv->ds); -} - -#ifdef CONFIG_PM_SLEEP -static void -qca8k_set_pm(struct qca8k_priv *priv, int enable) -{ - int i; - - for (i = 0; i < QCA8K_NUM_PORTS; i++) { - if (!priv->port_sts[i].enabled) - continue; - - qca8k_port_set_status(priv, i, enable); - } -} -static int qca8k_suspend(struct device *dev) -{ - struct qca8k_priv *priv = dev_get_drvdata(dev); - - qca8k_set_pm(priv, 0); - - return dsa_switch_suspend(priv->ds); -} + dev_set_drvdata(&pdev->dev, NULL); -static int qca8k_resume(struct device *dev) -{ - struct qca8k_priv *priv = dev_get_drvdata(dev); - - qca8k_set_pm(priv, 1); - - return dsa_switch_resume(priv->ds); + return 0; } -#endif /* CONFIG_PM_SLEEP */ - -static SIMPLE_DEV_PM_OPS(qca8k_pm_ops, - qca8k_suspend, qca8k_resume); - -static const struct qca8k_match_data qca8327 = { - .id = QCA8K_ID_QCA8327, - .reduced_package = true, -}; - -static const struct qca8k_match_data qca8328 = { - .id = QCA8K_ID_QCA8327, -}; - -static const struct qca8k_match_data qca833x = { - .id = QCA8K_ID_QCA8337, -}; -static const struct of_device_id qca8k_of_match[] = { - { .compatible = "qca,qca8327", .data = &qca8327 }, - { .compatible = "qca,qca8328", .data = &qca8328 }, - { .compatible = "qca,qca8334", .data = &qca833x }, - { .compatible = "qca,qca8337", .data = &qca833x }, +static const struct of_device_id qca8k_ipq4019_of_match[] = { + { .compatible = "qca,ipq4019-qca8337n" }, { /* sentinel */ }, }; -static struct mdio_driver qca8kmdio_driver = { - .probe = qca8k_sw_probe, - .remove = qca8k_sw_remove, - .mdiodrv.driver = { - .name = "qca8k", - .of_match_table = qca8k_of_match, - .pm = &qca8k_pm_ops, +static struct platform_driver qca8k_ipq4019_driver = { + .probe = qca8k_ipq4019_probe, + .remove = qca8k_ipq4019_remove, + .driver = { + .name = "qca8k-ipq4019", + .of_match_table = qca8k_ipq4019_of_match, }, }; -mdio_module_driver(qca8kmdio_driver); +module_platform_driver(qca8k_ipq4019_driver); MODULE_AUTHOR("Mathieu Olivari, John Crispin "); -MODULE_DESCRIPTION("Driver for QCA8K ethernet switch family"); +MODULE_AUTHOR("Gabor Juhos , Robert Marko "); +MODULE_DESCRIPTION("Qualcomm IPQ4019 built-in switch driver"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:qca8k"); diff --git a/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.h b/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.h index e10571a398..a36eb0dadc 100644 --- a/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.h +++ b/target/linux/ipq40xx/files/drivers/net/dsa/qca/qca8k-ipq4019.h @@ -8,19 +8,12 @@ #ifndef __QCA8K_H #define __QCA8K_H -#include #include -#include -#define QCA8K_NUM_PORTS 7 -#define QCA8K_NUM_CPU_PORTS 2 +#define QCA8K_NUM_PORTS 6 +#define QCA8K_CPU_PORT 0 #define QCA8K_MAX_MTU 9000 -#define PHY_ID_QCA8327 0x004dd034 -#define QCA8K_ID_QCA8327 0x12 -#define PHY_ID_QCA8337 0x004dd036 -#define QCA8K_ID_QCA8337 0x13 - #define QCA8K_BUSY_WAIT_TIMEOUT 2000 #define QCA8K_NUM_FDB_RECORDS 2048 @@ -33,46 +26,26 @@ #define QCA8K_MASK_CTRL_REV_ID(x) ((x) >> 0) #define QCA8K_MASK_CTRL_DEVICE_ID_MASK GENMASK(15, 8) #define QCA8K_MASK_CTRL_DEVICE_ID(x) ((x) >> 8) -#define QCA8K_REG_PORT0_PAD_CTRL 0x004 -#define QCA8K_PORT0_PAD_SGMII_RXCLK_FALLING_EDGE BIT(19) -#define QCA8K_PORT0_PAD_SGMII_TXCLK_FALLING_EDGE BIT(18) -#define QCA8K_REG_PORT5_PAD_CTRL 0x008 -#define QCA8K_REG_PORT6_PAD_CTRL 0x00c -#define QCA8K_PORT_PAD_RGMII_EN BIT(26) -#define QCA8K_PORT_PAD_RGMII_TX_DELAY_MASK GENMASK(23, 22) -#define QCA8K_PORT_PAD_RGMII_TX_DELAY(x) ((x) << 22) -#define QCA8K_PORT_PAD_RGMII_RX_DELAY_MASK GENMASK(21, 20) -#define QCA8K_PORT_PAD_RGMII_RX_DELAY(x) ((x) << 20) -#define QCA8K_PORT_PAD_RGMII_TX_DELAY_EN BIT(25) -#define QCA8K_PORT_PAD_RGMII_RX_DELAY_EN BIT(24) -#define QCA8K_MAX_DELAY 3 -#define QCA8K_PORT_PAD_SGMII_EN BIT(7) -#define QCA8K_REG_PWS 0x010 -#define QCA8K_PWS_POWER_ON_SEL BIT(31) -/* This reg is only valid for QCA832x and toggle the package - * type from 176 pin (by default) to 148 pin used on QCA8327 +#define QCA8K_REG_RGMII_CTRL 0x004 +#define QCA8K_RGMII_CTRL_RGMII_RXC GENMASK(1, 0) +#define QCA8K_RGMII_CTRL_RGMII_TXC GENMASK(9, 8) +/* Some kind of CLK selection + * 0: gcc_ess_dly2ns + * 1: gcc_ess_clk */ -#define QCA8327_PWS_PACKAGE148_EN BIT(30) -#define QCA8K_PWS_LED_OPEN_EN_CSR BIT(24) -#define QCA8K_PWS_SERDES_AEN_DIS BIT(7) +#define QCA8K_RGMII_CTRL_CLK BIT(10) +#define QCA8K_RGMII_CTRL_DELAY_RMII0 GENMASK(17, 16) +#define QCA8K_RGMII_CTRL_INVERT_RMII0_REF_CLK BIT(18) +#define QCA8K_RGMII_CTRL_DELAY_RMII1 GENMASK(20, 19) +#define QCA8K_RGMII_CTRL_INVERT_RMII1_REF_CLK BIT(21) +#define QCA8K_RGMII_CTRL_INVERT_RMII0_MASTER_EN BIT(24) +#define QCA8K_RGMII_CTRL_INVERT_RMII1_MASTER_EN BIT(25) #define QCA8K_REG_MODULE_EN 0x030 #define QCA8K_MODULE_EN_MIB BIT(0) #define QCA8K_REG_MIB 0x034 #define QCA8K_MIB_FLUSH BIT(24) #define QCA8K_MIB_CPU_KEEP BIT(20) #define QCA8K_MIB_BUSY BIT(17) -#define QCA8K_MDIO_MASTER_CTRL 0x3c -#define QCA8K_MDIO_MASTER_BUSY BIT(31) -#define QCA8K_MDIO_MASTER_EN BIT(30) -#define QCA8K_MDIO_MASTER_READ BIT(27) -#define QCA8K_MDIO_MASTER_WRITE 0 -#define QCA8K_MDIO_MASTER_SUP_PRE BIT(26) -#define QCA8K_MDIO_MASTER_PHY_ADDR(x) ((x) << 21) -#define QCA8K_MDIO_MASTER_REG_ADDR(x) ((x) << 16) -#define QCA8K_MDIO_MASTER_DATA(x) (x) -#define QCA8K_MDIO_MASTER_DATA_MASK GENMASK(15, 0) -#define QCA8K_MDIO_MASTER_MAX_PORTS 5 -#define QCA8K_MDIO_MASTER_MAX_REG 32 #define QCA8K_GOL_MAC_ADDR0 0x60 #define QCA8K_GOL_MAC_ADDR1 0x64 #define QCA8K_MAX_FRAME_SIZE 0x78 @@ -109,11 +82,6 @@ #define QCA8K_SGMII_MODE_CTRL_PHY (1 << 22) #define QCA8K_SGMII_MODE_CTRL_MAC (2 << 22) -/* MAC_PWR_SEL registers */ -#define QCA8K_REG_MAC_PWR_SEL 0x0e4 -#define QCA8K_MAC_PWR_RGMII1_1_8V BIT(18) -#define QCA8K_MAC_PWR_RGMII0_1_8V BIT(19) - /* EEE control registers */ #define QCA8K_REG_EEE_CTRL 0x100 #define QCA8K_REG_EEE_CTRL_LPI_EN(_i) ((_i + 1) * 2) @@ -228,9 +196,15 @@ /* MIB registers */ #define QCA8K_PORT_MIB_COUNTER(_i) (0x1000 + (_i) * 0x100) -/* QCA specific MII registers */ -#define MII_ATH_MMD_ADDR 0x0d -#define MII_ATH_MMD_DATA 0x0e +/* IPQ4019 PSGMII PHY registers */ +#define PSGMIIPHY_MODE_CONTROL 0x1b4 +#define PSGMIIPHY_MODE_ATHR_CSCO_MODE_25M BIT(0) +#define PSGMIIPHY_TX_CONTROL 0x288 +#define PSGMIIPHY_TX_CONTROL_MAGIC_VALUE 0x8380 +#define PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_1 0x9c +#define PSGMIIPHY_REG_PLL_VCO_CALIB_RESTART BIT(14) +#define PSGMIIPHY_VCO_CALIBRATION_CONTROL_REGISTER_2 0xa0 +#define PSGMIIPHY_REG_PLL_VCO_CALIB_READY BIT(0) enum { QCA8K_PORT_SPEED_10M = 0, @@ -260,29 +234,7 @@ struct ar8xxx_port_status { int enabled; }; -struct qca8k_match_data { - u8 id; - bool reduced_package; -}; - -enum { - QCA8K_CPU_PORT0, - QCA8K_CPU_PORT6, -}; - -struct qca8k_ports_config { - bool sgmii_rx_clk_falling_edge; - bool sgmii_tx_clk_falling_edge; - bool sgmii_enable_pll; - u8 rgmii_rx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */ - u8 rgmii_tx_delay[QCA8K_NUM_CPU_PORTS]; /* 0: CPU port0, 1: CPU port6 */ -}; - struct qca8k_priv { - u8 switch_id; - u8 switch_revision; - bool legacy_phy_port_mapping; - struct qca8k_ports_config ports_config; struct regmap *regmap; struct mii_bus *bus; struct ar8xxx_port_status port_sts[QCA8K_NUM_PORTS]; @@ -290,8 +242,12 @@ struct qca8k_priv { struct mutex reg_mutex; struct device *dev; struct dsa_switch_ops ops; - struct gpio_desc *reset_gpio; unsigned int port_mtu[QCA8K_NUM_PORTS]; + + /* IPQ4019 specific */ + struct regmap *psgmii; + bool psgmii_calibrated; + struct phy_device *psgmii_ethphy; }; struct qca8k_mib_desc { diff --git a/target/linux/ipq40xx/patches-5.10/705-net-dsa-add-Qualcomm-IPQ4019-built-in-switch-support.patch b/target/linux/ipq40xx/patches-5.10/705-net-dsa-add-Qualcomm-IPQ4019-built-in-switch-support.patch new file mode 100644 index 0000000000..ed201b7c58 --- /dev/null +++ b/target/linux/ipq40xx/patches-5.10/705-net-dsa-add-Qualcomm-IPQ4019-built-in-switch-support.patch @@ -0,0 +1,53 @@ +From b5f71652b85a85ea53162e9e2b760b84fd0d254f Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 1 Nov 2021 18:10:28 +0100 +Subject: [PATCH] net: dsa: add Qualcomm IPQ4019 built-in switch support + +Qualcomm IPQ40xx SoC-s have a variant of QCA8337N switch built-in. + +It shares most of the stuff with its external counterpart, however it is +modified for the SoC. +Namely, it doesn't have second CPU port (Port 6), so it has 6 ports +instead of 7. +It also has no built-in PHY-s but rather requires external PSGMII based +companion PHY-s (QCA8072 and QCA8075) for which it first needs to carry +out calibration before using them. +PSGMII has a SoC built-in PHY that is used to connect to the PHY-s which +unfortunately requires some magic values as the datasheet doesnt document +the bits that are being set or the register at all. + +Since its built-in it is MMIO like other peripherals and doesn't have its +own MDIO bus but depends on the SoC provided one. + +CPU connection is at Port 0 and it uses some kind of a internal connection +and no traditional RGMII/SGMII. +It also doesn't use in-band tagging like other qca8k switches so a shinfo +based tagger is used. + +Signed-off-by: Robert Marko +--- + drivers/net/dsa/qca/Kconfig | 9 +++++++++ + drivers/net/dsa/qca/Makefile | 1 + + 2 files changed, 10 insertions(+) + +--- a/drivers/net/dsa/qca/Kconfig ++++ b/drivers/net/dsa/qca/Kconfig +@@ -7,3 +7,12 @@ config NET_DSA_AR9331 + help + This enables support for the Qualcomm Atheros AR9331 built-in Ethernet + switch. ++ ++config NET_DSA_QCA8K_IPQ4019 ++ tristate "Qualcomm Atheros IPQ4019 built-in Ethernet switch support" ++ depends on HAS_IOMEM && NET_DSA ++ select NET_DSA_TAG_IPQ4019 ++ select REGMAP ++ help ++ This enables support for the Qualcomm Atheros IPQ4019 SoC built-in ++ Ethernet switch. +--- a/drivers/net/dsa/qca/Makefile ++++ b/drivers/net/dsa/qca/Makefile +@@ -1,2 +1,3 @@ + # SPDX-License-Identifier: GPL-2.0-only + obj-$(CONFIG_NET_DSA_AR9331) += ar9331.o ++obj-$(CONFIG_NET_DSA_QCA8K_IPQ4019) += qca8k-ipq4019.o diff --git a/target/linux/ipq40xx/patches-5.10/706-arm-dts-ipq4019-add-switch-node.patch b/target/linux/ipq40xx/patches-5.10/706-arm-dts-ipq4019-add-switch-node.patch new file mode 100644 index 0000000000..a231c7331b --- /dev/null +++ b/target/linux/ipq40xx/patches-5.10/706-arm-dts-ipq4019-add-switch-node.patch @@ -0,0 +1,98 @@ +From ebb62523990a27b3a25e422fa575619f7f725a20 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 1 Nov 2021 18:15:04 +0100 +Subject: [PATCH] arm: dts: ipq4019: add switch node + +Since the built-in IPQ40xx switch now has a driver, add the required node +for it to work. + +Signed-off-by: Robert Marko +--- + arch/arm/boot/dts/qcom-ipq4019.dtsi | 78 +++++++++++++++++++++++++++++ + 1 file changed, 78 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi +@@ -590,6 +590,82 @@ + status = "disabled"; + }; + ++ switch: switch@c000000 { ++ compatible = "qca,ipq4019-qca8337n"; ++ reg = <0xc000000 0x80000>, <0x98000 0x800>; ++ reg-names = "base", "psgmii_phy"; ++ resets = <&gcc ESS_PSGMII_ARES>; ++ reset-names = "psgmii_rst"; ++ mdio = <&mdio>; ++ psgmii-ethphy = <&psgmiiphy>; ++ ++ status = "disabled"; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { /* MAC0 */ ++ reg = <0>; ++ label = "cpu"; ++ ethernet = <&gmac>; ++ phy-mode = "internal"; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ pause; ++ asym-pause; ++ }; ++ }; ++ ++ swport1: port@1 { /* MAC1 */ ++ reg = <1>; ++ label = "lan1"; ++ phy-handle = <ðphy0>; ++ phy-mode = "psgmii"; ++ ++ status = "disabled"; ++ }; ++ ++ swport2: port@2 { /* MAC2 */ ++ reg = <2>; ++ label = "lan2"; ++ phy-handle = <ðphy1>; ++ phy-mode = "psgmii"; ++ ++ status = "disabled"; ++ }; ++ ++ swport3: port@3 { /* MAC3 */ ++ reg = <3>; ++ label = "lan3"; ++ phy-handle = <ðphy2>; ++ phy-mode = "psgmii"; ++ ++ status = "disabled"; ++ }; ++ ++ swport4: port@4 { /* MAC4 */ ++ reg = <4>; ++ label = "lan4"; ++ phy-handle = <ðphy3>; ++ phy-mode = "psgmii"; ++ ++ status = "disabled"; ++ }; ++ ++ swport5: port@5 { /* MAC5 */ ++ reg = <5>; ++ label = "wan"; ++ phy-handle = <ðphy4>; ++ phy-mode = "psgmii"; ++ ++ status = "disabled"; ++ }; ++ }; ++ }; ++ + gmac: ethernet@c080000 { + compatible = "qcom,ipq4019-ess-edma"; + reg = <0xc080000 0x8000>; diff --git a/target/linux/ipq40xx/patches-5.10/706-dt-bindings-net-add-QCA807x-PHY.patch b/target/linux/ipq40xx/patches-5.10/707-dt-bindings-net-add-QCA807x-PHY.patch similarity index 100% rename from target/linux/ipq40xx/patches-5.10/706-dt-bindings-net-add-QCA807x-PHY.patch rename to target/linux/ipq40xx/patches-5.10/707-dt-bindings-net-add-QCA807x-PHY.patch diff --git a/target/linux/ipq40xx/patches-5.10/707-net-phy-Add-Qualcom-QCA807x-driver.patch b/target/linux/ipq40xx/patches-5.10/708-net-phy-Add-Qualcom-QCA807x-driver.patch similarity index 100% rename from target/linux/ipq40xx/patches-5.10/707-net-phy-Add-Qualcom-QCA807x-driver.patch rename to target/linux/ipq40xx/patches-5.10/708-net-phy-Add-Qualcom-QCA807x-driver.patch diff --git a/target/linux/ipq40xx/patches-5.10/708-arm-dts-ipq4019-QCA807x-properties.patch b/target/linux/ipq40xx/patches-5.10/709-arm-dts-ipq4019-QCA807x-properties.patch similarity index 98% rename from target/linux/ipq40xx/patches-5.10/708-arm-dts-ipq4019-QCA807x-properties.patch rename to target/linux/ipq40xx/patches-5.10/709-arm-dts-ipq4019-QCA807x-properties.patch index 33310d9f86..cc4b44b393 100644 --- a/target/linux/ipq40xx/patches-5.10/708-arm-dts-ipq4019-QCA807x-properties.patch +++ b/target/linux/ipq40xx/patches-5.10/709-arm-dts-ipq4019-QCA807x-properties.patch @@ -20,7 +20,7 @@ Signed-off-by: Robert Marko / { #address-cells = <1>; -@@ -645,22 +646,39 @@ +@@ -726,22 +727,38 @@ ethphy0: ethernet-phy@0 { reg = <0>; diff --git a/target/linux/ipq40xx/patches-5.15/705-net-dsa-add-Qualcomm-IPQ4019-built-in-switch-support.patch b/target/linux/ipq40xx/patches-5.15/705-net-dsa-add-Qualcomm-IPQ4019-built-in-switch-support.patch new file mode 100644 index 0000000000..d75b5e514d --- /dev/null +++ b/target/linux/ipq40xx/patches-5.15/705-net-dsa-add-Qualcomm-IPQ4019-built-in-switch-support.patch @@ -0,0 +1,56 @@ +From b5f71652b85a85ea53162e9e2b760b84fd0d254f Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 1 Nov 2021 18:10:28 +0100 +Subject: [PATCH] net: dsa: add Qualcomm IPQ4019 built-in switch support + +Qualcomm IPQ40xx SoC-s have a variant of QCA8337N switch built-in. + +It shares most of the stuff with its external counterpart, however it is +modified for the SoC. +Namely, it doesn't have second CPU port (Port 6), so it has 6 ports +instead of 7. +It also has no built-in PHY-s but rather requires external PSGMII based +companion PHY-s (QCA8072 and QCA8075) for which it first needs to carry +out calibration before using them. +PSGMII has a SoC built-in PHY that is used to connect to the PHY-s which +unfortunately requires some magic values as the datasheet doesnt document +the bits that are being set or the register at all. + +Since its built-in it is MMIO like other peripherals and doesn't have its +own MDIO bus but depends on the SoC provided one. + +CPU connection is at Port 0 and it uses some kind of a internal connection +and no traditional RGMII/SGMII. +It also doesn't use in-band tagging like other qca8k switches so a shinfo +based tagger is used. + +Signed-off-by: Robert Marko +--- + drivers/net/dsa/qca/Kconfig | 9 +++++++++ + drivers/net/dsa/qca/Makefile | 1 + + 2 files changed, 10 insertions(+) + +--- a/drivers/net/dsa/qca/Kconfig ++++ b/drivers/net/dsa/qca/Kconfig +@@ -15,3 +15,13 @@ config NET_DSA_QCA8K + help + This enables support for the Qualcomm Atheros QCA8K Ethernet + switch chips. ++ ++config NET_DSA_QCA8K_IPQ4019 ++ tristate "Qualcomm Atheros IPQ4019 built-in Ethernet switch support" ++ depends on HAS_IOMEM && NET_DSA ++ select NET_DSA_TAG_IPQ4019 ++ select REGMAP ++ help ++ This enables support for the Qualcomm Atheros IPQ4019 SoC built-in ++ Ethernet switch. ++ +--- a/drivers/net/dsa/qca/Makefile ++++ b/drivers/net/dsa/qca/Makefile +@@ -1,4 +1,5 @@ + # SPDX-License-Identifier: GPL-2.0-only + obj-$(CONFIG_NET_DSA_AR9331) += ar9331.o ++obj-$(CONFIG_NET_DSA_QCA8K_IPQ4019) += qca8k-ipq4019.o + obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o + qca8k-y += qca8k-common.o qca8k-8xxx.o diff --git a/target/linux/ipq40xx/patches-5.15/706-arm-dts-ipq4019-add-switch-node.patch b/target/linux/ipq40xx/patches-5.15/706-arm-dts-ipq4019-add-switch-node.patch new file mode 100644 index 0000000000..a231c7331b --- /dev/null +++ b/target/linux/ipq40xx/patches-5.15/706-arm-dts-ipq4019-add-switch-node.patch @@ -0,0 +1,98 @@ +From ebb62523990a27b3a25e422fa575619f7f725a20 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 1 Nov 2021 18:15:04 +0100 +Subject: [PATCH] arm: dts: ipq4019: add switch node + +Since the built-in IPQ40xx switch now has a driver, add the required node +for it to work. + +Signed-off-by: Robert Marko +--- + arch/arm/boot/dts/qcom-ipq4019.dtsi | 78 +++++++++++++++++++++++++++++ + 1 file changed, 78 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi +@@ -590,6 +590,82 @@ + status = "disabled"; + }; + ++ switch: switch@c000000 { ++ compatible = "qca,ipq4019-qca8337n"; ++ reg = <0xc000000 0x80000>, <0x98000 0x800>; ++ reg-names = "base", "psgmii_phy"; ++ resets = <&gcc ESS_PSGMII_ARES>; ++ reset-names = "psgmii_rst"; ++ mdio = <&mdio>; ++ psgmii-ethphy = <&psgmiiphy>; ++ ++ status = "disabled"; ++ ++ ports { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ port@0 { /* MAC0 */ ++ reg = <0>; ++ label = "cpu"; ++ ethernet = <&gmac>; ++ phy-mode = "internal"; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ pause; ++ asym-pause; ++ }; ++ }; ++ ++ swport1: port@1 { /* MAC1 */ ++ reg = <1>; ++ label = "lan1"; ++ phy-handle = <ðphy0>; ++ phy-mode = "psgmii"; ++ ++ status = "disabled"; ++ }; ++ ++ swport2: port@2 { /* MAC2 */ ++ reg = <2>; ++ label = "lan2"; ++ phy-handle = <ðphy1>; ++ phy-mode = "psgmii"; ++ ++ status = "disabled"; ++ }; ++ ++ swport3: port@3 { /* MAC3 */ ++ reg = <3>; ++ label = "lan3"; ++ phy-handle = <ðphy2>; ++ phy-mode = "psgmii"; ++ ++ status = "disabled"; ++ }; ++ ++ swport4: port@4 { /* MAC4 */ ++ reg = <4>; ++ label = "lan4"; ++ phy-handle = <ðphy3>; ++ phy-mode = "psgmii"; ++ ++ status = "disabled"; ++ }; ++ ++ swport5: port@5 { /* MAC5 */ ++ reg = <5>; ++ label = "wan"; ++ phy-handle = <ðphy4>; ++ phy-mode = "psgmii"; ++ ++ status = "disabled"; ++ }; ++ }; ++ }; ++ + gmac: ethernet@c080000 { + compatible = "qcom,ipq4019-ess-edma"; + reg = <0xc080000 0x8000>; diff --git a/target/linux/ipq40xx/patches-5.15/706-dt-bindings-net-add-QCA807x-PHY.patch b/target/linux/ipq40xx/patches-5.15/707-dt-bindings-net-add-QCA807x-PHY.patch similarity index 100% rename from target/linux/ipq40xx/patches-5.15/706-dt-bindings-net-add-QCA807x-PHY.patch rename to target/linux/ipq40xx/patches-5.15/707-dt-bindings-net-add-QCA807x-PHY.patch diff --git a/target/linux/ipq40xx/patches-5.15/707-net-phy-Add-Qualcom-QCA807x-driver.patch b/target/linux/ipq40xx/patches-5.15/708-net-phy-Add-Qualcom-QCA807x-driver.patch similarity index 100% rename from target/linux/ipq40xx/patches-5.15/707-net-phy-Add-Qualcom-QCA807x-driver.patch rename to target/linux/ipq40xx/patches-5.15/708-net-phy-Add-Qualcom-QCA807x-driver.patch diff --git a/target/linux/ipq40xx/patches-5.15/708-arm-dts-ipq4019-QCA807x-properties.patch b/target/linux/ipq40xx/patches-5.15/709-arm-dts-ipq4019-QCA807x-properties.patch similarity index 98% rename from target/linux/ipq40xx/patches-5.15/708-arm-dts-ipq4019-QCA807x-properties.patch rename to target/linux/ipq40xx/patches-5.15/709-arm-dts-ipq4019-QCA807x-properties.patch index d978693b4f..cc4b44b393 100644 --- a/target/linux/ipq40xx/patches-5.15/708-arm-dts-ipq4019-QCA807x-properties.patch +++ b/target/linux/ipq40xx/patches-5.15/709-arm-dts-ipq4019-QCA807x-properties.patch @@ -20,7 +20,7 @@ Signed-off-by: Robert Marko / { #address-cells = <1>; -@@ -598,22 +599,38 @@ +@@ -726,22 +727,38 @@ ethphy0: ethernet-phy@0 { reg = <0>; -- 2.30.2