Different versions of NETC switches have different numbers of ports and
MAC capabilities, so add .phylink_get_caps() to struct netc_switch_info,
so that each version of the NETC switch can implement its own callback
to obtain MAC capabilities. In addition, related interfaces of struct
phylink_mac_ops are added, such as .mac_config(), .mac_link_up(), and
.mac_link_down().

Signed-off-by: Wei Fang <[email protected]>
---
 drivers/net/dsa/netc/netc_main.c      | 212 ++++++++++++++++++++++++++
 drivers/net/dsa/netc/netc_platform.c  |  40 +++++
 drivers/net/dsa/netc/netc_switch.h    |   4 +
 drivers/net/dsa/netc/netc_switch_hw.h |  25 +++
 4 files changed, 281 insertions(+)

diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index bc7d48b99610..884ee899fc89 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -595,10 +595,221 @@ static void netc_switch_get_ip_revision(struct 
netc_switch *priv)
        priv->revision = val & IPBRR0_IP_REV;
 }
 
+static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
+                                 struct phylink_config *config)
+{
+       struct netc_switch *priv = ds->priv;
+
+       priv->info->phylink_get_caps(port, config);
+}
+
+static void netc_port_set_mac_mode(struct netc_port *np,
+                                  unsigned int mode,
+                                  phy_interface_t phy_mode)
+{
+       u32 mask = PM_IF_MODE_IFMODE | PM_IF_MODE_ENA;
+       u32 val = 0;
+
+       switch (phy_mode) {
+       case PHY_INTERFACE_MODE_RGMII:
+       case PHY_INTERFACE_MODE_RGMII_ID:
+       case PHY_INTERFACE_MODE_RGMII_RXID:
+       case PHY_INTERFACE_MODE_RGMII_TXID:
+               val |= IFMODE_RGMII;
+               /* Enable auto-negotiation for the MAC if its
+                * RGMII interface supports In-Band status.
+                */
+               if (phylink_autoneg_inband(mode))
+                       val |= PM_IF_MODE_ENA;
+               break;
+       case PHY_INTERFACE_MODE_RMII:
+               val |= IFMODE_RMII;
+               break;
+       case PHY_INTERFACE_MODE_REVMII:
+               val |= PM_IF_MODE_REVMII;
+               fallthrough;
+       case PHY_INTERFACE_MODE_MII:
+               val |= IFMODE_MII;
+               break;
+       case PHY_INTERFACE_MODE_SGMII:
+       case PHY_INTERFACE_MODE_2500BASEX:
+               val |= IFMODE_SGMII;
+               break;
+       default:
+               break;
+       }
+
+       netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
+}
+
+static void netc_mac_config(struct phylink_config *config, unsigned int mode,
+                           const struct phylink_link_state *state)
+{
+       struct dsa_port *dp = dsa_phylink_to_port(config);
+
+       netc_port_set_mac_mode(NETC_PORT(dp->ds, dp->index), mode,
+                              state->interface);
+}
+
+static void netc_port_set_speed(struct netc_port *np, int speed)
+{
+       netc_port_rmw(np, NETC_PCR, PCR_PSPEED, PSPEED_SET_VAL(speed));
+}
+
+/* If the RGMII device does not support the In-Band Status (IBS), we need
+ * the MAC driver to get the link speed and duplex mode from the PHY driver.
+ * The MAC driver then sets the MAC for the correct speed and duplex mode
+ * to match the PHY. The PHY driver gets the link status and speed and duplex
+ * information from the PHY via the MDIO/MDC interface.
+ */
+static void netc_port_force_set_rgmii_mac(struct netc_port *np,
+                                         int speed, int duplex)
+{
+       u32 mask, val;
+
+       mask = PM_IF_MODE_ENA | PM_IF_MODE_SSP | PM_IF_MODE_HD |
+              PM_IF_MODE_M10 | PM_IF_MODE_REVMII;
+
+       switch (speed) {
+       default:
+       case SPEED_1000:
+               val = FIELD_PREP(PM_IF_MODE_SSP, SSP_1G);
+               break;
+       case SPEED_100:
+               val = FIELD_PREP(PM_IF_MODE_SSP, SSP_100M);
+               break;
+       case SPEED_10:
+               val = FIELD_PREP(PM_IF_MODE_SSP, SSP_10M);
+               break;
+       }
+
+       if (duplex != DUPLEX_FULL)
+               val |= PM_IF_MODE_HD;
+
+       netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
+}
+
+static void net_port_set_rmii_mii_mac(struct netc_port *np,
+                                     int speed, int duplex)
+{
+       u32 mask, val = 0;
+
+       mask = PM_IF_MODE_ENA | PM_IF_MODE_SSP | PM_IF_MODE_HD |
+              PM_IF_MODE_M10;
+
+       if (speed == SPEED_10)
+               val |= PM_IF_MODE_M10;
+
+       if (duplex != DUPLEX_FULL)
+               val |= PM_IF_MODE_HD;
+
+       netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
+}
+
+static void netc_port_set_hd_flow_control(struct netc_port *np, bool en)
+{
+       if (!np->caps.half_duplex)
+               return;
+
+       /* The HD_FCEN is used in conjunction with the PM_HD_FLOW_CTRL
+        * register, which has a default value, so currently we do not
+        * set it in the driver. The half duplex flow control works by
+        * the backpressure, and the backpressure is essentially just
+        * a long preamble transmitted on the link intended to create
+        * a collision and get the half duplex link partner to defer.
+        */
+       netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_HD_FCEN,
+                         en ? PM_CMD_CFG_HD_FCEN : 0);
+}
+
+static void netc_port_mac_rx_enable(struct netc_port *np)
+{
+       netc_port_rmw(np, NETC_POR, PCR_RXDIS, 0);
+       netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_RX_EN,
+                         PM_CMD_CFG_RX_EN);
+}
+
+static void netc_port_wait_rx_empty(struct netc_port *np, int mac)
+{
+       u32 val;
+
+       if (read_poll_timeout(netc_port_rd, val, val & PM_IEVENT_RX_EMPTY,
+                             100, 10000, false, np, NETC_PM_IEVENT(mac)))
+               dev_warn(np->switch_priv->dev,
+                        "MAC %d of swp%d RX is not empty\n", mac,
+                        np->dp->index);
+}
+
+static void netc_port_mac_rx_graceful_stop(struct netc_port *np)
+{
+       u32 val;
+
+       if (is_netc_pseudo_port(np))
+               goto check_rx_busy;
+
+       if (np->caps.pmac) {
+               netc_port_rmw(np, NETC_PM_CMD_CFG(1), PM_CMD_CFG_RX_EN, 0);
+               netc_port_wait_rx_empty(np, 1);
+       }
+
+       netc_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_RX_EN, 0);
+       netc_port_wait_rx_empty(np, 0);
+
+check_rx_busy:
+       if (read_poll_timeout(netc_port_rd, val, !(val & PSR_RX_BUSY),
+                             100, 10000, false, np, NETC_PSR))
+               dev_warn(np->switch_priv->dev, "swp%d RX is busy\n",
+                        np->dp->index);
+
+       netc_port_rmw(np, NETC_POR, PCR_RXDIS, PCR_RXDIS);
+}
+
+static void netc_mac_link_up(struct phylink_config *config,
+                            struct phy_device *phy, unsigned int mode,
+                            phy_interface_t interface, int speed,
+                            int duplex, bool tx_pause, bool rx_pause)
+{
+       struct dsa_port *dp = dsa_phylink_to_port(config);
+       struct netc_port *np;
+
+       np = NETC_PORT(dp->ds, dp->index);
+       netc_port_set_speed(np, speed);
+
+       if (phy_interface_mode_is_rgmii(interface) &&
+           !phylink_autoneg_inband(mode)) {
+               netc_port_force_set_rgmii_mac(np, speed, duplex);
+       }
+
+       if (interface == PHY_INTERFACE_MODE_RMII ||
+           interface == PHY_INTERFACE_MODE_REVMII ||
+           interface == PHY_INTERFACE_MODE_MII) {
+               net_port_set_rmii_mii_mac(np, speed, duplex);
+       }
+
+       netc_port_set_hd_flow_control(np, duplex == DUPLEX_HALF);
+       netc_port_mac_rx_enable(np);
+}
+
+static void netc_mac_link_down(struct phylink_config *config,
+                              unsigned int mode,
+                              phy_interface_t interface)
+{
+       struct dsa_port *dp = dsa_phylink_to_port(config);
+
+       netc_port_mac_rx_graceful_stop(NETC_PORT(dp->ds, dp->index));
+}
+
+static const struct phylink_mac_ops netc_phylink_mac_ops = {
+       .mac_config             = netc_mac_config,
+       .mac_link_up            = netc_mac_link_up,
+       .mac_link_down          = netc_mac_link_down,
+};
+
 static const struct dsa_switch_ops netc_switch_ops = {
        .get_tag_protocol               = netc_get_tag_protocol,
        .setup                          = netc_setup,
        .teardown                       = netc_teardown,
+       .phylink_get_caps               = netc_phylink_get_caps,
 };
 
 static int netc_switch_probe(struct pci_dev *pdev,
@@ -639,6 +850,7 @@ static int netc_switch_probe(struct pci_dev *pdev,
        ds->num_ports = priv->info->num_ports;
        ds->num_tx_queues = NETC_TC_NUM;
        ds->ops = &netc_switch_ops;
+       ds->phylink_mac_ops = &netc_phylink_mac_ops;
        ds->priv = priv;
 
        priv->ds = ds;
diff --git a/drivers/net/dsa/netc/netc_platform.c 
b/drivers/net/dsa/netc/netc_platform.c
index abd599ea9c8d..8d3fb5151902 100644
--- a/drivers/net/dsa/netc/netc_platform.c
+++ b/drivers/net/dsa/netc/netc_platform.c
@@ -11,8 +11,48 @@ struct netc_switch_platform {
        const struct netc_switch_info *info;
 };
 
+static void imx94_switch_phylink_get_caps(int port,
+                                         struct phylink_config *config)
+{
+       config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
+                                  MAC_1000FD;
+
+       switch (port) {
+       case 0 ... 1:
+               __set_bit(PHY_INTERFACE_MODE_SGMII,
+                         config->supported_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_1000BASEX,
+                         config->supported_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+                         config->supported_interfaces);
+               config->mac_capabilities |= MAC_2500FD;
+               fallthrough;
+       case 2:
+               config->mac_capabilities |= MAC_10 | MAC_100;
+               __set_bit(PHY_INTERFACE_MODE_MII,
+                         config->supported_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_RMII,
+                         config->supported_interfaces);
+               if (port == 2)
+                       __set_bit(PHY_INTERFACE_MODE_REVMII,
+                                 config->supported_interfaces);
+
+               phy_interface_set_rgmii(config->supported_interfaces);
+               break;
+       case 3: /* CPU port */
+               __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+                         config->supported_interfaces);
+               config->mac_capabilities |= MAC_10FD | MAC_100FD |
+                                           MAC_2500FD;
+               break;
+       default:
+               break;
+       }
+}
+
 static const struct netc_switch_info imx94_info = {
        .num_ports = 4,
+       .phylink_get_caps = imx94_switch_phylink_get_caps,
 };
 
 static const struct netc_switch_platform netc_platforms[] = {
diff --git a/drivers/net/dsa/netc/netc_switch.h 
b/drivers/net/dsa/netc/netc_switch.h
index dac19bfba02b..eb65c36ecead 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -34,6 +34,7 @@ struct netc_switch;
 
 struct netc_switch_info {
        u32 num_ports;
+       void (*phylink_get_caps)(int port, struct phylink_config *config);
 };
 
 struct netc_port_caps {
@@ -70,6 +71,9 @@ struct netc_switch {
        struct ntmp_user ntmp;
 };
 
+#define NETC_PRIV(ds)                  ((struct netc_switch *)((ds)->priv))
+#define NETC_PORT(ds, port_id)         (NETC_PRIV(ds)->ports[(port_id)])
+
 /* Write/Read Switch base registers */
 #define netc_base_rd(r, o)             netc_read((r)->base + (o))
 #define netc_base_wr(r, o, v)          netc_write((r)->base + (o), v)
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h 
b/drivers/net/dsa/netc/netc_switch_hw.h
index 03b49857c854..6d7758631e61 100644
--- a/drivers/net/dsa/netc/netc_switch_hw.h
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -71,6 +71,10 @@
 #define  PCR_TXDIS                     BIT(0)
 #define  PCR_RXDIS                     BIT(1)
 
+#define NETC_PSR                       0x104
+#define  PSR_TX_BUSY                   BIT(0)
+#define  PSR_RX_BUSY                   BIT(1)
+
 #define NETC_PTCTMSDUR(a)              (0x208 + (a) * 0x20)
 #define  PTCTMSDUR_MAXSDU              GENMASK(15, 0)
 #define  PTCTMSDUR_SDU_TYPE            GENMASK(17, 16)
@@ -145,6 +149,27 @@ enum netc_mfo {
 #define NETC_PM_MAXFRM(a)              (0x1014 + (a) * 0x400)
 #define  PM_MAXFRAM                    GENMASK(15, 0)
 
+#define NETC_PM_IEVENT(a)              (0x1040 + (a) * 0x400)
+#define  PM_IEVENT_RX_EMPTY            BIT(6)
+
+#define NETC_PM_IF_MODE(a)             (0x1300 + (a) * 0x400)
+#define  PM_IF_MODE_IFMODE             GENMASK(2, 0)
+#define   IFMODE_MII                   1
+#define   IFMODE_RMII                  3
+#define   IFMODE_RGMII                 4
+#define   IFMODE_SGMII                 5
+#define  PM_IF_MODE_REVMII             BIT(3)
+#define  PM_IF_MODE_M10                        BIT(4)
+#define  PM_IF_MODE_HD                 BIT(6)
+#define  PM_IF_MODE_RGMII_RX_SKW       BIT(10)
+#define  PM_IF_MODE_RGMII_TX_SKW       BIT(11)
+#define  PM_IF_MODE_CLK_STOP           BIT(12)
+#define  PM_IF_MODE_SSP                        GENMASK(14, 13)
+#define   SSP_100M                     0
+#define   SSP_10M                      1
+#define   SSP_1G                       2
+#define  PM_IF_MODE_ENA                        BIT(15)
+
 #define NETC_PEMDIOCR                  0x1c00
 #define NETC_EMDIO_BASE                        NETC_PEMDIOCR
 
-- 
2.34.1


Reply via email to