mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-23 23:42:43 +00:00
4a78c46cbd
Currently, in case that PSGMII calibration fails it will panic the kernel which is not ideal and is preventing any debugging to be done. So, since PGMII calibration failing only means that wired networking wont work lets convet the panic() call to dev_error. Link: https://github.com/openwrt/openwrt/pull/15542 Signed-off-by: Robert Marko <robimarko@gmail.com>
1131 lines
33 KiB
Diff
1131 lines
33 KiB
Diff
From a38126870488398932e017dd9d76174b4aadbbbb Mon Sep 17 00:00:00 2001
|
|
From: Robert Marko <robert.marko@sartura.hr>
|
|
Date: Sat, 10 Sep 2022 15:46:09 +0200
|
|
Subject: [PATCH] net: dsa: qca8k: add 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 out of
|
|
band based tagger is used.
|
|
|
|
Signed-off-by: Robert Marko <robert.marko@sartura.hr>
|
|
---
|
|
drivers/net/dsa/qca/Kconfig | 8 +
|
|
drivers/net/dsa/qca/Makefile | 1 +
|
|
drivers/net/dsa/qca/qca8k-common.c | 6 +-
|
|
drivers/net/dsa/qca/qca8k-ipq4019.c | 948 ++++++++++++++++++++++++++++
|
|
drivers/net/dsa/qca/qca8k.h | 56 ++
|
|
5 files changed, 1016 insertions(+), 3 deletions(-)
|
|
create mode 100644 drivers/net/dsa/qca/qca8k-ipq4019.c
|
|
|
|
--- a/drivers/net/dsa/qca/Kconfig
|
|
+++ b/drivers/net/dsa/qca/Kconfig
|
|
@@ -24,3 +24,11 @@ config NET_DSA_QCA8K_LEDS_SUPPORT
|
|
help
|
|
This enabled support for LEDs present on the Qualcomm Atheros
|
|
QCA8K Ethernet switch chips.
|
|
+
|
|
+config NET_DSA_QCA8K_IPQ4019
|
|
+ tristate "Qualcomm Atheros IPQ4019 Ethernet switch support"
|
|
+ select NET_DSA_TAG_OOB
|
|
+ select REGMAP_MMIO
|
|
+ help
|
|
+ This enables support for the switch built-into Qualcomm Atheros
|
|
+ IPQ4019 SoCs.
|
|
--- a/drivers/net/dsa/qca/Makefile
|
|
+++ b/drivers/net/dsa/qca/Makefile
|
|
@@ -5,3 +5,4 @@ qca8k-y += qca8k-common.o qca8k-8xxx.
|
|
ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT
|
|
qca8k-y += qca8k-leds.o
|
|
endif
|
|
+obj-$(CONFIG_NET_DSA_QCA8K_IPQ4019) += qca8k-ipq4019.o qca8k-common.o
|
|
--- a/drivers/net/dsa/qca/qca8k-common.c
|
|
+++ b/drivers/net/dsa/qca/qca8k-common.c
|
|
@@ -412,7 +412,7 @@ static int qca8k_vlan_del(struct qca8k_p
|
|
|
|
/* Check if we're the last member to be removed */
|
|
del = true;
|
|
- for (i = 0; i < QCA8K_NUM_PORTS; i++) {
|
|
+ for (i = 0; i < priv->ds->num_ports; i++) {
|
|
mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i);
|
|
|
|
if ((reg & mask) != mask) {
|
|
@@ -653,7 +653,7 @@ int qca8k_port_bridge_join(struct dsa_sw
|
|
cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
|
|
port_mask = BIT(cpu_port);
|
|
|
|
- for (i = 0; i < QCA8K_NUM_PORTS; i++) {
|
|
+ for (i = 0; i < ds->num_ports; i++) {
|
|
if (dsa_is_cpu_port(ds, i))
|
|
continue;
|
|
if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge))
|
|
@@ -685,7 +685,7 @@ void qca8k_port_bridge_leave(struct dsa_
|
|
|
|
cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
|
|
|
|
- for (i = 0; i < QCA8K_NUM_PORTS; i++) {
|
|
+ for (i = 0; i < ds->num_ports; i++) {
|
|
if (dsa_is_cpu_port(ds, i))
|
|
continue;
|
|
if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge))
|
|
--- /dev/null
|
|
+++ b/drivers/net/dsa/qca/qca8k-ipq4019.c
|
|
@@ -0,0 +1,946 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
|
|
+ * Copyright (C) 2011-2012, 2020-2021 Gabor Juhos <juhosg@openwrt.org>
|
|
+ * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved.
|
|
+ * Copyright (c) 2016 John Crispin <john@phrozen.org>
|
|
+ * Copyright (c) 2022 Robert Marko <robert.marko@sartura.hr>
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/phy.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/bitfield.h>
|
|
+#include <linux/regmap.h>
|
|
+#include <net/dsa.h>
|
|
+#include <linux/of_net.h>
|
|
+#include <linux/of_mdio.h>
|
|
+#include <linux/of_platform.h>
|
|
+#include <linux/mdio.h>
|
|
+#include <linux/phylink.h>
|
|
+
|
|
+#include "qca8k.h"
|
|
+
|
|
+static struct regmap_config qca8k_ipq4019_regmap_config = {
|
|
+ .reg_bits = 32,
|
|
+ .val_bits = 32,
|
|
+ .reg_stride = 4,
|
|
+ .max_register = 0x16ac, /* end MIB - Port6 range */
|
|
+ .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 enum dsa_tag_protocol
|
|
+qca8k_ipq4019_get_tag_protocol(struct dsa_switch *ds, int port,
|
|
+ enum dsa_tag_protocol mp)
|
|
+{
|
|
+ return DSA_TAG_PROTO_OOB;
|
|
+}
|
|
+
|
|
+static struct phylink_pcs *
|
|
+qca8k_ipq4019_phylink_mac_select_pcs(struct dsa_switch *ds, int port,
|
|
+ phy_interface_t interface)
|
|
+{
|
|
+ struct qca8k_priv *priv = ds->priv;
|
|
+ struct phylink_pcs *pcs = NULL;
|
|
+
|
|
+ switch (interface) {
|
|
+ case PHY_INTERFACE_MODE_PSGMII:
|
|
+ switch (port) {
|
|
+ case 0:
|
|
+ pcs = &priv->pcs_port_0.pcs;
|
|
+ break;
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return pcs;
|
|
+}
|
|
+
|
|
+static int qca8k_ipq4019_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
|
|
+ phy_interface_t interface,
|
|
+ const unsigned long *advertising,
|
|
+ bool permit_pause_to_mac)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void qca8k_ipq4019_pcs_an_restart(struct phylink_pcs *pcs)
|
|
+{
|
|
+}
|
|
+
|
|
+static struct qca8k_pcs *pcs_to_qca8k_pcs(struct phylink_pcs *pcs)
|
|
+{
|
|
+ return container_of(pcs, struct qca8k_pcs, pcs);
|
|
+}
|
|
+
|
|
+static void qca8k_ipq4019_pcs_get_state(struct phylink_pcs *pcs,
|
|
+ struct phylink_link_state *state)
|
|
+{
|
|
+ struct qca8k_priv *priv = pcs_to_qca8k_pcs(pcs)->priv;
|
|
+ int port = pcs_to_qca8k_pcs(pcs)->port;
|
|
+ u32 reg;
|
|
+ int ret;
|
|
+
|
|
+ ret = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port), ®);
|
|
+ if (ret < 0) {
|
|
+ state->link = false;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP);
|
|
+ state->an_complete = state->link;
|
|
+ state->duplex = (reg & QCA8K_PORT_STATUS_DUPLEX) ? DUPLEX_FULL :
|
|
+ DUPLEX_HALF;
|
|
+
|
|
+ switch (reg & QCA8K_PORT_STATUS_SPEED) {
|
|
+ case QCA8K_PORT_STATUS_SPEED_10:
|
|
+ state->speed = SPEED_10;
|
|
+ break;
|
|
+ case QCA8K_PORT_STATUS_SPEED_100:
|
|
+ state->speed = SPEED_100;
|
|
+ break;
|
|
+ case QCA8K_PORT_STATUS_SPEED_1000:
|
|
+ state->speed = SPEED_1000;
|
|
+ break;
|
|
+ default:
|
|
+ state->speed = SPEED_UNKNOWN;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (reg & QCA8K_PORT_STATUS_RXFLOW)
|
|
+ state->pause |= MLO_PAUSE_RX;
|
|
+ if (reg & QCA8K_PORT_STATUS_TXFLOW)
|
|
+ state->pause |= MLO_PAUSE_TX;
|
|
+}
|
|
+
|
|
+static const struct phylink_pcs_ops qca8k_pcs_ops = {
|
|
+ .pcs_get_state = qca8k_ipq4019_pcs_get_state,
|
|
+ .pcs_config = qca8k_ipq4019_pcs_config,
|
|
+ .pcs_an_restart = qca8k_ipq4019_pcs_an_restart,
|
|
+};
|
|
+
|
|
+static void qca8k_ipq4019_setup_pcs(struct qca8k_priv *priv,
|
|
+ struct qca8k_pcs *qpcs,
|
|
+ int port)
|
|
+{
|
|
+ qpcs->pcs.ops = &qca8k_pcs_ops;
|
|
+
|
|
+ /* We don't have interrupts for link changes, so we need to poll */
|
|
+ qpcs->pcs.poll = true;
|
|
+ qpcs->priv = priv;
|
|
+ qpcs->port = port;
|
|
+}
|
|
+
|
|
+static void qca8k_ipq4019_phylink_get_caps(struct dsa_switch *ds, int port,
|
|
+ struct phylink_config *config)
|
|
+{
|
|
+ switch (port) {
|
|
+ case 0: /* CPU port */
|
|
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
|
|
+ config->supported_interfaces);
|
|
+ break;
|
|
+
|
|
+ case 1:
|
|
+ case 2:
|
|
+ case 3:
|
|
+ __set_bit(PHY_INTERFACE_MODE_PSGMII,
|
|
+ config->supported_interfaces);
|
|
+ break;
|
|
+ case 4:
|
|
+ case 5:
|
|
+ phy_interface_set_rgmii(config->supported_interfaces);
|
|
+ __set_bit(PHY_INTERFACE_MODE_PSGMII,
|
|
+ config->supported_interfaces);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
|
|
+ MAC_10 | MAC_100 | MAC_1000FD;
|
|
+}
|
|
+
|
|
+static void
|
|
+qca8k_phylink_ipq4019_mac_link_down(struct dsa_switch *ds, int port,
|
|
+ unsigned int mode,
|
|
+ phy_interface_t interface)
|
|
+{
|
|
+ struct qca8k_priv *priv = ds->priv;
|
|
+
|
|
+ qca8k_port_set_status(priv, port, 0);
|
|
+}
|
|
+
|
|
+static void
|
|
+qca8k_phylink_ipq4019_mac_link_up(struct dsa_switch *ds, int port,
|
|
+ unsigned int mode, phy_interface_t interface,
|
|
+ struct phy_device *phydev, int speed,
|
|
+ int duplex, bool tx_pause, bool rx_pause)
|
|
+{
|
|
+ struct qca8k_priv *priv = ds->priv;
|
|
+ u32 reg;
|
|
+
|
|
+ if (phylink_autoneg_inband(mode)) {
|
|
+ reg = QCA8K_PORT_STATUS_LINK_AUTO;
|
|
+ } else {
|
|
+ switch (speed) {
|
|
+ case SPEED_10:
|
|
+ reg = QCA8K_PORT_STATUS_SPEED_10;
|
|
+ break;
|
|
+ case SPEED_100:
|
|
+ reg = QCA8K_PORT_STATUS_SPEED_100;
|
|
+ break;
|
|
+ case SPEED_1000:
|
|
+ reg = QCA8K_PORT_STATUS_SPEED_1000;
|
|
+ break;
|
|
+ default:
|
|
+ reg = QCA8K_PORT_STATUS_LINK_AUTO;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (duplex == DUPLEX_FULL)
|
|
+ reg |= QCA8K_PORT_STATUS_DUPLEX;
|
|
+
|
|
+ if (rx_pause || dsa_is_cpu_port(ds, port))
|
|
+ reg |= QCA8K_PORT_STATUS_RXFLOW;
|
|
+
|
|
+ if (tx_pause || dsa_is_cpu_port(ds, port))
|
|
+ reg |= QCA8K_PORT_STATUS_TXFLOW;
|
|
+ }
|
|
+
|
|
+ reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC;
|
|
+
|
|
+ qca8k_write(priv, QCA8K_REG_PORT_STATUS(port), reg);
|
|
+}
|
|
+
|
|
+static int psgmii_vco_calibrate(struct qca8k_priv *priv)
|
|
+{
|
|
+ int val, ret;
|
|
+
|
|
+ if (!priv->psgmii_ethphy) {
|
|
+ dev_err(priv->dev, "PSGMII eth PHY missing, calibration failed!\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ /* Fix PSGMII RX 20bit */
|
|
+ ret = phy_write(priv->psgmii_ethphy, MII_BMCR, 0x5b);
|
|
+ /* Reset PHY PSGMII */
|
|
+ ret = phy_write(priv->psgmii_ethphy, MII_BMCR, 0x1b);
|
|
+ /* Release PHY PSGMII reset */
|
|
+ ret = phy_write(priv->psgmii_ethphy, MII_BMCR, 0x5b);
|
|
+
|
|
+ /* Poll for VCO PLL calibration finish - Malibu(QCA8075) */
|
|
+ ret = phy_read_mmd_poll_timeout(priv->psgmii_ethphy,
|
|
+ MDIO_MMD_PMAPMD,
|
|
+ 0x28, val,
|
|
+ (val & BIT(0)),
|
|
+ 10000, 1000000,
|
|
+ false);
|
|
+ if (ret) {
|
|
+ dev_err(priv->dev, "QCA807x PSGMII VCO calibration PLL not ready\n");
|
|
+ return ret;
|
|
+ }
|
|
+ mdelay(50);
|
|
+
|
|
+ /* Freeze PSGMII RX CDR */
|
|
+ ret = phy_write(priv->psgmii_ethphy, MII_RESV2, 0x2230);
|
|
+
|
|
+ /* 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 - Dakota(IPQ40xx) */
|
|
+ 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(priv->dev, "IPQ PSGMIIPHY VCO calibration PLL not ready\n");
|
|
+ return ret;
|
|
+ }
|
|
+ mdelay(50);
|
|
+
|
|
+ /* 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);
|
|
+ mdelay(200);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void
|
|
+qca8k_switch_port_loopback_on_off(struct qca8k_priv *priv, int port, int on)
|
|
+{
|
|
+ u32 val = QCA8K_PORT_LOOKUP_LOOPBACK_EN;
|
|
+
|
|
+ if (on == 0)
|
|
+ val = 0;
|
|
+
|
|
+ qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
|
|
+ QCA8K_PORT_LOOKUP_LOOPBACK_EN, val);
|
|
+}
|
|
+
|
|
+static int
|
|
+qca8k_wait_for_phy_link_state(struct phy_device *phy, int need_status)
|
|
+{
|
|
+ int a;
|
|
+ u16 status;
|
|
+
|
|
+ for (a = 0; a < 100; a++) {
|
|
+ status = phy_read(phy, MII_QCA8075_SSTATUS);
|
|
+ status &= QCA8075_PHY_SPEC_STATUS_LINK;
|
|
+ status = !!status;
|
|
+ if (status == need_status)
|
|
+ return 0;
|
|
+ mdelay(8);
|
|
+ }
|
|
+
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static void
|
|
+qca8k_phy_loopback_on_off(struct qca8k_priv *priv, struct phy_device *phy,
|
|
+ int sw_port, int on)
|
|
+{
|
|
+ if (on) {
|
|
+ phy_write(phy, MII_BMCR, BMCR_ANENABLE | BMCR_RESET);
|
|
+ phy_modify(phy, MII_BMCR, BMCR_PDOWN, BMCR_PDOWN);
|
|
+ qca8k_wait_for_phy_link_state(phy, 0);
|
|
+ qca8k_write(priv, QCA8K_REG_PORT_STATUS(sw_port), 0);
|
|
+ phy_write(phy, MII_BMCR,
|
|
+ BMCR_SPEED1000 |
|
|
+ BMCR_FULLDPLX |
|
|
+ BMCR_LOOPBACK);
|
|
+ qca8k_wait_for_phy_link_state(phy, 1);
|
|
+ qca8k_write(priv, QCA8K_REG_PORT_STATUS(sw_port),
|
|
+ QCA8K_PORT_STATUS_SPEED_1000 |
|
|
+ QCA8K_PORT_STATUS_TXMAC |
|
|
+ QCA8K_PORT_STATUS_RXMAC |
|
|
+ QCA8K_PORT_STATUS_DUPLEX);
|
|
+ qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(sw_port),
|
|
+ QCA8K_PORT_LOOKUP_STATE_FORWARD,
|
|
+ QCA8K_PORT_LOOKUP_STATE_FORWARD);
|
|
+ } else { /* off */
|
|
+ qca8k_write(priv, QCA8K_REG_PORT_STATUS(sw_port), 0);
|
|
+ qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(sw_port),
|
|
+ QCA8K_PORT_LOOKUP_STATE_DISABLED,
|
|
+ QCA8K_PORT_LOOKUP_STATE_DISABLED);
|
|
+ phy_write(phy, MII_BMCR, BMCR_SPEED1000 | BMCR_ANENABLE | BMCR_RESET);
|
|
+ /* turn off the power of the phys - so that unused
|
|
+ ports do not raise links */
|
|
+ phy_modify(phy, MII_BMCR, BMCR_PDOWN, BMCR_PDOWN);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+qca8k_phy_pkt_gen_prep(struct qca8k_priv *priv, struct phy_device *phy,
|
|
+ int pkts_num, int on)
|
|
+{
|
|
+ if (on) {
|
|
+ /* enable CRC checker and packets counters */
|
|
+ phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_CRC_AND_PKTS_COUNT, 0);
|
|
+ phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_CRC_AND_PKTS_COUNT,
|
|
+ QCA8075_MMD7_CNT_FRAME_CHK_EN | QCA8075_MMD7_CNT_SELFCLR);
|
|
+ qca8k_wait_for_phy_link_state(phy, 1);
|
|
+ /* packet number */
|
|
+ phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_PKT_GEN_PKT_NUMB, pkts_num);
|
|
+ /* pkt size - 1504 bytes + 20 bytes */
|
|
+ phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_PKT_GEN_PKT_SIZE, 1504);
|
|
+ } else { /* off */
|
|
+ /* packet number */
|
|
+ phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_PKT_GEN_PKT_NUMB, 0);
|
|
+ /* disable CRC checker and packet counter */
|
|
+ phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_CRC_AND_PKTS_COUNT, 0);
|
|
+ /* disable traffic gen */
|
|
+ phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_PKT_GEN_CTRL, 0);
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+qca8k_wait_for_phy_pkt_gen_fin(struct qca8k_priv *priv, struct phy_device *phy)
|
|
+{
|
|
+ int val;
|
|
+ /* wait for all traffic end: 4096(pkt num)*1524(size)*8ns(125MHz)=49938us */
|
|
+ phy_read_mmd_poll_timeout(phy, MDIO_MMD_AN, QCA8075_MMD7_PKT_GEN_CTRL,
|
|
+ val, !(val & QCA8075_MMD7_PKT_GEN_INPROGR),
|
|
+ 50000, 1000000, true);
|
|
+}
|
|
+
|
|
+static void
|
|
+qca8k_start_phy_pkt_gen(struct phy_device *phy)
|
|
+{
|
|
+ /* start traffic gen */
|
|
+ phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_PKT_GEN_CTRL,
|
|
+ QCA8075_MMD7_PKT_GEN_START | QCA8075_MMD7_PKT_GEN_INPROGR);
|
|
+}
|
|
+
|
|
+static int
|
|
+qca8k_start_all_phys_pkt_gens(struct qca8k_priv *priv)
|
|
+{
|
|
+ struct phy_device *phy;
|
|
+ phy = phy_device_create(priv->bus, QCA8075_MDIO_BRDCST_PHY_ADDR,
|
|
+ 0, 0, NULL);
|
|
+ if (!phy) {
|
|
+ dev_err(priv->dev, "unable to create mdio broadcast PHY(0x%x)\n",
|
|
+ QCA8075_MDIO_BRDCST_PHY_ADDR);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ qca8k_start_phy_pkt_gen(phy);
|
|
+
|
|
+ phy_device_free(phy);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+qca8k_get_phy_pkt_gen_test_result(struct phy_device *phy, int pkts_num)
|
|
+{
|
|
+ u32 tx_ok, tx_error;
|
|
+ u32 rx_ok, rx_error;
|
|
+ u32 tx_ok_high16;
|
|
+ u32 rx_ok_high16;
|
|
+ u32 tx_all_ok, rx_all_ok;
|
|
+
|
|
+ /* check counters */
|
|
+ tx_ok = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_EG_FRAME_RECV_CNT_LO);
|
|
+ tx_ok_high16 = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_EG_FRAME_RECV_CNT_HI);
|
|
+ tx_error = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_EG_FRAME_ERR_CNT);
|
|
+ rx_ok = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_IG_FRAME_RECV_CNT_LO);
|
|
+ rx_ok_high16 = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_IG_FRAME_RECV_CNT_HI);
|
|
+ rx_error = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_IG_FRAME_ERR_CNT);
|
|
+ tx_all_ok = tx_ok + (tx_ok_high16 << 16);
|
|
+ rx_all_ok = rx_ok + (rx_ok_high16 << 16);
|
|
+
|
|
+ if (tx_all_ok < pkts_num)
|
|
+ return -1;
|
|
+ if(rx_all_ok < pkts_num)
|
|
+ return -2;
|
|
+ if(tx_error)
|
|
+ return -3;
|
|
+ if(rx_error)
|
|
+ return -4;
|
|
+ return 0; /* test is ok */
|
|
+}
|
|
+
|
|
+static
|
|
+void qca8k_phy_broadcast_write_on_off(struct qca8k_priv *priv,
|
|
+ struct phy_device *phy, int on)
|
|
+{
|
|
+ u32 val;
|
|
+
|
|
+ val = phy_read_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_MDIO_BRDCST_WRITE);
|
|
+
|
|
+ if (on == 0)
|
|
+ val &= ~QCA8075_MMD7_MDIO_BRDCST_WRITE_EN;
|
|
+ else
|
|
+ val |= QCA8075_MMD7_MDIO_BRDCST_WRITE_EN;
|
|
+
|
|
+ phy_write_mmd(phy, MDIO_MMD_AN, QCA8075_MMD7_MDIO_BRDCST_WRITE, val);
|
|
+}
|
|
+
|
|
+static int
|
|
+qca8k_test_dsa_port_for_errors(struct qca8k_priv *priv, struct phy_device *phy,
|
|
+ int port, int test_phase)
|
|
+{
|
|
+ int res = 0;
|
|
+ const int test_pkts_num = QCA8075_PKT_GEN_PKTS_COUNT;
|
|
+
|
|
+ if (test_phase == 1) { /* start test preps */
|
|
+ qca8k_phy_loopback_on_off(priv, phy, port, 1);
|
|
+ qca8k_switch_port_loopback_on_off(priv, port, 1);
|
|
+ qca8k_phy_broadcast_write_on_off(priv, phy, 1);
|
|
+ qca8k_phy_pkt_gen_prep(priv, phy, test_pkts_num, 1);
|
|
+ } else if (test_phase == 2) {
|
|
+ /* wait for test results, collect it and cleanup */
|
|
+ qca8k_wait_for_phy_pkt_gen_fin(priv, phy);
|
|
+ res = qca8k_get_phy_pkt_gen_test_result(phy, test_pkts_num);
|
|
+ qca8k_phy_pkt_gen_prep(priv, phy, test_pkts_num, 0);
|
|
+ qca8k_phy_broadcast_write_on_off(priv, phy, 0);
|
|
+ qca8k_switch_port_loopback_on_off(priv, port, 0);
|
|
+ qca8k_phy_loopback_on_off(priv, phy, port, 0);
|
|
+ }
|
|
+
|
|
+ return res;
|
|
+}
|
|
+
|
|
+static int
|
|
+qca8k_do_dsa_sw_ports_self_test(struct qca8k_priv *priv, int parallel_test)
|
|
+{
|
|
+ struct device_node *dn = priv->dev->of_node;
|
|
+ struct device_node *ports, *port;
|
|
+ struct device_node *phy_dn;
|
|
+ struct phy_device *phy;
|
|
+ int reg, err = 0, test_phase;
|
|
+ u32 tests_result = 0;
|
|
+
|
|
+ ports = of_get_child_by_name(dn, "ports");
|
|
+ if (!ports) {
|
|
+ dev_err(priv->dev, "no ports child node found\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ for (test_phase = 1; test_phase <= 2; test_phase++) {
|
|
+ if (parallel_test && test_phase == 2) {
|
|
+ err = qca8k_start_all_phys_pkt_gens(priv);
|
|
+ if (err)
|
|
+ goto error;
|
|
+ }
|
|
+ for_each_available_child_of_node(ports, port) {
|
|
+ err = of_property_read_u32(port, "reg", ®);
|
|
+ if (err)
|
|
+ goto error;
|
|
+ if (reg >= QCA8K_NUM_PORTS) {
|
|
+ err = -EINVAL;
|
|
+ goto error;
|
|
+ }
|
|
+ phy_dn = of_parse_phandle(port, "phy-handle", 0);
|
|
+ if (phy_dn) {
|
|
+ phy = of_phy_find_device(phy_dn);
|
|
+ of_node_put(phy_dn);
|
|
+ if (phy) {
|
|
+ int result;
|
|
+ result = qca8k_test_dsa_port_for_errors(priv,
|
|
+ phy, reg, test_phase);
|
|
+ if (!parallel_test && test_phase == 1)
|
|
+ qca8k_start_phy_pkt_gen(phy);
|
|
+ put_device(&phy->mdio.dev);
|
|
+ if (test_phase == 2) {
|
|
+ tests_result <<= 1;
|
|
+ if (result)
|
|
+ tests_result |= 1;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+end:
|
|
+ of_node_put(ports);
|
|
+ qca8k_fdb_flush(priv);
|
|
+ return tests_result;
|
|
+error:
|
|
+ tests_result |= 0xf000;
|
|
+ goto end;
|
|
+}
|
|
+
|
|
+static int
|
|
+psgmii_vco_calibrate_and_test(struct dsa_switch *ds)
|
|
+{
|
|
+ int ret, a, test_result;
|
|
+ struct qca8k_priv *priv = ds->priv;
|
|
+
|
|
+ for (a = 0; a <= QCA8K_PSGMII_CALB_NUM; a++) {
|
|
+ ret = psgmii_vco_calibrate(priv);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ /* first we run serial test */
|
|
+ test_result = qca8k_do_dsa_sw_ports_self_test(priv, 0);
|
|
+ /* and if it is ok then we run the test in parallel */
|
|
+ if (!test_result)
|
|
+ test_result = qca8k_do_dsa_sw_ports_self_test(priv, 1);
|
|
+ if (!test_result) {
|
|
+ if (a > 0) {
|
|
+ dev_warn(priv->dev, "PSGMII work was stabilized after %d "
|
|
+ "calibration retries !\n", a);
|
|
+ }
|
|
+ return 0;
|
|
+ } else {
|
|
+ schedule();
|
|
+ if (a > 0 && a % 10 == 0) {
|
|
+ dev_err(priv->dev, "PSGMII work is unstable !!! "
|
|
+ "Let's try to wait a bit ... %d\n", a);
|
|
+ set_current_state(TASK_INTERRUPTIBLE);
|
|
+ schedule_timeout(msecs_to_jiffies(a * 100));
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ dev_err(priv->dev, "PSGMII work is unstable !!! "
|
|
+ "Repeated recalibration attempts did not help(0x%x) !\n",
|
|
+ test_result);
|
|
+
|
|
+ return -EFAULT;
|
|
+}
|
|
+
|
|
+static int
|
|
+ipq4019_psgmii_configure(struct dsa_switch *ds)
|
|
+{
|
|
+ struct qca8k_priv *priv = ds->priv;
|
|
+ int ret;
|
|
+
|
|
+ if (!priv->psgmii_calibrated) {
|
|
+ dev_info(ds->dev, "PSGMII calibration!\n");
|
|
+ ret = psgmii_vco_calibrate_and_test(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
|
|
+qca8k_phylink_ipq4019_mac_config(struct dsa_switch *ds, int port,
|
|
+ unsigned int mode,
|
|
+ const struct phylink_link_state *state)
|
|
+{
|
|
+ struct qca8k_priv *priv = ds->priv;
|
|
+
|
|
+ switch (port) {
|
|
+ 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:
|
|
+ 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) {
|
|
+ regmap_set_bits(priv->regmap,
|
|
+ QCA8K_IPQ4019_REG_RGMII_CTRL,
|
|
+ QCA8K_IPQ4019_RGMII_CTRL_CLK);
|
|
+ }
|
|
+
|
|
+ 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, "%s: unsupported port: %i\n", __func__, port);
|
|
+ return;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int
|
|
+qca8k_ipq4019_setup_port(struct dsa_switch *ds, int port)
|
|
+{
|
|
+ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
|
|
+ int ret;
|
|
+
|
|
+ /* 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(port),
|
|
+ QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Disable CPU ARP Auto-learning by default */
|
|
+ ret = regmap_clear_bits(priv->regmap,
|
|
+ QCA8K_PORT_LOOKUP_CTRL(port),
|
|
+ QCA8K_PORT_LOOKUP_LEARN);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Individual user ports get connected to CPU port only */
|
|
+ if (dsa_is_user_port(ds, port)) {
|
|
+ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
|
|
+ QCA8K_PORT_LOOKUP_MEMBER,
|
|
+ BIT(QCA8K_IPQ4019_CPU_PORT));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Enable ARP Auto-learning by default */
|
|
+ ret = regmap_set_bits(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(port),
|
|
+ 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(port),
|
|
+ QCA8K_EGREES_VLAN_PORT_MASK(port),
|
|
+ QCA8K_EGREES_VLAN_PORT(port, QCA8K_PORT_VID_DEF));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ 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;
|
|
+}
|
|
+
|
|
+static int
|
|
+qca8k_ipq4019_setup(struct dsa_switch *ds)
|
|
+{
|
|
+ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
|
|
+ int ret, i;
|
|
+
|
|
+ /* Make sure that port 0 is the cpu port */
|
|
+ if (!dsa_is_cpu_port(ds, QCA8K_IPQ4019_CPU_PORT)) {
|
|
+ dev_err(priv->dev, "port %d is not the CPU port",
|
|
+ QCA8K_IPQ4019_CPU_PORT);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ qca8k_ipq4019_setup_pcs(priv, &priv->pcs_port_0, 0);
|
|
+
|
|
+ /* Enable CPU Port */
|
|
+ ret = regmap_set_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0,
|
|
+ QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
|
|
+ if (ret) {
|
|
+ dev_err(priv->dev, "failed enabling CPU port");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Enable MIB counters */
|
|
+ ret = qca8k_mib_init(priv);
|
|
+ if (ret)
|
|
+ dev_warn(priv->dev, "MIB init failed");
|
|
+
|
|
+ /* Disable forwarding by default on all ports */
|
|
+ for (i = 0; i < QCA8K_IPQ4019_NUM_PORTS; i++) {
|
|
+ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
|
|
+ QCA8K_PORT_LOOKUP_MEMBER, 0);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Enable QCA header mode on the CPU port */
|
|
+ ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(QCA8K_IPQ4019_CPU_PORT),
|
|
+ FIELD_PREP(QCA8K_PORT_HDR_CTRL_TX_MASK, QCA8K_PORT_HDR_CTRL_ALL) |
|
|
+ FIELD_PREP(QCA8K_PORT_HDR_CTRL_RX_MASK, QCA8K_PORT_HDR_CTRL_ALL));
|
|
+ if (ret) {
|
|
+ dev_err(priv->dev, "failed enabling QCA header mode");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Disable MAC by default on all ports */
|
|
+ for (i = 0; i < QCA8K_IPQ4019_NUM_PORTS; i++) {
|
|
+ if (dsa_is_user_port(ds, 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,
|
|
+ FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_MASK, BIT(QCA8K_IPQ4019_CPU_PORT)) |
|
|
+ FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_BC_DP_MASK, BIT(QCA8K_IPQ4019_CPU_PORT)) |
|
|
+ FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_MC_DP_MASK, BIT(QCA8K_IPQ4019_CPU_PORT)) |
|
|
+ FIELD_PREP(QCA8K_GLOBAL_FW_CTRL1_UC_DP_MASK, BIT(QCA8K_IPQ4019_CPU_PORT)));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Setup connection between CPU port & user ports */
|
|
+ for (i = 0; i < QCA8K_IPQ4019_NUM_PORTS; i++) {
|
|
+ ret = qca8k_ipq4019_setup_port(ds, i);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ /* Setup our port MTUs to match power on defaults */
|
|
+ ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
|
|
+ if (ret)
|
|
+ dev_warn(priv->dev, "failed setting MTU settings");
|
|
+
|
|
+ /* Flush the FDB table */
|
|
+ qca8k_fdb_flush(priv);
|
|
+
|
|
+ /* Set min a max ageing value supported */
|
|
+ ds->ageing_time_min = 7000;
|
|
+ ds->ageing_time_max = 458745000;
|
|
+
|
|
+ /* Set max number of LAGs supported */
|
|
+ ds->num_lag_ids = QCA8K_NUM_LAGS;
|
|
+
|
|
+ /* CPU port HW learning doesnt work correctly, so let DSA handle it */
|
|
+ ds->assisted_learning_on_cpu_port = true;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct dsa_switch_ops qca8k_ipq4019_switch_ops = {
|
|
+ .get_tag_protocol = qca8k_ipq4019_get_tag_protocol,
|
|
+ .setup = qca8k_ipq4019_setup,
|
|
+ .get_strings = qca8k_get_strings,
|
|
+ .get_ethtool_stats = qca8k_get_ethtool_stats,
|
|
+ .get_sset_count = qca8k_get_sset_count,
|
|
+ .set_ageing_time = qca8k_set_ageing_time,
|
|
+ .get_mac_eee = qca8k_get_mac_eee,
|
|
+ .set_mac_eee = qca8k_set_mac_eee,
|
|
+ .port_enable = qca8k_port_enable,
|
|
+ .port_disable = qca8k_port_disable,
|
|
+ .port_change_mtu = qca8k_port_change_mtu,
|
|
+ .port_max_mtu = qca8k_port_max_mtu,
|
|
+ .port_stp_state_set = qca8k_port_stp_state_set,
|
|
+ .port_bridge_join = qca8k_port_bridge_join,
|
|
+ .port_bridge_leave = qca8k_port_bridge_leave,
|
|
+ .port_fast_age = qca8k_port_fast_age,
|
|
+ .port_fdb_add = qca8k_port_fdb_add,
|
|
+ .port_fdb_del = qca8k_port_fdb_del,
|
|
+ .port_fdb_dump = qca8k_port_fdb_dump,
|
|
+ .port_mdb_add = qca8k_port_mdb_add,
|
|
+ .port_mdb_del = qca8k_port_mdb_del,
|
|
+ .port_mirror_add = qca8k_port_mirror_add,
|
|
+ .port_mirror_del = qca8k_port_mirror_del,
|
|
+ .port_vlan_filtering = qca8k_port_vlan_filtering,
|
|
+ .port_vlan_add = qca8k_port_vlan_add,
|
|
+ .port_vlan_del = qca8k_port_vlan_del,
|
|
+ .phylink_mac_select_pcs = qca8k_ipq4019_phylink_mac_select_pcs,
|
|
+ .phylink_get_caps = qca8k_ipq4019_phylink_get_caps,
|
|
+ .phylink_mac_config = qca8k_phylink_ipq4019_mac_config,
|
|
+ .phylink_mac_link_down = qca8k_phylink_ipq4019_mac_link_down,
|
|
+ .phylink_mac_link_up = qca8k_phylink_ipq4019_mac_link_up,
|
|
+ .port_lag_join = qca8k_port_lag_join,
|
|
+ .port_lag_leave = qca8k_port_lag_leave,
|
|
+};
|
|
+
|
|
+static const struct qca8k_match_data ipq4019 = {
|
|
+ .id = QCA8K_ID_IPQ4019,
|
|
+ .mib_count = QCA8K_QCA833X_MIB_COUNT,
|
|
+};
|
|
+
|
|
+static int
|
|
+qca8k_ipq4019_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device *dev = &pdev->dev;
|
|
+ struct qca8k_priv *priv;
|
|
+ void __iomem *base, *psgmii;
|
|
+ struct device_node *np = dev->of_node, *mdio_np, *psgmii_ethphy_np;
|
|
+ int ret;
|
|
+
|
|
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
+ if (!priv)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ priv->dev = dev;
|
|
+ priv->info = &ipq4019;
|
|
+
|
|
+ /* Start by setting up the register mapping */
|
|
+ base = devm_platform_ioremap_resource_byname(pdev, "base");
|
|
+ if (IS_ERR(base))
|
|
+ return PTR_ERR(base);
|
|
+
|
|
+ priv->regmap = devm_regmap_init_mmio(dev, base,
|
|
+ &qca8k_ipq4019_regmap_config);
|
|
+ if (IS_ERR(priv->regmap)) {
|
|
+ ret = PTR_ERR(priv->regmap);
|
|
+ dev_err(dev, "base regmap initialization failed, %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ psgmii = devm_platform_ioremap_resource_byname(pdev, "psgmii_phy");
|
|
+ if (IS_ERR(psgmii))
|
|
+ return PTR_ERR(psgmii);
|
|
+
|
|
+ priv->psgmii = devm_regmap_init_mmio(dev, psgmii,
|
|
+ &qca8k_ipq4019_psgmii_phy_regmap_config);
|
|
+ if (IS_ERR(priv->psgmii)) {
|
|
+ ret = PTR_ERR(priv->psgmii);
|
|
+ dev_err(dev, "PSGMII regmap initialization failed, %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ mdio_np = of_parse_phandle(np, "mdio", 0);
|
|
+ if (!mdio_np) {
|
|
+ dev_err(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(dev, "unable to find MDIO bus\n");
|
|
+ return -EPROBE_DEFER;
|
|
+ }
|
|
+
|
|
+ psgmii_ethphy_np = of_parse_phandle(np, "psgmii-ethphy", 0);
|
|
+ if (!psgmii_ethphy_np) {
|
|
+ dev_dbg(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(dev, "unable to get PSGMII eth PHY\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Check the detected switch id */
|
|
+ ret = qca8k_read_switch_id(priv);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL);
|
|
+ if (!priv->ds)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ priv->ds->dev = dev;
|
|
+ priv->ds->num_ports = QCA8K_IPQ4019_NUM_PORTS;
|
|
+ priv->ds->priv = priv;
|
|
+ priv->ds->ops = &qca8k_ipq4019_switch_ops;
|
|
+ mutex_init(&priv->reg_mutex);
|
|
+ platform_set_drvdata(pdev, priv);
|
|
+
|
|
+ return dsa_register_switch(priv->ds);
|
|
+}
|
|
+
|
|
+static int
|
|
+qca8k_ipq4019_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct qca8k_priv *priv = dev_get_drvdata(&pdev->dev);
|
|
+ int i;
|
|
+
|
|
+ if (!priv)
|
|
+ return 0;
|
|
+
|
|
+ for (i = 0; i < QCA8K_IPQ4019_NUM_PORTS; i++)
|
|
+ qca8k_port_set_status(priv, i, 0);
|
|
+
|
|
+ dsa_unregister_switch(priv->ds);
|
|
+
|
|
+ platform_set_drvdata(pdev, NULL);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id qca8k_ipq4019_of_match[] = {
|
|
+ { .compatible = "qca,ipq4019-qca8337n", },
|
|
+ { /* sentinel */ },
|
|
+};
|
|
+
|
|
+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,
|
|
+ },
|
|
+};
|
|
+
|
|
+module_platform_driver(qca8k_ipq4019_driver);
|
|
+
|
|
+MODULE_AUTHOR("Mathieu Olivari, John Crispin <john@phrozen.org>");
|
|
+MODULE_AUTHOR("Gabor Juhos <j4g8y7@gmail.com>, Robert Marko <robert.marko@sartura.hr>");
|
|
+MODULE_DESCRIPTION("Qualcomm IPQ4019 built-in switch driver");
|
|
+MODULE_LICENSE("GPL");
|
|
--- a/drivers/net/dsa/qca/qca8k.h
|
|
+++ b/drivers/net/dsa/qca/qca8k.h
|
|
@@ -19,7 +19,10 @@
|
|
#define QCA8K_ETHERNET_TIMEOUT 5
|
|
|
|
#define QCA8K_NUM_PORTS 7
|
|
+#define QCA8K_IPQ4019_NUM_PORTS 6
|
|
#define QCA8K_NUM_CPU_PORTS 2
|
|
+#define QCA8K_IPQ4019_NUM_CPU_PORTS 1
|
|
+#define QCA8K_IPQ4019_CPU_PORT 0
|
|
#define QCA8K_MAX_MTU 9000
|
|
#define QCA8K_NUM_LAGS 4
|
|
#define QCA8K_NUM_PORTS_FOR_LAG 4
|
|
@@ -28,6 +31,7 @@
|
|
#define QCA8K_ID_QCA8327 0x12
|
|
#define PHY_ID_QCA8337 0x004dd036
|
|
#define QCA8K_ID_QCA8337 0x13
|
|
+#define QCA8K_ID_IPQ4019 0x14
|
|
|
|
#define QCA8K_QCA832X_MIB_COUNT 39
|
|
#define QCA8K_QCA833X_MIB_COUNT 41
|
|
@@ -265,6 +269,7 @@
|
|
#define QCA8K_PORT_LOOKUP_STATE_LEARNING QCA8K_PORT_LOOKUP_STATE(0x3)
|
|
#define QCA8K_PORT_LOOKUP_STATE_FORWARD QCA8K_PORT_LOOKUP_STATE(0x4)
|
|
#define QCA8K_PORT_LOOKUP_LEARN BIT(20)
|
|
+#define QCA8K_PORT_LOOKUP_LOOPBACK_EN BIT(21)
|
|
#define QCA8K_PORT_LOOKUP_ING_MIRROR_EN BIT(25)
|
|
|
|
#define QCA8K_REG_GOL_TRUNK_CTRL0 0x700
|
|
@@ -341,6 +346,53 @@
|
|
#define MII_ATH_MMD_ADDR 0x0d
|
|
#define MII_ATH_MMD_DATA 0x0e
|
|
|
|
+/* IPQ4019 PSGMII PHY registers */
|
|
+#define QCA8K_IPQ4019_REG_RGMII_CTRL 0x004
|
|
+#define QCA8K_IPQ4019_RGMII_CTRL_RGMII_RXC GENMASK(1, 0)
|
|
+#define QCA8K_IPQ4019_RGMII_CTRL_RGMII_TXC GENMASK(9, 8)
|
|
+/* Some kind of CLK selection
|
|
+ * 0: gcc_ess_dly2ns
|
|
+ * 1: gcc_ess_clk
|
|
+ */
|
|
+#define QCA8K_IPQ4019_RGMII_CTRL_CLK BIT(10)
|
|
+#define QCA8K_IPQ4019_RGMII_CTRL_DELAY_RMII0 GENMASK(17, 16)
|
|
+#define QCA8K_IPQ4019_RGMII_CTRL_INVERT_RMII0_REF_CLK BIT(18)
|
|
+#define QCA8K_IPQ4019_RGMII_CTRL_DELAY_RMII1 GENMASK(20, 19)
|
|
+#define QCA8K_IPQ4019_RGMII_CTRL_INVERT_RMII1_REF_CLK BIT(21)
|
|
+#define QCA8K_IPQ4019_RGMII_CTRL_INVERT_RMII0_MASTER_EN BIT(24)
|
|
+#define QCA8K_IPQ4019_RGMII_CTRL_INVERT_RMII1_MASTER_EN BIT(25)
|
|
+
|
|
+#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)
|
|
+
|
|
+#define QCA8K_PSGMII_CALB_NUM 100
|
|
+#define MII_QCA8075_SSTATUS 0x11
|
|
+#define QCA8075_PHY_SPEC_STATUS_LINK BIT(10)
|
|
+#define QCA8075_MMD7_CRC_AND_PKTS_COUNT 0x8029
|
|
+#define QCA8075_MMD7_PKT_GEN_PKT_NUMB 0x8021
|
|
+#define QCA8075_MMD7_PKT_GEN_PKT_SIZE 0x8062
|
|
+#define QCA8075_MMD7_PKT_GEN_CTRL 0x8020
|
|
+#define QCA8075_MMD7_CNT_SELFCLR BIT(1)
|
|
+#define QCA8075_MMD7_CNT_FRAME_CHK_EN BIT(0)
|
|
+#define QCA8075_MMD7_PKT_GEN_START BIT(13)
|
|
+#define QCA8075_MMD7_PKT_GEN_INPROGR BIT(15)
|
|
+#define QCA8075_MMD7_IG_FRAME_RECV_CNT_HI 0x802a
|
|
+#define QCA8075_MMD7_IG_FRAME_RECV_CNT_LO 0x802b
|
|
+#define QCA8075_MMD7_IG_FRAME_ERR_CNT 0x802c
|
|
+#define QCA8075_MMD7_EG_FRAME_RECV_CNT_HI 0x802d
|
|
+#define QCA8075_MMD7_EG_FRAME_RECV_CNT_LO 0x802e
|
|
+#define QCA8075_MMD7_EG_FRAME_ERR_CNT 0x802f
|
|
+#define QCA8075_MMD7_MDIO_BRDCST_WRITE 0x8028
|
|
+#define QCA8075_MMD7_MDIO_BRDCST_WRITE_EN BIT(15)
|
|
+#define QCA8075_MDIO_BRDCST_PHY_ADDR 0x1f
|
|
+#define QCA8075_PKT_GEN_PKTS_COUNT 4096
|
|
+
|
|
enum {
|
|
QCA8K_PORT_SPEED_10M = 0,
|
|
QCA8K_PORT_SPEED_100M = 1,
|
|
@@ -466,6 +518,10 @@ struct qca8k_priv {
|
|
struct qca8k_pcs pcs_port_6;
|
|
const struct qca8k_match_data *info;
|
|
struct qca8k_led ports_led[QCA8K_LED_COUNT];
|
|
+ /* IPQ4019 specific */
|
|
+ struct regmap *psgmii;
|
|
+ struct phy_device *psgmii_ethphy;
|
|
+ bool psgmii_calibrated;
|
|
};
|
|
|
|
struct qca8k_mib_desc {
|