From: Tristram Ha <tristram...@microchip.com>

Add KSZ9893 switch support in KSZ9477 driver.  This switch is similar to
KSZ9477 except the ingress tail tag has 1 byte instead of 2 bytes.  The
XMII register that governs how the host port communicates with the MAC
also has different register definitions.

Signed-off-by: Tristram Ha <tristram...@microchip.com>
---
 drivers/net/dsa/microchip/ksz9477.c     | 244 ++++++++++++++++++++++++++++++--
 drivers/net/dsa/microchip/ksz9477_spi.c |   4 +-
 drivers/net/dsa/microchip/ksz_common.c  |   4 +-
 include/net/dsa.h                       |   1 +
 net/dsa/dsa.c                           |   2 +
 net/dsa/dsa_priv.h                      |   1 +
 net/dsa/tag_ksz.c                       |  34 +++++
 7 files changed, 274 insertions(+), 16 deletions(-)

diff --git a/drivers/net/dsa/microchip/ksz9477.c 
b/drivers/net/dsa/microchip/ksz9477.c
index 03de50e..3bb548a 100644
--- a/drivers/net/dsa/microchip/ksz9477.c
+++ b/drivers/net/dsa/microchip/ksz9477.c
@@ -18,6 +18,11 @@
 #include "ksz9477_reg.h"
 #include "ksz_common.h"
 
