[PATCH 4/7] net: stmmac: introducing support for DWC xPCS logics

From: Weifeng Voon
Date: Wed Apr 24 2019 - 05:17:08 EST


From: Ong Boon Leong <boon.leong.ong@xxxxxxxxx>

xPCS is DWC Ethernet Physical Coding Sublayer that may be integrated
into a GbE controller that uses DWC EQoS MAC controller. An example of
HW configuration is shown below:-

<-----------------GBE Controller---------->|<--External PHY chip-->

+----------+ +----+ +---+ +--------------+
| EQoS | <-GMII->|xPCS|<-->|L1 | <-- SGMII --> | External GbE |
| MAC | | | |PHY| | PHY Chip |
+----------+ +----+ +---+ +--------------+
^ ^ ^
| | |
+---------------------MDIO-------------------------+

xPCS is a Clause-45 MDIO Manageable Device (MMD) and we need a way to
differentiate it from external PHY chip that is discovered over MDIO.
Therefore, xpcs_phy_addr is introduced in stmmac platform data
(plat_stmmacenet_data) for differentiating xPCS from 'phy_addr' that
belongs to external PHY.

Basic functionalities for initializing xPCS and configuring auto
negotiation (AN), loopback, link status, AN advertisement and Link
Partner ability are implemented.

xPCS interrupt handling for C37 AN complete is also implemented.

Tested-by: Kweh Hock Leong <hock.leong.kweh@xxxxxxxxx>
Reviewed-by: Chuah Kim Tatt <kim.tatt.chuah@xxxxxxxxx>
Reviewed-by: Voon Weifeng <weifeng.voon@xxxxxxxxx>
Reviewed-by: Kweh Hock Leong <hock.leong.kweh@xxxxxxxxx>
Reviewed-by: Baoli Zhang <baoli.zhang@xxxxxxxxx>
Signed-off-by: Ong Boon Leong <boon.leong.ong@xxxxxxxxx>
---
drivers/net/ethernet/stmicro/stmmac/dw_xpcs.h | 288 ++++++++++++++++++++++++++
drivers/net/ethernet/stmicro/stmmac/hwif.h | 17 ++
include/linux/stmmac.h | 1 +
3 files changed, 306 insertions(+)
create mode 100644 drivers/net/ethernet/stmicro/stmmac/dw_xpcs.h

