]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
Merge branch 'net-dsa-microchip-add-KSZ9893-switch-support'
authorDavid S. Miller <davem@davemloft.net>
Sun, 3 Mar 2019 21:48:49 +0000 (13:48 -0800)
committerDavid S. Miller <davem@davemloft.net>
Sun, 3 Mar 2019 21:48:49 +0000 (13:48 -0800)
Tristram Ha says:

====================
net: dsa: microchip: add KSZ9893 switch support

This series of patches is to modify the KSZ9477 DSA driver to support
running KSZ9893 switch.

The KSZ9893 switch is similar to KSZ9477 except the ingress tail tag has
1 byte instead of 2 bytes.  The XMII register that governs the MAC
communication also has different register definitions.

v1
- Put KSZ9893 tagging in separate patch
- Remove other switch support
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
Documentation/devicetree/bindings/net/dsa/ksz.txt
drivers/net/dsa/microchip/ksz9477.c
drivers/net/dsa/microchip/ksz9477_spi.c
drivers/net/dsa/microchip/ksz_common.c
include/net/dsa.h
net/dsa/dsa.c
net/dsa/dsa_priv.h
net/dsa/tag_ksz.c

index 8d58c2a7de39c0858901bc9f8fffda56b4a32a1f..e7db7268fd0fd6502d81e2d6014b041895292236 100644 (file)
@@ -7,6 +7,11 @@ Required properties:
   of the following:
   - "microchip,ksz9477"
   - "microchip,ksz9897"
+  - "microchip,ksz9896"
+  - "microchip,ksz9567"
+  - "microchip,ksz8565"
+  - "microchip,ksz9893"
+  - "microchip,ksz9563"
 
 Optional properties:
 
@@ -73,4 +78,42 @@ Ethernet switch connected via SPI to the host, CPU port wired to eth0:
                                };
                        };
                };
+               ksz8565: ksz8565@0 {
+                       compatible = "microchip,ksz8565";
+                       reg = <0>;
+
+                       spi-max-frequency = <44000000>;
+                       spi-cpha;
+                       spi-cpol;
+
+                       ports {
+                               #address-cells = <1>;
+                               #size-cells = <0>;
+                               port@0 {
+                                       reg = <0>;
+                                       label = "lan1";
+                               };
+                               port@1 {
+                                       reg = <1>;
+                                       label = "lan2";
+                               };
+                               port@2 {
+                                       reg = <2>;
+                                       label = "lan3";
+                               };
+                               port@3 {
+                                       reg = <3>;
+                                       label = "lan4";
+                               };
+                               port@6 {
+                                       reg = <6>;
+                                       label = "cpu";
+                                       ethernet = <&eth0>;
+                                       fixed-link {
+                                               speed = <1000>;
+                                               full-duplex;
+                                       };
+                               };
+                       };
+               };
        };
index 03de50e62beb7666d6f1bd9c90ad37f06b85b16e..f16e1d7d8615d6191a42d158bf274a4e663e94d0 100644 (file)
 #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)
@@ -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;
 
@@ -1245,6 +1424,9 @@ static int ksz9477_switch_detect(struct ksz_device *dev)
 
        /* read chip id */
        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;
 
@@ -1252,6 +1434,32 @@ static int ksz9477_switch_detect(struct ksz_device *dev)
        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 @@ static const struct ksz_chip_data ksz9477_switch_chips[] = {
                .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;
 }
index d757ba151cb1614e0ff5f6abdc66b953d73ed926..75178624d3f563bcb8d991284c478cb39c7a7a76 100644 (file)
@@ -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);
index 9328b88849d27fa24fb796838a1f783634e5fa71..39dace8e3512a4a49d5f5789e1b08477f92c030b 100644 (file)
@@ -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)
index e8ac5b35ac4aaaf506d86c74364c817494e2baf8..ae480bba11f58e28435bd45c18632d2d5614a661 100644 (file)
@@ -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,
index aee909bcddc4fe8a7beb0e86198d521905cbafbe..36de4f2a3366b4a56d50d3c3ac26a61c0c89548b 100644 (file)
@@ -57,6 +57,7 @@ const struct dsa_device_ops *dsa_device_ops[DSA_TAG_LAST] = {
 #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",
index c6caa58c5c7176559228af58a35f93ec589c9b10..093b7d145eb14d7c8b581c1f1438fa270d9800e4 100644 (file)
@@ -216,6 +216,7 @@ extern const struct dsa_device_ops gswip_netdev_ops;
 
 /* 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;
index 927e9c86f745184d6c0fcb773289896cd60a5728..de246c93d3bbd752a8ca432edfc0b179a7893be7 100644 (file)
@@ -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 @@ const struct dsa_device_ops ksz9477_netdev_ops = {
        .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,
+};