+/* Used with variable features to indicate capabilities. */
+#define GBIT_SUPPORT                   BIT(0)
+#define NEW_XMII                       BIT(1)
+#define IS_9893                                BIT(2)
+
 static const struct {
        int index;
        char string[ETH_GSTRING_LEN];
@@ -328,7 +333,12 @@ static void ksz9477_port_init_cnt(struct ksz_device *dev, 
int port)
 static enum dsa_tag_protocol ksz9477_get_tag_protocol(struct dsa_switch *ds,
                                                      int port)
 {
-       return DSA_TAG_PROTO_KSZ9477;
+       enum dsa_tag_protocol proto = DSA_TAG_PROTO_KSZ9477;
+       struct ksz_device *dev = ds->priv;
+
+       if (dev->features & IS_9893)
+               proto = DSA_TAG_PROTO_KSZ9893;
+       return proto;
 }
 
 static int ksz9477_phy_read16(struct dsa_switch *ds, int addr, int reg)
@@ -353,7 +363,7 @@ static int ksz9477_phy_read16(struct dsa_switch *ds, int 
addr, int reg)
                        val = 0x796d;
                        break;
                case MII_PHYSID1:
-                       val = 0x0022;
+                       val = KSZ9477_ID_HI;
                        break;
                case MII_PHYSID2:
                        val = 0x1631;
@@ -389,6 +399,10 @@ static int ksz9477_phy_write16(struct dsa_switch *ds, int 
addr, int reg,
        /* No real PHY after this. */
        if (addr >= dev->phy_port_cnt)
                return 0;
+
+       /* No gigabit support.  Do not write to this register. */
+       if (!(dev->features & GBIT_SUPPORT) && reg == MII_CTRL1000)
+               return 0;
        ksz_pwrite16(dev, addr, 0x100 + (reg << 1), val);
 
        return 0;
@@ -998,11 +1012,156 @@ static void ksz9477_port_mirror_del(struct dsa_switch 
*ds, int port,
 static void ksz9477_phy_setup(struct ksz_device *dev, int port,
                              struct phy_device *phy)
 {
-       if (port < dev->phy_port_cnt) {
-               /* The MAC actually cannot run in 1000 half-duplex mode. */
+       /* Only apply to port with PHY. */
+       if (port >= dev->phy_port_cnt)
+               return;
+
+       /* The MAC actually cannot run in 1000 half-duplex mode. */
+       phy_remove_link_mode(phy,
+                            ETHTOOL_LINK_MODE_1000baseT_Half_BIT);
+
+       /* PHY does not support gigabit. */
+       if (!(dev->features & GBIT_SUPPORT))
                phy_remove_link_mode(phy,
-                                    ETHTOOL_LINK_MODE_1000baseT_Half_BIT);
+                                    ETHTOOL_LINK_MODE_1000baseT_Full_BIT);
+}
+
+static bool ksz9477_get_gbit(struct ksz_device *dev, u8 data)
+{
+       bool gbit;
+
+       if (dev->features & NEW_XMII)
+               gbit = !(data & PORT_MII_NOT_1GBIT);
+       else
+               gbit = !!(data & PORT_MII_1000MBIT_S1);
+       return gbit;
+}
+
+static void ksz9477_set_gbit(struct ksz_device *dev, bool gbit, u8 *data)
+{
+       if (dev->features & NEW_XMII) {
+               if (gbit)
+                       *data &= ~PORT_MII_NOT_1GBIT;
+               else
+                       *data |= PORT_MII_NOT_1GBIT;
+       } else {
+               if (gbit)
+                       *data |= PORT_MII_1000MBIT_S1;
+               else
+                       *data &= ~PORT_MII_1000MBIT_S1;
+       }
+}
+
+static int ksz9477_get_xmii(struct ksz_device *dev, u8 data)
+{
+       int mode;
+
+       if (dev->features & NEW_XMII) {
+               switch (data & PORT_MII_SEL_M) {
+               case PORT_MII_SEL:
+                       mode = 0;
+                       break;
+               case PORT_RMII_SEL:
+                       mode = 1;
+                       break;
+               case PORT_GMII_SEL:
+                       mode = 2;
+                       break;
+               default:
+                       mode = 3;
+               }
+       } else {
+               switch (data & PORT_MII_SEL_M) {
+               case PORT_MII_SEL_S1:
+                       mode = 0;
+                       break;
+               case PORT_RMII_SEL_S1:
+                       mode = 1;
+                       break;
+               case PORT_GMII_SEL_S1:
+                       mode = 2;
+                       break;
+               default:
+                       mode = 3;
+               }
+       }
+       return mode;
+}
+
+static void ksz9477_set_xmii(struct ksz_device *dev, int mode, u8 *data)
+{
+       u8 xmii;
+
+       if (dev->features & NEW_XMII) {
+               switch (mode) {
+               case 0:
+                       xmii = PORT_MII_SEL;
+                       break;
+               case 1:
+                       xmii = PORT_RMII_SEL;
+                       break;
+               case 2:
+                       xmii = PORT_GMII_SEL;
+                       break;
+               default:
+                       xmii = PORT_RGMII_SEL;
+                       break;
+               }
+       } else {
+               switch (mode) {
+               case 0:
+                       xmii = PORT_MII_SEL_S1;
+                       break;
+               case 1:
+                       xmii = PORT_RMII_SEL_S1;
+                       break;
+               case 2:
+                       xmii = PORT_GMII_SEL_S1;
+                       break;
+               default:
+                       xmii = PORT_RGMII_SEL_S1;
+                       break;
+               }
+       }
+       *data &= ~PORT_MII_SEL_M;
+       *data |= xmii;
+}
+
+static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port)
+{
+       phy_interface_t interface;
+       bool gbit;
+       int mode;
+       u8 data8;
+
+       if (port < dev->phy_port_cnt)
+               return PHY_INTERFACE_MODE_NA;
+       ksz_pread8(dev, port, REG_PORT_XMII_CTRL_1, &data8);
+       gbit = ksz9477_get_gbit(dev, data8);
+       mode = ksz9477_get_xmii(dev, data8);
+       switch (mode) {
+       case 2:
+               interface = PHY_INTERFACE_MODE_GMII;
+               if (gbit)
+                       break;
+       case 0:
+               interface = PHY_INTERFACE_MODE_MII;
+               break;
+       case 1:
+               interface = PHY_INTERFACE_MODE_RMII;
+               break;
+       default:
+               interface = PHY_INTERFACE_MODE_RGMII;
+               if (data8 & PORT_RGMII_ID_EG_ENABLE)
+                       interface = PHY_INTERFACE_MODE_RGMII_TXID;
+               if (data8 & PORT_RGMII_ID_IG_ENABLE) {
+                       interface = PHY_INTERFACE_MODE_RGMII_RXID;
+                       if (data8 & PORT_RGMII_ID_EG_ENABLE)
+                               interface = PHY_INTERFACE_MODE_RGMII_ID;
+               }
+               break;
        }
+       return interface;
 }
 
 static void ksz9477_port_setup(struct ksz_device *dev, int port, bool cpu_port)
@@ -1051,24 +1210,25 @@ static void ksz9477_port_setup(struct ksz_device *dev, 
int port, bool cpu_port)
 
                /* configure MAC to 1G & RGMII mode */
                ksz_pread8(dev, port, REG_PORT_XMII_CTRL_1, &data8);
-               data8 &= ~PORT_MII_NOT_1GBIT;
-               data8 &= ~PORT_MII_SEL_M;
                switch (dev->interface) {
                case PHY_INTERFACE_MODE_MII:
-                       data8 |= PORT_MII_NOT_1GBIT;
-                       data8 |= PORT_MII_SEL;
+                       ksz9477_set_xmii(dev, 0, &data8);
+                       ksz9477_set_gbit(dev, false, &data8);
                        p->phydev.speed = SPEED_100;
                        break;
                case PHY_INTERFACE_MODE_RMII:
-                       data8 |= PORT_MII_NOT_1GBIT;
-                       data8 |= PORT_RMII_SEL;
+                       ksz9477_set_xmii(dev, 1, &data8);
+                       ksz9477_set_gbit(dev, false, &data8);
                        p->phydev.speed = SPEED_100;
                        break;
                case PHY_INTERFACE_MODE_GMII:
-                       data8 |= PORT_GMII_SEL;
+                       ksz9477_set_xmii(dev, 2, &data8);
+                       ksz9477_set_gbit(dev, true, &data8);
                        p->phydev.speed = SPEED_1000;
                        break;
                default:
+                       ksz9477_set_xmii(dev, 3, &data8);
+                       ksz9477_set_gbit(dev, true, &data8);
                        data8 &= ~PORT_RGMII_ID_IG_ENABLE;
                        data8 &= ~PORT_RGMII_ID_EG_ENABLE;
                        if (dev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
@@ -1077,7 +1237,6 @@ static void ksz9477_port_setup(struct ksz_device *dev, 
int port, bool cpu_port)
                        if (dev->interface == PHY_INTERFACE_MODE_RGMII_ID ||
                            dev->interface == PHY_INTERFACE_MODE_RGMII_TXID)
                                data8 |= PORT_RGMII_ID_EG_ENABLE;
-                       data8 |= PORT_RGMII_SEL;
                        p->phydev.speed = SPEED_1000;
                        break;
                }
@@ -1115,10 +1274,25 @@ static void ksz9477_config_cpu_port(struct dsa_switch 
*ds)
 
        for (i = 0; i < dev->port_cnt; i++) {
                if (dsa_is_cpu_port(ds, i) && (dev->cpu_ports & (1 << i))) {
+                       phy_interface_t interface;
+
                        dev->cpu_port = i;
                        dev->host_mask = (1 << dev->cpu_port);
                        dev->port_mask |= dev->host_mask;
 
+                       /* Read from XMII register to determine host port
+                        * interface.  If set specifically in device tree
+                        * note the difference to help debugging.
+                        */
+                       interface = ksz9477_get_interface(dev, i);
+                       if (!dev->interface)
+                               dev->interface = interface;
+                       if (interface && interface != dev->interface)
+                               dev_info(dev->dev,
+                                        "use %s instead of %s\n",
+                                         phy_modes(dev->interface),
+                                         phy_modes(interface));
+
                        /* enable cpu port */
                        ksz9477_port_setup(dev, i, true);
                        p = &dev->ports[dev->cpu_port];
@@ -1172,6 +1346,9 @@ static int ksz9477_setup(struct dsa_switch *ds)
        ksz9477_cfg32(dev, REG_SW_QM_CTRL__4, UNICAST_VLAN_BOUNDARY,
                      true);
 
+       /* Do not work correctly with tail tagging. */
+       ksz_cfg(dev, REG_SW_MAC_CTRL_0, SW_CHECK_LENGTH, false);
+
        /* accept packet up to 2000bytes */
        ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_LEGAL_PACKET_DISABLE, true);
 
@@ -1230,6 +1407,8 @@ static u32 ksz9477_get_port_addr(int port, int offset)
 static int ksz9477_switch_detect(struct ksz_device *dev)
 {
        u8 data8;
+       u8 id_hi;
+       u8 id_lo;
        u32 id32;
        int ret;
 
@@ -1247,11 +1426,40 @@ static int ksz9477_switch_detect(struct ksz_device *dev)
        ret = ksz_read32(dev, REG_CHIP_ID0__1, &id32);
        if (ret)
                return ret;
+       ret = ksz_read8(dev, REG_GLOBAL_OPTIONS, &data8);
+       if (ret)
+               return ret;
 
        /* Number of ports can be reduced depending on chip. */
        dev->mib_port_cnt = TOTAL_PORT_NUM;
        dev->phy_port_cnt = 5;
 
+       /* Default capability is gigabit capable. */
+       dev->features = GBIT_SUPPORT;
+
+       id_hi = (u8)(id32 >> 16);
+       id_lo = (u8)(id32 >> 8);
+       if ((id_lo & 0xf) == 3) {
+               /* Chip is from KSZ9893 design. */
+               dev->features |= IS_9893;
+
+               /* Chip does not support gigabit. */
+               if (data8 & SW_QW_ABLE)
+                       dev->features &= ~GBIT_SUPPORT;
+               dev->mib_port_cnt = 3;
+               dev->phy_port_cnt = 2;
+       } else {
+               /* Chip uses new XMII register definitions. */
+               dev->features |= NEW_XMII;
+
+               /* Chip does not support gigabit. */
+               if (!(data8 & SW_GIGABIT_ABLE))
+                       dev->features &= ~GBIT_SUPPORT;
+       }
+
+       /* Change chip id to known ones so it can be matched against them. */
+       id32 = (id_hi << 16) | (id_lo << 8);
+
        dev->chip_id = id32;
 
        return 0;
@@ -1286,6 +1494,15 @@ struct ksz_chip_data {
                .cpu_ports = 0x7F,      /* can be configured as cpu port */
                .port_cnt = 7,          /* total physical port count */
        },
+       {
+               .chip_id = 0x00989300,
+               .dev_name = "KSZ9893",
+               .num_vlans = 4096,
+               .num_alus = 4096,
+               .num_statics = 16,
+               .cpu_ports = 0x07,      /* can be configured as cpu port */
+               .port_cnt = 3,          /* total port count */
+       },
 };
 
 static int ksz9477_switch_init(struct ksz_device *dev)
@@ -1333,7 +1550,6 @@ static int ksz9477_switch_init(struct ksz_device *dev)
                if (!dev->ports[i].mib.counters)
                        return -ENOMEM;
        }
-       dev->interface = PHY_INTERFACE_MODE_RGMII_TXID;
 
        return 0;
 }
diff --git a/drivers/net/dsa/microchip/ksz9477_spi.c 
b/drivers/net/dsa/microchip/ksz9477_spi.c
index d757ba1..7517862 100644
--- a/drivers/net/dsa/microchip/ksz9477_spi.c
+++ b/drivers/net/dsa/microchip/ksz9477_spi.c
@@ -2,7 +2,7 @@
 /*
  * Microchip KSZ9477 series register access through SPI
  *
- * Copyright (C) 2017-2018 Microchip Technology Inc.
+ * Copyright (C) 2017-2019 Microchip Technology Inc.
  */
 
 #include <asm/unaligned.h>
@@ -155,6 +155,8 @@ static void ksz9477_spi_shutdown(struct spi_device *spi)
 static const struct of_device_id ksz9477_dt_ids[] = {
        { .compatible = "microchip,ksz9477" },
        { .compatible = "microchip,ksz9897" },
+       { .compatible = "microchip,ksz9893" },
+       { .compatible = "microchip,ksz9563" },
        {},
 };
 MODULE_DEVICE_TABLE(of, ksz9477_dt_ids);
diff --git a/drivers/net/dsa/microchip/ksz_common.c 
b/drivers/net/dsa/microchip/ksz_common.c
index 9328b88..39dace8 100644
--- a/drivers/net/dsa/microchip/ksz_common.c
+++ b/drivers/net/dsa/microchip/ksz_common.c
@@ -453,7 +453,9 @@ int ksz_switch_register(struct ksz_device *dev,
        if (ret)
                return ret;
 
-       dev->interface = PHY_INTERFACE_MODE_MII;
+       /* Host port interface will be self detected, or specifically set in
+        * device tree.
+        */
        if (dev->dev->of_node) {
                ret = of_get_phy_mode(dev->dev->of_node);
                if (ret >= 0)
diff --git a/include/net/dsa.h b/include/net/dsa.h
index e8ac5b3..ae480bb 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -38,6 +38,7 @@ enum dsa_tag_protocol {
        DSA_TAG_PROTO_EDSA,
        DSA_TAG_PROTO_GSWIP,
        DSA_TAG_PROTO_KSZ9477,
+       DSA_TAG_PROTO_KSZ9893,
        DSA_TAG_PROTO_LAN9303,
        DSA_TAG_PROTO_MTK,
        DSA_TAG_PROTO_QCA,
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index aee909b..36de4f2 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -57,6 +57,7 @@ static struct sk_buff *dsa_slave_notag_xmit(struct sk_buff 
*skb,
 #endif
 #ifdef CONFIG_NET_DSA_TAG_KSZ9477
        [DSA_TAG_PROTO_KSZ9477] = &ksz9477_netdev_ops,
+       [DSA_TAG_PROTO_KSZ9893] = &ksz9893_netdev_ops,
 #endif
 #ifdef CONFIG_NET_DSA_TAG_LAN9303
        [DSA_TAG_PROTO_LAN9303] = &lan9303_netdev_ops,
@@ -93,6 +94,7 @@ const char *dsa_tag_protocol_to_str(const struct 
dsa_device_ops *ops)
 #endif
 #ifdef CONFIG_NET_DSA_TAG_KSZ9477
                [DSA_TAG_PROTO_KSZ9477] = "ksz9477",
+               [DSA_TAG_PROTO_KSZ9893] = "ksz9893",
 #endif
 #ifdef CONFIG_NET_DSA_TAG_LAN9303
                [DSA_TAG_PROTO_LAN9303] = "lan9303",
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index c6caa58..093b7d1 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -216,6 +216,7 @@ static inline struct dsa_port *dsa_slave_to_port(const 
struct net_device *dev)
 
 /* tag_ksz.c */
 extern const struct dsa_device_ops ksz9477_netdev_ops;
+extern const struct dsa_device_ops ksz9893_netdev_ops;
 
 /* tag_lan9303.c */
 extern const struct dsa_device_ops lan9303_netdev_ops;
diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c
index 927e9c8..de246c9 100644
--- a/net/dsa/tag_ksz.c
+++ b/net/dsa/tag_ksz.c
@@ -16,6 +16,7 @@
 
 /* Typically only one byte is used for tail tag. */
 #define KSZ_EGRESS_TAG_LEN             1
+#define KSZ_INGRESS_TAG_LEN            1
 
 static struct sk_buff *ksz_common_xmit(struct sk_buff *skb,
                                       struct net_device *dev, int len)
@@ -141,3 +142,36 @@ static struct sk_buff *ksz9477_rcv(struct sk_buff *skb, 
struct net_device *dev,
        .rcv    = ksz9477_rcv,
        .overhead = KSZ9477_INGRESS_TAG_LEN,
 };
+
+#define KSZ9893_TAIL_TAG_OVERRIDE      BIT(5)
+#define KSZ9893_TAIL_TAG_LOOKUP                BIT(6)
+
+static struct sk_buff *ksz9893_xmit(struct sk_buff *skb,
+                                   struct net_device *dev)
+{
+       struct dsa_port *dp = dsa_slave_to_port(dev);
+       struct sk_buff *nskb;
+       u8 *addr;
+       u8 *tag;
+
+       nskb = ksz_common_xmit(skb, dev, KSZ_INGRESS_TAG_LEN);
+       if (!nskb)
+               return NULL;
+
+       /* Tag encoding */
+       tag = skb_put(nskb, KSZ_INGRESS_TAG_LEN);
+       addr = skb_mac_header(nskb);
+
+       *tag = BIT(dp->index);
+
+       if (is_link_local_ether_addr(addr))
+               *tag |= KSZ9893_TAIL_TAG_OVERRIDE;
+
+       return nskb;
+}
+
+const struct dsa_device_ops ksz9893_netdev_ops = {
+       .xmit   = ksz9893_xmit,
+       .rcv    = ksz9477_rcv,
+       .overhead = KSZ_INGRESS_TAG_LEN,
+};
-- 
1.9.1

Reply via email to