diff --git a/drivers/net/ethernet/stmicro/stmmac/dw_xpcs.h b/drivers/net/ethernet/stmicro/stmmac/dw_xpcs.h
new file mode 100644
index 0000000..446b714
--- /dev/null
+++ b/drivers/net/ethernet/stmicro/stmmac/dw_xpcs.h
@@ -0,0 +1,288 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* dw_xpcs.h: DWC Ethernet Physical Coding Sublayer Header
+ *
+ * Copyright (c) 2019, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+#ifndef __DW_XPCS_H__
+#define __DW_XPCS_H__
+
+#include <linux/mdio.h>
+#include <linux/bitops.h>
+#include "stmmac.h"
+
+/* XPCS Control & MII MMD Device Addresses */
+#define XPCS_MDIO_CONTROL_MMD MDIO_MMD_VEND1
+#define XPCS_MDIO_MII_MMD MDIO_MMD_VEND2
+
+/* Control MMD register offsets */
+#define MDIO_CONTROL_MMD_CTRL 0x9 /* Control */
+
+/* Control MMD Control defines */
+#define MDIO_CONTROL_MMD_CTRL_MII_MMD_EN 1 /* MII MMD Enable */
+
+/* MII MMD registers offsets */
+#define MDIO_MII_MMD_CTRL 0x0 /* Control */
+#define MDIO_MII_MMD_ADV 0x4 /* AN Advertisement */
+#define MDIO_MII_MMD_LPA 0x5 /* Link Partner Ability */
+#define MDIO_MII_MMD_DIGITAL_CTRL_1 0x8000 /* Digital Control 1 */
+#define MDIO_MII_MMD_AN_CTRL 0x8001 /* AN Control */
+#define MDIO_MII_MMD_AN_STAT 0x8002 /* AN Status */
+
+/* MII MMD Control defines */
+#define MDIO_MII_MMD_CTRL_ANE BIT(12) /* AN Enable */
+#define MDIO_MII_MMD_CTRL_LBE BIT(14) /* Loopback Enable */
+#define MDIO_MII_MMD_CTRL_RANE BIT(9) /* Restart AN */
+
+/* MII MMD AN Advertisement & Link Partner Ability */
+#define MDIO_MII_MMD_HD BIT(6) /* Half duplex */
+#define MDIO_MII_MMD_FD BIT(5) /* Full duplex */
+#define MDIO_MII_MMD_PSE_SHIFT 7 /* Pause Ability shift */
+#define MDIO_MII_MMD_PSE GENMASK(8, 7) /* Pause Ability */
+
+/* MII MMD Digital Control 1 defines */
+#define MDIO_MII_MMD_DIGI_CTRL_1_SGMII_PHY_MD BIT(0) /* SGMII PHY mode */
+
+/* MII MMD AN Control defines */
+#define MDIO_MII_MMD_AN_CTRL_TX_CONFIG_SHIFT 3 /* TX Config shift */
+#define AN_CTRL_TX_CONF_PHY_SIDE_SGMII 0x1 /* PHY side SGMII mode */
+#define AN_CTRL_TX_CONF_MAC_SIDE_SGMII 0x0 /* MAC side SGMII mode */
+#define MDIO_MII_MMD_AN_CTRL_PCS_MD_SHIFT 1 /* PCS Mode shift */
+#define MDIO_MII_MMD_AN_CTRL_PCS_MD GENMASK(2, 1) /* PCS Mode */
+#define AN_CTRL_PCS_MD_C37_1000BASEX 0x0 /* C37 AN for 1000BASE-X */
+#define AN_CTRL_PCS_MD_C37_SGMII 0x2 /* C37 AN for SGMII */
+#define MDIO_MII_MMD_AN_CTRL_AN_INTR_EN BIT(0) /* AN Complete Intr Enable */
+
+/* MII MMD AN Status defines for C37 AN SGMII Status */
+#define AN_STAT_C37_AN_CMPLT BIT(0) /* AN Complete Intr */
+#define AN_STAT_C37_AN_FD BIT(1) /* Full Duplex */
+#define AN_STAT_C37_AN_SPEED_SHIFT 2 /* AN Speed shift */
+#define AN_STAT_C37_AN_SPEED GENMASK(3, 2) /* AN Speed */
+#define AN_STAT_C37_AN_10MBPS 0x0 /* 10 Mbps */
+#define AN_STAT_C37_AN_100MBPS 0x1 /* 100 Mbps */
+#define AN_STAT_C37_AN_1000MBPS 0x2 /* 1000 Mbps */
+#define AN_STAT_C37_AN_LNKSTS BIT(4) /* Link Status */
+
+/**
+ * dw_xpcs_init - To initialize xPCS
+ * @ndev: network device pointer
+ * @mode: PCS mode
+ * Description: this is to initialize xPCS
+ */
+static inline void dw_xpcs_init(struct net_device *ndev, int pcs_mode)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int xpcs_phy_addr = priv->plat->xpcs_phy_addr;
+
+ /* Set SGMII PHY mode control */
+ u16 phydata = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+ (MII_ADDR_C45 |
+ (XPCS_MDIO_MII_MMD <<
+ MII_DEVADDR_C45_SHIFT) |
+ MDIO_MII_MMD_DIGITAL_CTRL_1));
+
+ phydata |= MDIO_MII_MMD_DIGI_CTRL_1_SGMII_PHY_MD;
+
+ mdiobus_write(priv->mii, xpcs_phy_addr,
+ (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+ MII_DEVADDR_C45_SHIFT) | MDIO_MII_MMD_DIGITAL_CTRL_1),
+ (int)phydata);
+
+ /* Set PHY side SGMII, PCS Mode & Enable C37 AN complete interrupt */
+ phydata = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+ (MII_ADDR_C45 |
+ (XPCS_MDIO_MII_MMD <<
+ MII_DEVADDR_C45_SHIFT) |
+ MDIO_MII_MMD_AN_CTRL));
+
+ phydata &= ~MDIO_MII_MMD_AN_CTRL_PCS_MD;
+ phydata |= (((pcs_mode << MDIO_MII_MMD_AN_CTRL_PCS_MD_SHIFT) &
+ MDIO_MII_MMD_AN_CTRL_PCS_MD) |
+ (AN_CTRL_TX_CONF_PHY_SIDE_SGMII <<
+ MDIO_MII_MMD_AN_CTRL_TX_CONFIG_SHIFT) |
+ MDIO_MII_MMD_AN_CTRL_AN_INTR_EN);
+
+ mdiobus_write(priv->mii, xpcs_phy_addr,
+ (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+ MII_DEVADDR_C45_SHIFT) | MDIO_MII_MMD_AN_CTRL),
+ (int)phydata);
+}
+
+/**
+ * dw_xpcs_rane - To restart Auto Negotiation (AN)
+ * @ndev: network device pointer
+ * @restart: to restart AN
+ * Description: this is to restart AN.
+ */
+static inline void dw_xpcs_rane(struct net_device *ndev, bool restart)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int xpcs_phy_addr = priv->plat->xpcs_phy_addr;
+
+ u16 phydata = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+ (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+ MII_DEVADDR_C45_SHIFT) |
+ MDIO_MII_MMD_CTRL));
+
+ if (restart)
+ phydata |= MDIO_MII_MMD_CTRL_RANE;
+
+ mdiobus_write(priv->mii, xpcs_phy_addr,
+ (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+ MII_DEVADDR_C45_SHIFT) | MDIO_MII_MMD_CTRL),
+ (int)phydata);
+}
+
+/**
+ * dw_xpcs_ctrl_ane - To program the MII MMD Control Register.
+ * @ndev: network device pointer
+ * @ane: to enable the Auto Negotiation
+ * @loopback: to cause the PHY to loopback Tx data into Rx path.
+ * Description: this is the main function to configure the MII MMD
+ * control register and init the AN Enable and select loopback.
+ */
+static inline void dw_xpcs_ctrl_ane(struct net_device *ndev, bool ane,
+ bool loopback)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int xpcs_phy_addr = priv->plat->xpcs_phy_addr;
+
+ u16 phydata = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+ (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+ MII_DEVADDR_C45_SHIFT) |
+ MDIO_MII_MMD_CTRL));
+
+ if (ane)
+ phydata |= (MDIO_MII_MMD_CTRL_ANE | MDIO_MII_MMD_CTRL_RANE);
+
+ if (loopback)
+ phydata |= MDIO_MII_MMD_CTRL_LBE;
+
+ mdiobus_write(priv->mii, xpcs_phy_addr,
+ (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+ MII_DEVADDR_C45_SHIFT) | MDIO_MII_MMD_CTRL),
+ (int)phydata);
+}
+
+/**
+ * dw_xpcs_get_adv_lp - Get AN Advertisement and Link Partner Ability
+ * @ndev: network device pointer
+ * @adv_lp: structure to store the adv, lp status
+ * Description: this is to expose the Auto Negotiation Advertisement and
+ * Link partner ability status to ethtool support.
+ */
+static inline void dw_xpcs_get_adv_lp(struct net_device *ndev,
+ struct rgmii_adv *adv_lp)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int xpcs_phy_addr = priv->plat->xpcs_phy_addr;
+
+ /* AN Advertisement Ability */
+ u16 value = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+ (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+ MII_DEVADDR_C45_SHIFT) |
+ MDIO_MII_MMD_ADV));
+
+ if (value & MDIO_MII_MMD_FD)
+ adv_lp->duplex = DUPLEX_FULL;
+ if (value & MDIO_MII_MMD_HD)
+ adv_lp->duplex = DUPLEX_HALF;
+ adv_lp->pause = (u32)((value & MDIO_MII_MMD_PSE) >>
+ MDIO_MII_MMD_PSE_SHIFT);
+
+ /* Link Partner Ability */
+ value = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+ (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+ MII_DEVADDR_C45_SHIFT) |
+ MDIO_MII_MMD_LPA));
+
+ if (value & MDIO_MII_MMD_FD)
+ adv_lp->lp_duplex = DUPLEX_FULL;
+ if (value & MDIO_MII_MMD_HD)
+ adv_lp->lp_duplex = DUPLEX_HALF;
+ adv_lp->lp_pause = (u32)((value & MDIO_MII_MMD_PSE) >>
+ MDIO_MII_MMD_PSE_SHIFT);
+}
+
+/**
+ * dw_xpcs_get_linkstatus - Get Link Status
+ * @an_stat: C37 AN status value
+ * @x: stmmac extra status
+ * Description: this is to read the link extra status from <C37 AN SGMII
+ * status> field of MII MMD AN Status register.
+ */
+static inline void dw_xpcs_get_linkstatus(u16 an_stat,
+ struct stmmac_extra_stats *x)
+{
+ /* Check the link status */
+ if (an_stat & AN_STAT_C37_AN_LNKSTS) {
+ int speed_value;
+
+ x->pcs_link = 1;
+
+ speed_value = ((an_stat & AN_STAT_C37_AN_SPEED) >>
+ AN_STAT_C37_AN_SPEED_SHIFT);
+ if (speed_value == AN_STAT_C37_AN_1000MBPS)
+ x->pcs_speed = SPEED_1000;
+ else if (speed_value == AN_STAT_C37_AN_100MBPS)
+ x->pcs_speed = SPEED_100;
+ else
+ x->pcs_speed = SPEED_10;
+
+ if (an_stat & AN_STAT_C37_AN_FD)
+ x->pcs_duplex = 1;
+ else
+ x->pcs_duplex = 0;
+
+ pr_info("Link is Up - %d/%s\n", (int)x->pcs_speed,
+ x->pcs_duplex ? "Full" : "Half");
+ } else {
+ x->pcs_link = 0;
+ pr_info("Link is Down\n");
+ }
+}
+
+/**
+ * dw_xpcs_irq_status - Get xPCS IRQ Status
+ * @ndev: network device pointer
+ * @x: stmmac extra status
+ * Description: this is to read the xPCS IRQ status.
+ */
+static inline int dw_xpcs_irq_status(struct net_device *ndev,
+ struct stmmac_extra_stats *x)
+{
+ struct stmmac_priv *priv = netdev_priv(ndev);
+ int xpcs_phy_addr = priv->plat->xpcs_phy_addr;
+ int ret = IRQ_NONE;
+
+ /* C37 AN status */
+ u16 an_stat = (u16)mdiobus_read(priv->mii, xpcs_phy_addr,
+ (MII_ADDR_C45 |
+ (XPCS_MDIO_MII_MMD <<
+ MII_DEVADDR_C45_SHIFT) |
+ MDIO_MII_MMD_AN_STAT));
+
+ if (an_stat & AN_STAT_C37_AN_CMPLT) {
+ x->irq_pcs_ane_n++;
+ dw_xpcs_get_linkstatus(an_stat, x);
+
+ /* Clear C37 AN complete status by writing zero */
+ mdiobus_write(priv->mii, xpcs_phy_addr,
+ (MII_ADDR_C45 | (XPCS_MDIO_MII_MMD <<
+ MII_DEVADDR_C45_SHIFT) |
+ MDIO_MII_MMD_AN_STAT),
+ 0);
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+#endif /* __DW_XPCS_H__ */
diff --git a/drivers/net/ethernet/stmicro/stmmac/hwif.h b/drivers/net/ethernet/stmicro/stmmac/hwif.h
index 5bb0023..cb7eb48 100644
--- a/drivers/net/ethernet/stmicro/stmmac/hwif.h
+++ b/drivers/net/ethernet/stmicro/stmmac/hwif.h
@@ -310,6 +310,13 @@ struct stmmac_ops {
bool loopback);
void (*pcs_rane)(void __iomem *ioaddr, bool restart);
void (*pcs_get_adv_lp)(void __iomem *ioaddr, struct rgmii_adv *adv);
+ /* xPCS calls */
+ void (*xpcs_init)(struct net_device *ndev, int pcs_mode);
+ void (*xpcs_ctrl_ane)(struct net_device *ndev, bool ane, bool loopback);
+ void (*xpcs_rane)(struct net_device *ndev, bool restart);
+ void (*xpcs_get_adv_lp)(struct net_device *ndev, struct rgmii_adv *adv);
+ int (*xpcs_irq_status)(struct net_device *ndev,
+ struct stmmac_extra_stats *x);
/* Safety Features */
int (*safety_feat_config)(void __iomem *ioaddr, unsigned int asp);
int (*safety_feat_irq_status)(struct net_device *ndev,
@@ -382,6 +389,16 @@ struct stmmac_ops {
stmmac_do_void_callback(__priv, mac, pcs_rane, __args)
#define stmmac_pcs_get_adv_lp(__priv, __args...) \
stmmac_do_void_callback(__priv, mac, pcs_get_adv_lp, __args)
+#define stmmac_xpcs_init(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, xpcs_init, __args)
+#define stmmac_xpcs_ctrl_ane(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, xpcs_ctrl_ane, __args)
+#define stmmac_xpcs_rane(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, xpcs_rane, __args)
+#define stmmac_xpcs_get_adv_lp(__priv, __args...) \
+ stmmac_do_void_callback(__priv, mac, xpcs_get_adv_lp, __args)
+#define stmmac_xpcs_irq_status(__priv, __args...) \
+ stmmac_do_callback(__priv, mac, xpcs_irq_status, __args)
#define stmmac_safety_feat_config(__priv, __args...) \
stmmac_do_callback(__priv, mac, safety_feat_config, __args)
#define stmmac_safety_feat_irq_status(__priv, __args...) \
diff --git a/include/linux/stmmac.h b/include/linux/stmmac.h
index 4335bd7..b00e795 100644
--- a/include/linux/stmmac.h
+++ b/include/linux/stmmac.h
@@ -148,6 +148,7 @@ struct stmmac_txq_cfg {
struct plat_stmmacenet_data {
int bus_id;
int phy_addr;
+ int xpcs_phy_addr;
int interface;
struct stmmac_mdio_bus_data *mdio_bus_data;
struct device_node *phy_node;
--
1.9.1