]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
Merge branch 'sf2'
authorDavid S. Miller <davem@davemloft.net>
Thu, 28 Aug 2014 06:07:18 +0000 (23:07 -0700)
committerDavid S. Miller <davem@davemloft.net>
Thu, 28 Aug 2014 06:07:18 +0000 (23:07 -0700)
Florian Fainelli says:

====================
dsa: Broadcom Starfighter 2 switch support

This patch series adds support for the Broadcom Starfighter 2 (Roboswitch
successor) using the existing DSA infrastructure. This integrated switch
is heavily used in Set Top Box, Cable gateways and DSL gateways products
from Broadcom, and to a larger extent the new ARM-based Wi-Fi routers although
slightly differently.

Changes in v5 are the introduction of ETH_P_XDSA as suggested by Alexander to
help capture applications see this is a multiplexed DSA approach now.

Changes in v4 are the introducing of an indirection level for DSA switch tag
protocols receive and transmit functions.

I intentionnaly did not address one comment from Alexander who suggested to
move port_names and port_dn in a separate structure since that involves
touching arch/arm/ and arch/blackfin/ code which I am not yet comfortable
doing.

Notable changes in v3 is the preliminary patch that reworks the skb->protocol
override helpers for non-Ethertype switch tags, based on feedback from
Alexander Duyck.

The biggest changes from v1 of this patch series are:

- use the new fixed PHY helpers
- improved the switch driver with more complete features (interrupts,
  (RG)MII configuration, memory arrays power down/up, port disabling/enable
  VLAN separation

Future work will focus on bringing the upstream driver in feature parity with
the current downstream driver, including:

- adding Wake-on-LAN support to the switch
- adding suspend/resume callbacks for S2/S3 Power Management modes
- extending the switch register interface to cover BCM5310X SoCs
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
25 files changed:
Documentation/devicetree/bindings/net/broadcom-mdio-unimac.txt [new file with mode: 0644]
Documentation/devicetree/bindings/net/broadcom-sf2.txt [new file with mode: 0644]
Documentation/devicetree/bindings/net/dsa/dsa.txt
drivers/net/dsa/Kconfig
drivers/net/dsa/Makefile
drivers/net/dsa/bcm_sf2.c [new file with mode: 0644]
drivers/net/dsa/bcm_sf2.h [new file with mode: 0644]
drivers/net/dsa/bcm_sf2_regs.h [new file with mode: 0644]
drivers/net/phy/Kconfig
drivers/net/phy/Makefile
drivers/net/phy/mdio-bcm-unimac.c [new file with mode: 0644]
include/linux/netdevice.h
include/linux/phy_fixed.h
include/net/dsa.h
include/uapi/linux/if_ether.h
net/dsa/Kconfig
net/dsa/Makefile
net/dsa/dsa.c
net/dsa/dsa_priv.h
net/dsa/slave.c
net/dsa/tag_brcm.c [new file with mode: 0644]
net/dsa/tag_dsa.c
net/dsa/tag_edsa.c
net/dsa/tag_trailer.c
net/ethernet/eth.c

diff --git a/Documentation/devicetree/bindings/net/broadcom-mdio-unimac.txt b/Documentation/devicetree/bindings/net/broadcom-mdio-unimac.txt
new file mode 100644 (file)
index 0000000..ab0bb42
--- /dev/null
@@ -0,0 +1,39 @@
+* Broadcom UniMAC MDIO bus controller
+
+Required properties:
+- compatible: should one from "brcm,genet-mdio-v1", "brcm,genet-mdio-v2",
+  "brcm,genet-mdio-v3", "brcm,genet-mdio-v4" or "brcm,unimac-mdio"
+- reg: address and length of the regsiter set for the device, first one is the
+  base register, and the second one is optional and for indirect accesses to
+  larger than 16-bits MDIO transactions
+- reg-names: name(s) of the register must be "mdio" and optional "mdio_indir_rw"
+- #size-cells: must be 1
+- #address-cells: must be 0
+
+Optional properties:
+- interrupts: must be one if the interrupt is shared with the Ethernet MAC or
+  Ethernet switch this MDIO block is integrated from, or must be two, if there
+  are two separate interrupts, first one must be "mdio done" and second must be
+  for "mdio error"
+- interrupt-names: must be "mdio_done_error" when there is a share interrupt fed
+  to this hardware block, or must be "mdio_done" for the first interrupt and
+  "mdio_error" for the second when there are separate interrupts
+
+Child nodes of this MDIO bus controller node are standard Ethernet PHY device
+nodes as described in Documentation/devicetree/bindings/net/phy.txt
+
+Example:
+
+mdio@403c0 {
+       compatible = "brcm,unimac-mdio";
+       reg = <0x403c0 0x8 0x40300 0x18>;
+       reg-names = "mdio", "mdio_indir_rw";
+       #size-cells = <1>;
+       #address-cells = <0>;
+
+       ...
+       phy@0 {
+               compatible = "ethernet-phy-ieee802.3-c22";
+               reg = <0>;
+       };
+};
diff --git a/Documentation/devicetree/bindings/net/broadcom-sf2.txt b/Documentation/devicetree/bindings/net/broadcom-sf2.txt
new file mode 100644 (file)
index 0000000..30d4875
--- /dev/null
@@ -0,0 +1,78 @@
+* Broadcom Starfighter 2 integrated swich
+
+Required properties:
+
+- compatible: should be "brcm,bcm7445-switch-v4.0"
+- reg: addresses and length of the register sets for the device, must be 6
+  pairs of register addresses and lengths
+- interrupts: interrupts for the devices, must be two interrupts
+- dsa,mii-bus: phandle to the MDIO bus controller, see dsa/dsa.txt
+- dsa,ethernet: phandle to the CPU network interface controller, see dsa/dsa.txt
+- #size-cells: must be 0
+- #address-cells: must be 2, see dsa/dsa.txt
+
+Subnodes:
+
+The integrated switch subnode should be specified according to the binding
+described in dsa/dsa.txt.
+
+Optional properties:
+
+- reg-names: litteral names for the device base register addresses, when present
+  must be: "core", "reg", "intrl2_0", "intrl2_1", "fcb", "acb"
+
+- interrupt-names: litternal names for the device interrupt lines, when present
+  must be: "switch_0" and "switch_1"
+
+- brcm,num-gphy: specify the maximum number of integrated gigabit PHYs in the
+  switch
+
+- brcm,num-rgmii-ports: specify the maximum number of RGMII interfaces supported
+  by the switch
+
+- brcm,fcb-pause-override: boolean property, if present indicates that the switch
+  supports Failover Control Block pause override capability
+
+- brcm,acb-packets-inflight: boolean property, if present indicates that the switch
+  Admission Control Block supports reporting the number of packets in-flight in a
+  switch queue
+
+Example:
+
+switch_top@f0b00000 {
+       compatible = "simple-bus";
+       #size-cells = <1>;
+       #address-cells = <1>;
+       ranges = <0 0xf0b00000 0x40804>;
+
+       ethernet_switch@0 {
+               compatible = "brcm,bcm7445-switch-v4.0";
+               #size-cells = <0>;
+               #address-cells = <2>;
+               reg = <0x0 0x40000
+                       0x40000 0x110
+                       0x40340 0x30
+                       0x40380 0x30
+                       0x40400 0x34
+                       0x40600 0x208>;
+               interrupts = <0 0x18 0
+                               0 0x19 0>;
+               brcm,num-gphy = <1>;
+               brcm,num-rgmii-ports = <2>;
+               brcm,fcb-pause-override;
+               brcm,acb-packets-inflight;
+
+               ...
+               switch@0 {
+                       reg = <0 0>;
+                       #size-cells = <0>;
+                       #address-cells <1>;
+
+                       port@0 {
+                               label = "gphy";
+                               reg = <0>;
+                       };
+                       ...
+               };
+       };
+};
index 49f4f7ae3f5145921a6def4250cc39fabb4fdafa..a62c889aafcaf070774bc3e9bc93c96ba9ea1992 100644 (file)
@@ -39,6 +39,22 @@ Optionnal property:
                          This property is only used when switches are being
                          chained/cascaded together.
 
+- phy-handle           : Phandle to a PHY on an external MDIO bus, not the
+                         switch internal one. See
+                         Documentation/devicetree/bindings/net/ethernet.txt
+                         for details.
+
+- phy-mode             : String representing the connection to the designated
+                         PHY node specified by the 'phy-handle' property. See
+                         Documentation/devicetree/bindings/net/ethernet.txt
+                         for details.
+
+Optional subnodes:
+- fixed-link           : Fixed-link subnode describing a link to a non-MDIO
+                         managed entity. See
+                         Documentation/devicetree/bindings/net/fixed-link.txt
+                         for details.
+
 Example:
 
        dsa@0 {
@@ -58,6 +74,7 @@ Example:
                        port@0 {
                                reg = <0>;
                                label = "lan1";
+                               phy-handle = <&phy0>;
                        };
 
                        port@1 {
index b8fe808b7957c735e0f4c8248ad3bcff14b46716..c6ee07c6a1b54d3197d3ff04ee40f426d097b7de 100644 (file)
@@ -36,4 +36,15 @@ config NET_DSA_MV88E6123_61_65
          This enables support for the Marvell 88E6123/6161/6165
          ethernet switch chips.
 
+config NET_DSA_BCM_SF2
+       tristate "Broadcom Starfighter 2 Ethernet switch support"
+       select NET_DSA
+       select NET_DSA_TAG_BRCM
+       select FIXED_PHY if NET_DSA_BCM_SF2=y
+       select BCM7XXX_PHY
+       select MDIO_BCM_UNIMAC
+       ---help---
+         This enables support for the Broadcom Starfighter 2 Ethernet
+         switch chips.
+
 endmenu
index f3bda05536cc55e861ae2996103d930433a74dfc..dd3cd3b8157f82ead0a78c07fdf2d7c0754418bd 100644 (file)
@@ -7,3 +7,4 @@ endif
 ifdef CONFIG_NET_DSA_MV88E6131
 mv88e6xxx_drv-y += mv88e6131.o
 endif
+obj-$(CONFIG_NET_DSA_BCM_SF2)  += bcm_sf2.o
diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c
new file mode 100644 (file)
index 0000000..bb7cb8e
--- /dev/null
@@ -0,0 +1,626 @@
+/*
+ * Broadcom Starfighter 2 DSA switch driver
+ *
+ * Copyright (C) 2014, Broadcom Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/phy_fixed.h>
+#include <linux/mii.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_address.h>
+#include <net/dsa.h>
+
+#include "bcm_sf2.h"
+#include "bcm_sf2_regs.h"
+
+/* String, offset, and register size in bytes if different from 4 bytes */
+static const struct bcm_sf2_hw_stats bcm_sf2_mib[] = {
+       { "TxOctets",           0x000, 8        },
+       { "TxDropPkts",         0x020           },
+       { "TxQPKTQ0",           0x030           },
+       { "TxBroadcastPkts",    0x040           },
+       { "TxMulticastPkts",    0x050           },
+       { "TxUnicastPKts",      0x060           },
+       { "TxCollisions",       0x070           },
+       { "TxSingleCollision",  0x080           },
+       { "TxMultipleCollision", 0x090          },
+       { "TxDeferredCollision", 0x0a0          },
+       { "TxLateCollision",    0x0b0           },
+       { "TxExcessiveCollision", 0x0c0         },
+       { "TxFrameInDisc",      0x0d0           },
+       { "TxPausePkts",        0x0e0           },
+       { "TxQPKTQ1",           0x0f0           },
+       { "TxQPKTQ2",           0x100           },
+       { "TxQPKTQ3",           0x110           },
+       { "TxQPKTQ4",           0x120           },
+       { "TxQPKTQ5",           0x130           },
+       { "RxOctets",           0x140, 8        },
+       { "RxUndersizePkts",    0x160           },
+       { "RxPausePkts",        0x170           },
+       { "RxPkts64Octets",     0x180           },
+       { "RxPkts65to127Octets", 0x190          },
+       { "RxPkts128to255Octets", 0x1a0         },
+       { "RxPkts256to511Octets", 0x1b0         },
+       { "RxPkts512to1023Octets", 0x1c0        },
+       { "RxPkts1024toMaxPktsOctets", 0x1d0    },
+       { "RxOversizePkts",     0x1e0           },
+       { "RxJabbers",          0x1f0           },
+       { "RxAlignmentErrors",  0x200           },
+       { "RxFCSErrors",        0x210           },
+       { "RxGoodOctets",       0x220, 8        },
+       { "RxDropPkts",         0x240           },
+       { "RxUnicastPkts",      0x250           },
+       { "RxMulticastPkts",    0x260           },
+       { "RxBroadcastPkts",    0x270           },
+       { "RxSAChanges",        0x280           },
+       { "RxFragments",        0x290           },
+       { "RxJumboPkt",         0x2a0           },
+       { "RxSymblErr",         0x2b0           },
+       { "InRangeErrCount",    0x2c0           },
+       { "OutRangeErrCount",   0x2d0           },
+       { "EEELpiEvent",        0x2e0           },
+       { "EEELpiDuration",     0x2f0           },
+       { "RxDiscard",          0x300, 8        },
+       { "TxQPKTQ6",           0x320           },
+       { "TxQPKTQ7",           0x330           },
+       { "TxPkts64Octets",     0x340           },
+       { "TxPkts65to127Octets", 0x350          },
+       { "TxPkts128to255Octets", 0x360         },
+       { "TxPkts256to511Ocets", 0x370          },
+       { "TxPkts512to1023Ocets", 0x380         },
+       { "TxPkts1024toMaxPktOcets", 0x390      },
+};
+
+#define BCM_SF2_STATS_SIZE     ARRAY_SIZE(bcm_sf2_mib)
+
+static void bcm_sf2_sw_get_strings(struct dsa_switch *ds,
+                                  int port, uint8_t *data)
+{
+       unsigned int i;
+
+       for (i = 0; i < BCM_SF2_STATS_SIZE; i++)
+               memcpy(data + i * ETH_GSTRING_LEN,
+                      bcm_sf2_mib[i].string, ETH_GSTRING_LEN);
+}
+
+static void bcm_sf2_sw_get_ethtool_stats(struct dsa_switch *ds,
+                                        int port, uint64_t *data)
+{
+       struct bcm_sf2_priv *priv = ds_to_priv(ds);
+       const struct bcm_sf2_hw_stats *s;
+       unsigned int i;
+       u64 val = 0;
+       u32 offset;
+
+       mutex_lock(&priv->stats_mutex);
+
+       /* Now fetch the per-port counters */
+       for (i = 0; i < BCM_SF2_STATS_SIZE; i++) {
+               s = &bcm_sf2_mib[i];
+
+               /* Do a latched 64-bit read if needed */
+               offset = s->reg + CORE_P_MIB_OFFSET(port);
+               if (s->sizeof_stat == 8)
+                       val = core_readq(priv, offset);
+               else
+                       val = core_readl(priv, offset);
+
+               data[i] = (u64)val;
+       }
+
+       mutex_unlock(&priv->stats_mutex);
+}
+
+static int bcm_sf2_sw_get_sset_count(struct dsa_switch *ds)
+{
+       return BCM_SF2_STATS_SIZE;
+}
+
+static char *bcm_sf2_sw_probe(struct mii_bus *bus, int sw_addr)
+{
+       return "Broadcom Starfighter 2";
+}
+
+static void bcm_sf2_imp_setup(struct dsa_switch *ds, int port)
+{
+       struct bcm_sf2_priv *priv = ds_to_priv(ds);
+       unsigned int i;
+       u32 reg, val;
+
+       /* Enable the port memories */
+       reg = core_readl(priv, CORE_MEM_PSM_VDD_CTRL);
+       reg &= ~P_TXQ_PSM_VDD(port);
+       core_writel(priv, reg, CORE_MEM_PSM_VDD_CTRL);
+
+       /* Enable Broadcast, Multicast, Unicast forwarding to IMP port */
+       reg = core_readl(priv, CORE_IMP_CTL);
+       reg |= (RX_BCST_EN | RX_MCST_EN | RX_UCST_EN);
+       reg &= ~(RX_DIS | TX_DIS);
+       core_writel(priv, reg, CORE_IMP_CTL);
+
+       /* Enable forwarding */
+       core_writel(priv, SW_FWDG_EN, CORE_SWMODE);
+
+       /* Enable IMP port in dumb mode */
+       reg = core_readl(priv, CORE_SWITCH_CTRL);
+       reg |= MII_DUMB_FWDG_EN;
+       core_writel(priv, reg, CORE_SWITCH_CTRL);
+
+       /* Resolve which bit controls the Broadcom tag */
+       switch (port) {
+       case 8:
+               val = BRCM_HDR_EN_P8;
+               break;
+       case 7:
+               val = BRCM_HDR_EN_P7;
+               break;
+       case 5:
+               val = BRCM_HDR_EN_P5;
+               break;
+       default:
+               val = 0;
+               break;
+       }
+
+       /* Enable Broadcom tags for IMP port */
+       reg = core_readl(priv, CORE_BRCM_HDR_CTRL);
+       reg |= val;
+       core_writel(priv, reg, CORE_BRCM_HDR_CTRL);
+
+       /* Enable reception Broadcom tag for CPU TX (switch RX) to
+        * allow us to tag outgoing frames
+        */
+       reg = core_readl(priv, CORE_BRCM_HDR_RX_DIS);
+       reg &= ~(1 << port);
+       core_writel(priv, reg, CORE_BRCM_HDR_RX_DIS);
+
+       /* Enable transmission of Broadcom tags from the switch (CPU RX) to
+        * allow delivering frames to the per-port net_devices
+        */
+       reg = core_readl(priv, CORE_BRCM_HDR_TX_DIS);
+       reg &= ~(1 << port);
+       core_writel(priv, reg, CORE_BRCM_HDR_TX_DIS);
+
+       /* Force link status for IMP port */
+       reg = core_readl(priv, CORE_STS_OVERRIDE_IMP);
+       reg |= (MII_SW_OR | LINK_STS);
+       core_writel(priv, reg, CORE_STS_OVERRIDE_IMP);
+
+       /* Enable the IMP Port to be in the same VLAN as the other ports
+        * on a per-port basis such that we only have Port i and IMP in
+        * the same VLAN.
+        */
+       for (i = 0; i < priv->hw_params.num_ports; i++) {
+               if (!((1 << i) & ds->phys_port_mask))
+                       continue;
+
+               reg = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(i));
+               reg |= (1 << port);
+               core_writel(priv, reg, CORE_PORT_VLAN_CTL_PORT(i));
+       }
+}
+
+static void bcm_sf2_port_setup(struct dsa_switch *ds, int port)
+{
+       struct bcm_sf2_priv *priv = ds_to_priv(ds);
+       u32 reg;
+
+       /* Clear the memory power down */
+       reg = core_readl(priv, CORE_MEM_PSM_VDD_CTRL);
+       reg &= ~P_TXQ_PSM_VDD(port);
+       core_writel(priv, reg, CORE_MEM_PSM_VDD_CTRL);
+
+       /* Clear the Rx and Tx disable bits and set to no spanning tree */
+       core_writel(priv, 0, CORE_G_PCTL_PORT(port));
+
+       /* Enable port 7 interrupts to get notified */
+       if (port == 7)
+               intrl2_1_mask_clear(priv, P_IRQ_MASK(P7_IRQ_OFF));
+
+       /* Set this port, and only this one to be in the default VLAN */
+       reg = core_readl(priv, CORE_PORT_VLAN_CTL_PORT(port));
+       reg &= ~PORT_VLAN_CTRL_MASK;
+       reg |= (1 << port);
+       core_writel(priv, reg, CORE_PORT_VLAN_CTL_PORT(port));
+}
+
+static void bcm_sf2_port_disable(struct dsa_switch *ds, int port)
+{
+       struct bcm_sf2_priv *priv = ds_to_priv(ds);
+       u32 off, reg;
+
+       if (dsa_is_cpu_port(ds, port))
+               off = CORE_IMP_CTL;
+       else
+               off = CORE_G_PCTL_PORT(port);
+
+       reg = core_readl(priv, off);
+       reg |= RX_DIS | TX_DIS;
+       core_writel(priv, reg, off);
+
+       /* Power down the port memory */
+       reg = core_readl(priv, CORE_MEM_PSM_VDD_CTRL);
+       reg |= P_TXQ_PSM_VDD(port);
+       core_writel(priv, reg, CORE_MEM_PSM_VDD_CTRL);
+}
+
+static irqreturn_t bcm_sf2_switch_0_isr(int irq, void *dev_id)
+{
+       struct bcm_sf2_priv *priv = dev_id;
+
+       priv->irq0_stat = intrl2_0_readl(priv, INTRL2_CPU_STATUS) &
+                               ~priv->irq0_mask;
+       intrl2_0_writel(priv, priv->irq0_stat, INTRL2_CPU_CLEAR);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t bcm_sf2_switch_1_isr(int irq, void *dev_id)
+{
+       struct bcm_sf2_priv *priv = dev_id;
+
+       priv->irq1_stat = intrl2_1_readl(priv, INTRL2_CPU_STATUS) &
+                               ~priv->irq1_mask;
+       intrl2_1_writel(priv, priv->irq1_stat, INTRL2_CPU_CLEAR);
+
+       if (priv->irq1_stat & P_LINK_UP_IRQ(P7_IRQ_OFF))
+               priv->port_sts[7].link = 1;
+       if (priv->irq1_stat & P_LINK_DOWN_IRQ(P7_IRQ_OFF))
+               priv->port_sts[7].link = 0;
+
+       return IRQ_HANDLED;
+}
+
+static int bcm_sf2_sw_setup(struct dsa_switch *ds)
+{
+       const char *reg_names[BCM_SF2_REGS_NUM] = BCM_SF2_REGS_NAME;
+       struct bcm_sf2_priv *priv = ds_to_priv(ds);
+       struct device_node *dn;
+       void __iomem **base;
+       unsigned int port;
+       unsigned int i;
+       u32 reg, rev;
+       int ret;
+
+       spin_lock_init(&priv->indir_lock);
+       mutex_init(&priv->stats_mutex);
+
+       /* All the interesting properties are at the parent device_node
+        * level
+        */
+       dn = ds->pd->of_node->parent;
+
+       priv->irq0 = irq_of_parse_and_map(dn, 0);
+       priv->irq1 = irq_of_parse_and_map(dn, 1);
+
+       base = &priv->core;
+       for (i = 0; i < BCM_SF2_REGS_NUM; i++) {
+               *base = of_iomap(dn, i);
+               if (*base == NULL) {
+                       pr_err("unable to find register: %s\n", reg_names[i]);
+                       return -ENODEV;
+               }
+               base++;
+       }
+
+       /* Disable all interrupts and request them */
+       intrl2_0_writel(priv, 0xffffffff, INTRL2_CPU_MASK_SET);
+       intrl2_0_writel(priv, 0xffffffff, INTRL2_CPU_CLEAR);
+       intrl2_0_writel(priv, 0, INTRL2_CPU_MASK_CLEAR);
+       intrl2_1_writel(priv, 0xffffffff, INTRL2_CPU_MASK_SET);
+       intrl2_1_writel(priv, 0xffffffff, INTRL2_CPU_CLEAR);
+       intrl2_1_writel(priv, 0, INTRL2_CPU_MASK_CLEAR);
+
+       ret = request_irq(priv->irq0, bcm_sf2_switch_0_isr, 0,
+                         "switch_0", priv);
+       if (ret < 0) {
+               pr_err("failed to request switch_0 IRQ\n");
+               goto out_unmap;
+       }
+
+       ret = request_irq(priv->irq1, bcm_sf2_switch_1_isr, 0,
+                         "switch_1", priv);
+       if (ret < 0) {
+               pr_err("failed to request switch_1 IRQ\n");
+               goto out_free_irq0;
+       }
+
+       /* Reset the MIB counters */
+       reg = core_readl(priv, CORE_GMNCFGCFG);
+       reg |= RST_MIB_CNT;
+       core_writel(priv, reg, CORE_GMNCFGCFG);
+       reg &= ~RST_MIB_CNT;
+       core_writel(priv, reg, CORE_GMNCFGCFG);
+
+       /* Get the maximum number of ports for this switch */
+       priv->hw_params.num_ports = core_readl(priv, CORE_IMP0_PRT_ID) + 1;
+       if (priv->hw_params.num_ports > DSA_MAX_PORTS)
+               priv->hw_params.num_ports = DSA_MAX_PORTS;
+
+       /* Assume a single GPHY setup if we can't read that property */
+       if (of_property_read_u32(dn, "brcm,num-gphy",
+                                &priv->hw_params.num_gphy))
+               priv->hw_params.num_gphy = 1;
+
+       /* Enable all valid ports and disable those unused */
+       for (port = 0; port < priv->hw_params.num_ports; port++) {
+               /* IMP port receives special treatment */
+               if ((1 << port) & ds->phys_port_mask)
+                       bcm_sf2_port_setup(ds, port);
+               else if (dsa_is_cpu_port(ds, port))
+                       bcm_sf2_imp_setup(ds, port);
+               else
+                       bcm_sf2_port_disable(ds, port);
+       }
+
+       /* Include the pseudo-PHY address and the broadcast PHY address to
+        * divert reads towards our workaround
+        */
+       ds->phys_mii_mask |= ((1 << 30) | (1 << 0));
+
+       rev = reg_readl(priv, REG_SWITCH_REVISION);
+       priv->hw_params.top_rev = (rev >> SWITCH_TOP_REV_SHIFT) &
+                                       SWITCH_TOP_REV_MASK;
+       priv->hw_params.core_rev = (rev & SF2_REV_MASK);
+
+       pr_info("Starfighter 2 top: %x.%02x, core: %x.%02x base: 0x%p, IRQs: %d, %d\n",
+               priv->hw_params.top_rev >> 8, priv->hw_params.top_rev & 0xff,
+               priv->hw_params.core_rev >> 8, priv->hw_params.core_rev & 0xff,
+               priv->core, priv->irq0, priv->irq1);
+
+       return 0;
+
+out_free_irq0:
+       free_irq(priv->irq0, priv);
+out_unmap:
+       base = &priv->core;
+       for (i = 0; i < BCM_SF2_REGS_NUM; i++) {
+               iounmap(*base);
+               base++;
+       }
+       return ret;
+}
+
+static int bcm_sf2_sw_set_addr(struct dsa_switch *ds, u8 *addr)
+{
+       return 0;
+}
+
+static int bcm_sf2_sw_indir_rw(struct dsa_switch *ds, int op, int addr,
+                              int regnum, u16 val)
+{
+       struct bcm_sf2_priv *priv = ds_to_priv(ds);
+       int ret = 0;
+       u32 reg;
+
+       reg = reg_readl(priv, REG_SWITCH_CNTRL);
+       reg |= MDIO_MASTER_SEL;
+       reg_writel(priv, reg, REG_SWITCH_CNTRL);
+
+       /* Page << 8 | offset */
+       reg = 0x70;
+       reg <<= 2;
+       core_writel(priv, addr, reg);
+
+       /* Page << 8 | offset */
+       reg = 0x80 << 8 | regnum << 1;
+       reg <<= 2;
+
+       if (op)
+               ret = core_readl(priv, reg);
+       else
+               core_writel(priv, val, reg);
+
+       reg = reg_readl(priv, REG_SWITCH_CNTRL);
+       reg &= ~MDIO_MASTER_SEL;
+       reg_writel(priv, reg, REG_SWITCH_CNTRL);
+
+       return ret & 0xffff;
+}
+
+static int bcm_sf2_sw_phy_read(struct dsa_switch *ds, int addr, int regnum)
+{
+       /* Intercept reads from the MDIO broadcast address or Broadcom
+        * pseudo-PHY address
+        */
+       switch (addr) {
+       case 0:
+       case 30:
+               return bcm_sf2_sw_indir_rw(ds, 1, addr, regnum, 0);
+       default:
+               return 0xffff;
+       }
+}
+
+static int bcm_sf2_sw_phy_write(struct dsa_switch *ds, int addr, int regnum,
+                               u16 val)
+{
+       /* Intercept writes to the MDIO broadcast address or Broadcom
+        * pseudo-PHY address
+        */
+       switch (addr) {
+       case 0:
+       case 30:
+               bcm_sf2_sw_indir_rw(ds, 0, addr, regnum, val);
+               break;
+       }
+
+       return 0;
+}
+
+static void bcm_sf2_sw_adjust_link(struct dsa_switch *ds, int port,
+                                  struct phy_device *phydev)
+{
+       struct bcm_sf2_priv *priv = ds_to_priv(ds);
+       u32 id_mode_dis = 0, port_mode;
+       const char *str = NULL;
+       u32 reg;
+
+       switch (phydev->interface) {
+       case PHY_INTERFACE_MODE_RGMII:
+               str = "RGMII (no delay)";
+               id_mode_dis = 1;
+       case PHY_INTERFACE_MODE_RGMII_TXID:
+               if (!str)
+                       str = "RGMII (TX delay)";
+               port_mode = EXT_GPHY;
+               break;
+       case PHY_INTERFACE_MODE_MII:
+               str = "MII";
+               port_mode = EXT_EPHY;
+               break;
+       case PHY_INTERFACE_MODE_REVMII:
+               str = "Reverse MII";
+               port_mode = EXT_REVMII;
+               break;
+       default:
+               goto force_link;
+       }
+
+       /* Clear id_mode_dis bit, and the existing port mode, but
+        * make sure we enable the RGMII block for data to pass
+        */
+       reg = reg_readl(priv, REG_RGMII_CNTRL_P(port));
+       reg &= ~ID_MODE_DIS;
+       reg &= ~(PORT_MODE_MASK << PORT_MODE_SHIFT);
+       reg &= ~(RX_PAUSE_EN | TX_PAUSE_EN);
+
+       reg |= port_mode | RGMII_MODE_EN;
+       if (id_mode_dis)
+               reg |= ID_MODE_DIS;
+
+       if (phydev->pause) {
+               if (phydev->asym_pause)
+                       reg |= TX_PAUSE_EN;
+               reg |= RX_PAUSE_EN;
+       }
+
+       reg_writel(priv, reg, REG_RGMII_CNTRL_P(port));
+
+       pr_info("Port %d configured for %s\n", port, str);
+
+force_link:
+       /* Force link settings detected from the PHY */
+       reg = SW_OVERRIDE;
+       switch (phydev->speed) {
+       case SPEED_1000:
+               reg |= SPDSTS_1000 << SPEED_SHIFT;
+               break;
+       case SPEED_100:
+               reg |= SPDSTS_100 << SPEED_SHIFT;
+               break;
+       }
+
+       if (phydev->link)
+               reg |= LINK_STS;
+       if (phydev->duplex == DUPLEX_FULL)
+               reg |= DUPLX_MODE;
+
+       core_writel(priv, reg, CORE_STS_OVERRIDE_GMIIP_PORT(port));
+}
+
+static void bcm_sf2_sw_fixed_link_update(struct dsa_switch *ds, int port,
+                                        struct fixed_phy_status *status)
+{
+       struct bcm_sf2_priv *priv = ds_to_priv(ds);
+       u32 link, duplex, pause, speed;
+       u32 reg;
+
+       link = core_readl(priv, CORE_LNKSTS);
+       duplex = core_readl(priv, CORE_DUPSTS);
+       pause = core_readl(priv, CORE_PAUSESTS);
+       speed = core_readl(priv, CORE_SPDSTS);
+
+       speed >>= (port * SPDSTS_SHIFT);
+       speed &= SPDSTS_MASK;
+
+       status->link = 0;
+
+       /* Port 7 is special as we do not get link status from CORE_LNKSTS,
+        * which means that we need to force the link at the port override
+        * level to get the data to flow. We do use what the interrupt handler
+        * did determine before.
+        */
+       if (port == 7) {
+               status->link = priv->port_sts[port].link;
+               reg = core_readl(priv, CORE_STS_OVERRIDE_GMIIP_PORT(7));
+               reg |= SW_OVERRIDE;
+               if (status->link)
+                       reg |= LINK_STS;
+               else
+                       reg &= ~LINK_STS;
+               core_writel(priv, reg, CORE_STS_OVERRIDE_GMIIP_PORT(7));
+               status->duplex = 1;
+       } else {
+               status->link = !!(link & (1 << port));
+               status->duplex = !!(duplex & (1 << port));
+       }
+
+       switch (speed) {
+       case SPDSTS_10:
+               status->speed = SPEED_10;
+               break;
+       case SPDSTS_100:
+               status->speed = SPEED_100;
+               break;
+       case SPDSTS_1000:
+               status->speed = SPEED_1000;
+               break;
+       }
+
+       if ((pause & (1 << port)) &&
+           (pause & (1 << (port + PAUSESTS_TX_PAUSE_SHIFT)))) {
+               status->asym_pause = 1;
+               status->pause = 1;
+       }
+
+       if (pause & (1 << port))
+               status->pause = 1;
+}
+
+static struct dsa_switch_driver bcm_sf2_switch_driver = {
+       .tag_protocol           = htons(ETH_P_BRCMTAG),
+       .priv_size              = sizeof(struct bcm_sf2_priv),
+       .probe                  = bcm_sf2_sw_probe,
+       .setup                  = bcm_sf2_sw_setup,
+       .set_addr               = bcm_sf2_sw_set_addr,
+       .phy_read               = bcm_sf2_sw_phy_read,
+       .phy_write              = bcm_sf2_sw_phy_write,
+       .get_strings            = bcm_sf2_sw_get_strings,
+       .get_ethtool_stats      = bcm_sf2_sw_get_ethtool_stats,
+       .get_sset_count         = bcm_sf2_sw_get_sset_count,
+       .adjust_link            = bcm_sf2_sw_adjust_link,
+       .fixed_link_update      = bcm_sf2_sw_fixed_link_update,
+};
+
+static int __init bcm_sf2_init(void)
+{
+       register_switch_driver(&bcm_sf2_switch_driver);
+
+       return 0;
+}
+module_init(bcm_sf2_init);
+
+static void __exit bcm_sf2_exit(void)
+{
+       unregister_switch_driver(&bcm_sf2_switch_driver);
+}
+module_exit(bcm_sf2_exit);
+
+MODULE_AUTHOR("Broadcom Corporation");
+MODULE_DESCRIPTION("Driver for Broadcom Starfighter 2 ethernet switch chip");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:brcm-sf2");
diff --git a/drivers/net/dsa/bcm_sf2.h b/drivers/net/dsa/bcm_sf2.h
new file mode 100644 (file)
index 0000000..260bab3
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Broadcom Starfighter2 private context
+ *
+ * Copyright (C) 2014, Broadcom Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __BCM_SF2_H
+#define __BCM_SF2_H
+
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/mii.h>
+
+#include <net/dsa.h>
+
+#include "bcm_sf2_regs.h"
+
+struct bcm_sf2_hw_params {
+       u16     top_rev;
+       u16     core_rev;
+       u32     num_gphy;
+       u8      num_acb_queue;
+       u8      num_rgmii;
+       u8      num_ports;
+       u8      fcb_pause_override:1;
+       u8      acb_packets_inflight:1;
+};
+
+#define BCM_SF2_REGS_NAME {\
+       "core", "reg", "intrl2_0", "intrl2_1", "fcb", "acb" \
+}
+
+#define BCM_SF2_REGS_NUM       6
+
+struct bcm_sf2_port_status {
+       unsigned int link;
+};
+
+struct bcm_sf2_priv {
+       /* Base registers, keep those in order with BCM_SF2_REGS_NAME */
+       void __iomem                    *core;
+       void __iomem                    *reg;
+       void __iomem                    *intrl2_0;
+       void __iomem                    *intrl2_1;
+       void __iomem                    *fcb;
+       void __iomem                    *acb;
+
+       /* spinlock protecting access to the indirect registers */
+       spinlock_t                      indir_lock;
+
+       int                             irq0;
+       int                             irq1;
+       u32                             irq0_stat;
+       u32                             irq0_mask;
+       u32                             irq1_stat;
+       u32                             irq1_mask;
+
+       /* Mutex protecting access to the MIB counters */
+       struct mutex                    stats_mutex;
+
+       struct bcm_sf2_hw_params        hw_params;
+
+       struct bcm_sf2_port_status      port_sts[DSA_MAX_PORTS];
+};
+
+struct bcm_sf2_hw_stats {
+       const char      *string;
+       u16             reg;
+       u8              sizeof_stat;
+};
+
+#define SF2_IO_MACRO(name) \
+static inline u32 name##_readl(struct bcm_sf2_priv *priv, u32 off)     \
+{                                                                      \
+       return __raw_readl(priv->name + off);                           \
+}                                                                      \
+static inline void name##_writel(struct bcm_sf2_priv *priv,            \
+                                 u32 val, u32 off)                     \
+{                                                                      \
+       __raw_writel(val, priv->name + off);                            \
+}                                                                      \
+
+/* Accesses to 64-bits register requires us to latch the hi/lo pairs
+ * using the REG_DIR_DATA_{READ,WRITE} ancillary registers. The 'indir_lock'
+ * spinlock is automatically grabbed and released to provide relative
+ * atomiticy with latched reads/writes.
+ */
+#define SF2_IO64_MACRO(name) \
+static inline u64 name##_readq(struct bcm_sf2_priv *priv, u32 off)     \
+{                                                                      \
+       u32 indir, dir;                                                 \
+       spin_lock(&priv->indir_lock);                                   \
+       indir = reg_readl(priv, REG_DIR_DATA_READ);                     \
+       dir = __raw_readl(priv->name + off);                            \
+       spin_unlock(&priv->indir_lock);                                 \
+       return (u64)indir << 32 | dir;                                  \
+}                                                                      \
+static inline void name##_writeq(struct bcm_sf2_priv *priv, u32 off,   \
+                                                       u64 val)        \
+{                                                                      \
+       spin_lock(&priv->indir_lock);                                   \
+       reg_writel(priv, upper_32_bits(val), REG_DIR_DATA_WRITE);       \
+       __raw_writel(lower_32_bits(val), priv->name + off);             \
+       spin_unlock(&priv->indir_lock);                                 \
+}
+
+#define SWITCH_INTR_L2(which)                                          \
+static inline void intrl2_##which##_mask_clear(struct bcm_sf2_priv *priv, \
+                                               u32 mask)               \
+{                                                                      \
+       intrl2_##which##_writel(priv, mask, INTRL2_CPU_MASK_CLEAR);     \
+       priv->irq##which##_mask &= ~(mask);                             \
+}                                                                      \
+static inline void intrl2_##which##_mask_set(struct bcm_sf2_priv *priv, \
+                                               u32 mask)               \
+{                                                                      \
+       intrl2_## which##_writel(priv, mask, INTRL2_CPU_MASK_SET);      \
+       priv->irq##which##_mask |= (mask);                              \
+}                                                                      \
+
+SF2_IO_MACRO(core);
+SF2_IO_MACRO(reg);
+SF2_IO64_MACRO(core);
+SF2_IO_MACRO(intrl2_0);
+SF2_IO_MACRO(intrl2_1);
+SF2_IO_MACRO(fcb);
+SF2_IO_MACRO(acb);
+
+SWITCH_INTR_L2(0);
+SWITCH_INTR_L2(1);
+
+#endif /* __BCM_SF2_H */
diff --git a/drivers/net/dsa/bcm_sf2_regs.h b/drivers/net/dsa/bcm_sf2_regs.h
new file mode 100644 (file)
index 0000000..885c231
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Broadcom Starfighter 2 switch register defines
+ *
+ * Copyright (C) 2014, Broadcom Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef __BCM_SF2_REGS_H
+#define __BCM_SF2_REGS_H
+
+/* Register set relative to 'REG' */
+#define REG_SWITCH_CNTRL               0x00
+#define  MDIO_MASTER_SEL               (1 << 0)
+
+#define REG_SWITCH_STATUS              0x04
+#define REG_DIR_DATA_WRITE             0x08
+#define REG_DIR_DATA_READ              0x0C
+
+#define REG_SWITCH_REVISION            0x18
+#define  SF2_REV_MASK                  0xffff
+#define  SWITCH_TOP_REV_SHIFT          16
+#define  SWITCH_TOP_REV_MASK           0xffff
+
+#define REG_PHY_REVISION               0x1C
+
+#define REG_SPHY_CNTRL                 0x2C
+#define  IDDQ_BIAS                     (1 << 0)
+#define  EXT_PWR_DOWN                  (1 << 1)
+#define  FORCE_DLL_EN                  (1 << 2)
+#define  IDDQ_GLOBAL_PWR               (1 << 3)
+#define  CK25_DIS                      (1 << 4)
+#define  PHY_RESET                     (1 << 5)
+#define  PHY_PHYAD_SHIFT               8
+#define  PHY_PHYAD_MASK                        0x1F
+
+#define REG_RGMII_0_BASE               0x34
+#define REG_RGMII_CNTRL                        0x00
+#define REG_RGMII_IB_STATUS            0x04
+#define REG_RGMII_RX_CLOCK_DELAY_CNTRL 0x08
+#define REG_RGMII_CNTRL_SIZE           0x0C
+#define REG_RGMII_CNTRL_P(x)           (REG_RGMII_0_BASE + \
+                                       ((x) * REG_RGMII_CNTRL_SIZE))
+/* Relative to REG_RGMII_CNTRL */
+#define  RGMII_MODE_EN                 (1 << 0)
+#define  ID_MODE_DIS                   (1 << 1)
+#define  PORT_MODE_SHIFT               2
+#define  INT_EPHY                      (0 << PORT_MODE_SHIFT)
+#define  INT_GPHY                      (1 << PORT_MODE_SHIFT)
+#define  EXT_EPHY                      (2 << PORT_MODE_SHIFT)
+#define  EXT_GPHY                      (3 << PORT_MODE_SHIFT)
+#define  EXT_REVMII                    (4 << PORT_MODE_SHIFT)
+#define  PORT_MODE_MASK                        0x7
+#define  RVMII_REF_SEL                 (1 << 5)
+#define  RX_PAUSE_EN                   (1 << 6)
+#define  TX_PAUSE_EN                   (1 << 7)
+#define  TX_CLK_STOP_EN                        (1 << 8)
+#define  LPI_COUNT_SHIFT               9
+#define  LPI_COUNT_MASK                        0x3F
+
+/* Register set relative to 'INTRL2_0' and 'INTRL2_1' */
+#define INTRL2_CPU_STATUS              0x00
+#define INTRL2_CPU_SET                 0x04
+#define INTRL2_CPU_CLEAR               0x08
+#define INTRL2_CPU_MASK_STATUS         0x0c
+#define INTRL2_CPU_MASK_SET            0x10
+#define INTRL2_CPU_MASK_CLEAR          0x14
+
+/* Shared INTRL2_0 and INTRL2_ interrupt sources macros */
+#define P_LINK_UP_IRQ(x)               (1 << (0 + (x)))
+#define P_LINK_DOWN_IRQ(x)             (1 << (1 + (x)))
+#define P_ENERGY_ON_IRQ(x)             (1 << (2 + (x)))
+#define P_ENERGY_OFF_IRQ(x)            (1 << (3 + (x)))
+#define P_GPHY_IRQ(x)                  (1 << (4 + (x)))
+#define P_NUM_IRQ                      5
+#define P_IRQ_MASK(x)                  (P_LINK_UP_IRQ((x)) | \
+                                        P_LINK_DOWN_IRQ((x)) | \
+                                        P_ENERGY_ON_IRQ((x)) | \
+                                        P_ENERGY_OFF_IRQ((x)) | \
+                                        P_GPHY_IRQ((x)))
+
+/* INTRL2_0 interrupt sources */
+#define P0_IRQ_OFF                     0
+#define MEM_DOUBLE_IRQ                 (1 << 5)
+#define EEE_LPI_IRQ                    (1 << 6)
+#define P5_CPU_WAKE_IRQ                        (1 << 7)
+#define P8_CPU_WAKE_IRQ                        (1 << 8)
+#define P7_CPU_WAKE_IRQ                        (1 << 9)
+#define IEEE1588_IRQ                   (1 << 10)
+#define MDIO_ERR_IRQ                   (1 << 11)
+#define MDIO_DONE_IRQ                  (1 << 12)
+#define GISB_ERR_IRQ                   (1 << 13)
+#define UBUS_ERR_IRQ                   (1 << 14)
+#define FAILOVER_ON_IRQ                        (1 << 15)
+#define FAILOVER_OFF_IRQ               (1 << 16)
+#define TCAM_SOFT_ERR_IRQ              (1 << 17)
+
+/* INTRL2_1 interrupt sources */
+#define P7_IRQ_OFF                     0
+#define P_IRQ_OFF(x)                   ((6 - (x)) * P_NUM_IRQ)
+
+/* Register set relative to 'CORE' */
+#define CORE_G_PCTL_PORT0              0x00000
+#define CORE_G_PCTL_PORT(x)            (CORE_G_PCTL_PORT0 + (x * 0x4))
+#define CORE_IMP_CTL                   0x00020
+#define  RX_DIS                                (1 << 0)
+#define  TX_DIS                                (1 << 1)
+#define  RX_BCST_EN                    (1 << 2)
+#define  RX_MCST_EN                    (1 << 3)
+#define  RX_UCST_EN                    (1 << 4)
+#define  G_MISTP_STATE_SHIFT           5
+#define  G_MISTP_NO_STP                        (0 << G_MISTP_STATE_SHIFT)
+#define  G_MISTP_DIS_STATE             (1 << G_MISTP_STATE_SHIFT)
+#define  G_MISTP_BLOCK_STATE           (2 << G_MISTP_STATE_SHIFT)
+#define  G_MISTP_LISTEN_STATE          (3 << G_MISTP_STATE_SHIFT)
+#define  G_MISTP_LEARN_STATE           (4 << G_MISTP_STATE_SHIFT)
+#define  G_MISTP_FWD_STATE             (5 << G_MISTP_STATE_SHIFT)
+#define  G_MISTP_STATE_MASK            0x7
+
+#define CORE_SWMODE                    0x0002c
+#define  SW_FWDG_MODE                  (1 << 0)
+#define  SW_FWDG_EN                    (1 << 1)
+#define  RTRY_LMT_DIS                  (1 << 2)
+
+#define CORE_STS_OVERRIDE_IMP          0x00038
+#define  GMII_SPEED_UP_2G              (1 << 6)
+#define  MII_SW_OR                     (1 << 7)
+
+#define CORE_NEW_CTRL                  0x00084
+#define  IP_MC                         (1 << 0)
+#define  OUTRANGEERR_DISCARD           (1 << 1)
+#define  INRANGEERR_DISCARD            (1 << 2)
+#define  CABLE_DIAG_LEN                        (1 << 3)
+#define  OVERRIDE_AUTO_PD_WAR          (1 << 4)
+#define  EN_AUTO_PD_WAR                        (1 << 5)
+#define  UC_FWD_EN                     (1 << 6)
+#define  MC_FWD_EN                     (1 << 7)
+
+#define CORE_SWITCH_CTRL               0x00088
+#define  MII_DUMB_FWDG_EN              (1 << 6)
+
+#define CORE_SFT_LRN_CTRL              0x000f8
+#define  SW_LEARN_CNTL(x)              (1 << (x))
+
+#define CORE_STS_OVERRIDE_GMIIP_PORT(x)        (0x160 + (x) * 4)
+#define  LINK_STS                      (1 << 0)
+#define  DUPLX_MODE                    (1 << 1)
+#define  SPEED_SHIFT                   2
+#define  SPEED_MASK                    0x3
+#define  RXFLOW_CNTL                   (1 << 4)
+#define  TXFLOW_CNTL                   (1 << 5)
+#define  SW_OVERRIDE                   (1 << 6)
+
+#define CORE_WATCHDOG_CTRL             0x001e4
+#define  SOFTWARE_RESET                        (1 << 7)
+#define  EN_CHIP_RST                   (1 << 6)
+#define  EN_SW_RESET                   (1 << 4)
+
+#define CORE_LNKSTS                    0x00400
+#define  LNK_STS_MASK                  0x1ff
+
+#define CORE_SPDSTS                    0x00410
+#define  SPDSTS_10                     0
+#define  SPDSTS_100                    1
+#define  SPDSTS_1000                   2
+#define  SPDSTS_SHIFT                  2
+#define  SPDSTS_MASK                   0x3
+
+#define CORE_DUPSTS                    0x00420
+#define  CORE_DUPSTS_MASK              0x1ff
+
+#define CORE_PAUSESTS                  0x00428
+#define  PAUSESTS_TX_PAUSE_SHIFT       9
+
+#define CORE_GMNCFGCFG                 0x0800
+#define  RST_MIB_CNT                   (1 << 0)
+#define  RXBPDU_EN                     (1 << 1)
+
+#define CORE_IMP0_PRT_ID               0x0804
+
+#define CORE_BRCM_HDR_CTRL             0x0080c
+#define  BRCM_HDR_EN_P8                        (1 << 0)
+#define  BRCM_HDR_EN_P5                        (1 << 1)
+#define  BRCM_HDR_EN_P7                        (1 << 2)
+
+#define CORE_BRCM_HDR_CTRL2            0x0828
+
+#define CORE_HL_PRTC_CTRL              0x0940
+#define  ARP_EN                                (1 << 0)
+#define  RARP_EN                       (1 << 1)
+#define  DHCP_EN                       (1 << 2)
+#define  ICMPV4_EN                     (1 << 3)
+#define  ICMPV6_EN                     (1 << 4)
+#define  ICMPV6_FWD_MODE               (1 << 5)
+#define  IGMP_DIP_EN                   (1 << 8)
+#define  IGMP_RPTLVE_EN                        (1 << 9)
+#define  IGMP_RTPLVE_FWD_MODE          (1 << 10)
+#define  IGMP_QRY_EN                   (1 << 11)
+#define  IGMP_QRY_FWD_MODE             (1 << 12)
+#define  IGMP_UKN_EN                   (1 << 13)
+#define  IGMP_UKN_FWD_MODE             (1 << 14)
+#define  MLD_RPTDONE_EN                        (1 << 15)
+#define  MLD_RPTDONE_FWD_MODE          (1 << 16)
+#define  MLD_QRY_EN                    (1 << 17)
+#define  MLD_QRY_FWD_MODE              (1 << 18)
+
+#define CORE_RST_MIB_CNT_EN            0x0950
+
+#define CORE_BRCM_HDR_RX_DIS           0x0980
+#define CORE_BRCM_HDR_TX_DIS           0x0988
+
+#define CORE_MEM_PSM_VDD_CTRL          0x2380
+#define  P_TXQ_PSM_VDD_SHIFT           2
+#define  P_TXQ_PSM_VDD_MASK            0x3
+#define  P_TXQ_PSM_VDD(x)              (P_TXQ_PSM_VDD_MASK << \
+                                       ((x) * P_TXQ_PSM_VDD_SHIFT))
+
+#define        CORE_P0_MIB_OFFSET              0x8000
+#define P_MIB_SIZE                     0x400
+#define CORE_P_MIB_OFFSET(x)           (CORE_P0_MIB_OFFSET + (x) * P_MIB_SIZE)
+
+#define CORE_PORT_VLAN_CTL_PORT(x)     (0xc400 + ((x) * 0x8))
+#define  PORT_VLAN_CTRL_MASK           0x1ff
+
+#endif /* __BCM_SF2_REGS_H */
index 65de0cab8d07795c5c2255d7397a95e3720477fc..28437ab9c7bde976b9fd7468a2a109f21a817f24 100644 (file)
@@ -205,6 +205,14 @@ config MDIO_BUS_MUX_MMIOREG
 
          Currently, only 8-bit registers are supported.
 
+config MDIO_BCM_UNIMAC
+       tristate "Broadcom UniMAC MDIO bus controller"
+       help
+         This module provides a driver for the Broadcom UniMAC MDIO busses.
+         This hardware can be found in the Broadcom GENET Ethernet MAC
+         controllers as well as some Broadcom Ethernet switches such as the
+         Starfighter 2 switches.
+
 endif # PHYLIB
 
 config MICREL_KS8995MA
index 7dc3d5b304cfc250a2618e096abecf428ed05bad..eb3b18b5978b33ec7441a2ca2e0f4aa33906cdd5 100644 (file)
@@ -34,3 +34,4 @@ obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o
 obj-$(CONFIG_MDIO_SUN4I)       += mdio-sun4i.o
 obj-$(CONFIG_MDIO_MOXART)      += mdio-moxart.o
 obj-$(CONFIG_AMD_XGBE_PHY)     += amd-xgbe-phy.o
+obj-$(CONFIG_MDIO_BCM_UNIMAC)  += mdio-bcm-unimac.o
diff --git a/drivers/net/phy/mdio-bcm-unimac.c b/drivers/net/phy/mdio-bcm-unimac.c
new file mode 100644 (file)
index 0000000..e6b08ce
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Broadcom UniMAC MDIO bus controller driver
+ *
+ * Copyright (C) 2014, Broadcom Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_mdio.h>
+
+#define MDIO_CMD               0x00
+#define  MDIO_START_BUSY       (1 << 29)
+#define  MDIO_READ_FAIL                (1 << 28)
+#define  MDIO_RD               (2 << 26)
+#define  MDIO_WR               (1 << 26)
+#define  MDIO_PMD_SHIFT                21
+#define  MDIO_PMD_MASK         0x1F
+#define  MDIO_REG_SHIFT                16
+#define  MDIO_REG_MASK         0x1F
+
+#define MDIO_CFG               0x04
+#define  MDIO_C22              (1 << 0)
+#define  MDIO_C45              0
+#define  MDIO_CLK_DIV_SHIFT    4
+#define  MDIO_CLK_DIV_MASK     0x3F
+#define  MDIO_SUPP_PREAMBLE    (1 << 12)
+
+struct unimac_mdio_priv {
+       struct mii_bus          *mii_bus;
+       void __iomem            *base;
+};
+
+static inline void unimac_mdio_start(struct unimac_mdio_priv *priv)
+{
+       u32 reg;
+
+       reg = __raw_readl(priv->base + MDIO_CMD);
+       reg |= MDIO_START_BUSY;
+       __raw_writel(reg, priv->base + MDIO_CMD);
+}
+
+static inline unsigned int unimac_mdio_busy(struct unimac_mdio_priv *priv)
+{
+       return __raw_readl(priv->base + MDIO_CMD) & MDIO_START_BUSY;
+}
+
+static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg)
+{
+       struct unimac_mdio_priv *priv = bus->priv;
+       unsigned int timeout = 1000;
+       u32 cmd;
+
+       /* Prepare the read operation */
+       cmd = MDIO_RD | (phy_id << MDIO_PMD_SHIFT) | (reg << MDIO_REG_SHIFT);
+       __raw_writel(cmd, priv->base + MDIO_CMD);
+
+       /* Start MDIO transaction */
+       unimac_mdio_start(priv);
+
+       do {
+               if (!unimac_mdio_busy(priv))
+                       break;
+
+               usleep_range(1000, 2000);
+       } while (timeout--);
+
+       if (!timeout)
+               return -ETIMEDOUT;
+
+       cmd = __raw_readl(priv->base + MDIO_CMD);
+       if (cmd & MDIO_READ_FAIL)
+               return -EIO;
+
+       return cmd & 0xffff;
+}
+
+static int unimac_mdio_write(struct mii_bus *bus, int phy_id,
+                            int reg, u16 val)
+{
+       struct unimac_mdio_priv *priv = bus->priv;
+       unsigned int timeout = 1000;
+       u32 cmd;
+
+       /* Prepare the write operation */
+       cmd = MDIO_WR | (phy_id << MDIO_PMD_SHIFT) |
+               (reg << MDIO_REG_SHIFT) | (0xffff & val);
+       __raw_writel(cmd, priv->base + MDIO_CMD);
+
+       unimac_mdio_start(priv);
+
+       do {
+               if (!unimac_mdio_busy(priv))
+                       break;
+
+               usleep_range(1000, 2000);
+       } while (timeout--);
+
+       if (!timeout)
+               return -ETIMEDOUT;
+
+       return 0;
+}
+
+static int unimac_mdio_probe(struct platform_device *pdev)
+{
+       struct unimac_mdio_priv *priv;
+       struct device_node *np;
+       struct mii_bus *bus;
+       struct resource *r;
+       int ret;
+
+       np = pdev->dev.of_node;
+
+       priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+       /* Just ioremap, as this MDIO block is usually integrated into an
+        * Ethernet MAC controller register range
+        */
+       priv->base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
+       if (!priv->base) {
+               dev_err(&pdev->dev, "failed to remap register\n");
+               return -ENOMEM;
+       }
+
+       priv->mii_bus = mdiobus_alloc();
+       if (!priv->mii_bus)
+               return -ENOMEM;
+
+       bus = priv->mii_bus;
+       bus->priv = priv;
+       bus->name = "unimac MII bus";
+       bus->parent = &pdev->dev;
+       bus->read = unimac_mdio_read;
+       bus->write = unimac_mdio_write;
+       snprintf(bus->id, MII_BUS_ID_SIZE, "%s", pdev->name);
+
+       bus->irq = kcalloc(PHY_MAX_ADDR, sizeof(int), GFP_KERNEL);
+       if (!bus->irq) {
+               ret = -ENOMEM;
+               goto out_mdio_free;
+       }
+
+       ret = of_mdiobus_register(bus, np);
+       if (ret) {
+               dev_err(&pdev->dev, "MDIO bus registration failed\n");
+               goto out_mdio_irq;
+       }
+
+       platform_set_drvdata(pdev, priv);
+
+       dev_info(&pdev->dev, "Broadcom UniMAC MDIO bus at 0x%p\n", priv->base);
+
+       return 0;
+
+out_mdio_irq:
+       kfree(bus->irq);
+out_mdio_free:
+       mdiobus_free(bus);
+       return ret;
+}
+
+static int unimac_mdio_remove(struct platform_device *pdev)
+{
+       struct unimac_mdio_priv *priv = platform_get_drvdata(pdev);
+
+       mdiobus_unregister(priv->mii_bus);
+       kfree(priv->mii_bus->irq);
+       mdiobus_free(priv->mii_bus);
+
+       return 0;
+}
+
+static struct of_device_id unimac_mdio_ids[] = {
+       { .compatible = "brcm,genet-mdio-v4", },
+       { .compatible = "brcm,genet-mdio-v3", },
+       { .compatible = "brcm,genet-mdio-v2", },
+       { .compatible = "brcm,genet-mdio-v1", },
+       { .compatible = "brcm,unimac-mdio", },
+};
+
+static struct platform_driver unimac_mdio_driver = {
+       .driver = {
+               .name = "unimac-mdio",
+               .owner = THIS_MODULE,
+               .of_match_table = unimac_mdio_ids,
+       },
+       .probe  = unimac_mdio_probe,
+       .remove = unimac_mdio_remove,
+};
+module_platform_driver(unimac_mdio_driver);
+
+MODULE_AUTHOR("Broadcom Corporation");
+MODULE_DESCRIPTION("Broadcom UniMAC MDIO bus controller");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:unimac-mdio");
index 039b23786c2229e1b147fae5b28589f80223da32..429801370d0c45c677f0cd9667cc07d17f531490 100644 (file)
@@ -1781,24 +1781,13 @@ void dev_net_set(struct net_device *dev, struct net *net)
 #endif
 }
 
-static inline bool netdev_uses_dsa_tags(struct net_device *dev)
+static inline bool netdev_uses_dsa(struct net_device *dev)
 {
-#ifdef CONFIG_NET_DSA_TAG_DSA
+#ifdef CONFIG_NET_DSA
        if (dev->dsa_ptr != NULL)
-               return dsa_uses_dsa_tags(dev->dsa_ptr);
+               return dsa_uses_tagged_protocol(dev->dsa_ptr);
 #endif
-
-       return 0;
-}
-
-static inline bool netdev_uses_trailer_tags(struct net_device *dev)
-{
-#ifdef CONFIG_NET_DSA_TAG_TRAILER
-       if (dev->dsa_ptr != NULL)
-               return dsa_uses_trailer_tags(dev->dsa_ptr);
-#endif
-
-       return 0;
+       return false;
 }
 
 /**
@@ -1933,6 +1922,13 @@ struct udp_offload {
        struct offload_callbacks callbacks;
 };
 
+struct dsa_device_ops {
+       netdev_tx_t (*xmit)(struct sk_buff *skb, struct net_device *dev);
+       int (*rcv)(struct sk_buff *skb, struct net_device *dev,
+                  struct packet_type *pt, struct net_device *orig_dev);
+};
+
+
 /* often modified stats are per cpu, other are shared (netdev->stats) */
 struct pcpu_sw_netstats {
        u64     rx_packets;
index ae612acebb53d2f0bf121226afde877cb4d32f91..941138664c1db88c61e83fe53fdaf6fc31c73b7b 100644 (file)
@@ -18,6 +18,9 @@ extern int fixed_phy_register(unsigned int irq,
                              struct fixed_phy_status *status,
                              struct device_node *np);
 extern void fixed_phy_del(int phy_addr);
+extern int fixed_phy_set_link_update(struct phy_device *phydev,
+                       int (*link_update)(struct net_device *,
+                                          struct fixed_phy_status *));
 #else
 static inline int fixed_phy_add(unsigned int irq, int phy_id,
                                struct fixed_phy_status *status)
@@ -34,14 +37,12 @@ static inline int fixed_phy_del(int phy_addr)
 {
        return -ENODEV;
 }
-#endif /* CONFIG_FIXED_PHY */
-
-/*
- * This function issued only by fixed_phy-aware drivers, no need
- * protect it with #ifdef
- */
-extern int fixed_phy_set_link_update(struct phy_device *phydev,
+static inline int fixed_phy_set_link_update(struct phy_device *phydev,
                        int (*link_update)(struct net_device *,
-                                          struct fixed_phy_status *));
+                                          struct fixed_phy_status *))
+{
+       return -ENODEV;
+}
+#endif /* CONFIG_FIXED_PHY */
 
 #endif /* __PHY_FIXED_H */
index 6efce384451e56f16d8aab0847a6a7be4e94e0a0..97712927a9d2a5f50f09932ec54e7db01f25ef6b 100644 (file)
 #include <linux/list.h>
 #include <linux/timer.h>
 #include <linux/workqueue.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/phy_fixed.h>
+
+/* Not an official ethertype value, used only internally for DSA
+ * demultiplexing
+ */
+#define ETH_P_BRCMTAG          (ETH_P_XDSA + 1)
 
 #define DSA_MAX_SWITCHES       4
 #define DSA_MAX_PORTS          12
@@ -26,6 +34,12 @@ struct dsa_chip_data {
        struct device   *mii_bus;
        int             sw_addr;
 
+       /* Device tree node pointer for this specific switch chip
+        * used during switch setup in case additional properties
+        * and resources needs to be used
+        */
+       struct device_node *of_node;
+
        /*
         * The names of the switch's ports.  Use "cpu" to
         * designate the switch port that the cpu is connected to,
@@ -34,6 +48,7 @@ struct dsa_chip_data {
         * or any other string to indicate this is a physical port.
         */
        char            *port_names[DSA_MAX_PORTS];
+       struct device_node *port_dn[DSA_MAX_PORTS];
 
        /*
         * An array (with nr_chips elements) of which element [a]
@@ -59,6 +74,8 @@ struct dsa_platform_data {
        struct dsa_chip_data    *chip;
 };
 
+struct dsa_device_ops;
+
 struct dsa_switch_tree {
        /*
         * Configuration data for the platform device that owns
@@ -71,6 +88,7 @@ struct dsa_switch_tree {
         * protocol to use.
         */
        struct net_device       *master_netdev;
+       const struct dsa_device_ops     *ops;
        __be16                  tag_protocol;
 
        /*
@@ -119,6 +137,7 @@ struct dsa_switch {
         */
        u32                     dsa_port_mask;
        u32                     phys_port_mask;
+       u32                     phys_mii_mask;
        struct mii_bus          *slave_mii_bus;
        struct net_device       *ports[DSA_MAX_PORTS];
 };
@@ -169,6 +188,14 @@ struct dsa_switch_driver {
         */
        void    (*poll_link)(struct dsa_switch *ds);
 
+       /*
+        * Link state adjustment (called from libphy)
+        */
+       void    (*adjust_link)(struct dsa_switch *ds, int port,
+                               struct phy_device *phydev);
+       void    (*fixed_link_update)(struct dsa_switch *ds, int port,
+                               struct fixed_phy_status *st);
+
        /*
         * ethtool hardware statistics.
         */
@@ -186,21 +213,9 @@ static inline void *ds_to_priv(struct dsa_switch *ds)
        return (void *)(ds + 1);
 }
 
-/*
- * The original DSA tag format and some other tag formats have no
- * ethertype, which means that we need to add a little hack to the
- * networking receive path to make sure that received frames get
- * the right ->protocol assigned to them when one of those tag
- * formats is in use.
- */
-static inline bool dsa_uses_dsa_tags(struct dsa_switch_tree *dst)
-{
-       return !!(dst->tag_protocol == htons(ETH_P_DSA));
-}
-
-static inline bool dsa_uses_trailer_tags(struct dsa_switch_tree *dst)
+static inline bool dsa_uses_tagged_protocol(struct dsa_switch_tree *dst)
 {
-       return !!(dst->tag_protocol == htons(ETH_P_TRAILER));
+       return dst->tag_protocol != 0;
 }
 
 #endif
index 0f8210b8e0bc47ac0b7faab45a12ca6dcfbdf72d..aa63ed023c2b96b61b42231f9dd9b34b6ae46b66 100644 (file)
 #define ETH_P_PHONET   0x00F5          /* Nokia Phonet frames          */
 #define ETH_P_IEEE802154 0x00F6                /* IEEE802.15.4 frame           */
 #define ETH_P_CAIF     0x00F7          /* ST-Ericsson CAIF protocol    */
+#define ETH_P_XDSA     0x00F8          /* Multiplexed DSA protocol     */
 
 /*
  *     This is an Ethernet frame header.
index f5eede1d6cb8eb92cbf97b1b37de1362f13ac095..a585fd6352ebaf8e8bdf0e9772653857def08811 100644 (file)
@@ -12,6 +12,9 @@ config NET_DSA
 if NET_DSA
 
 # tagging formats
+config NET_DSA_TAG_BRCM
+       bool
+
 config NET_DSA_TAG_DSA
        bool
 
index 7b9fcbbeda5d0d80678ba909f76b0b30d8436a2d..da06ed1df620fc620a90ab06cfadad29ce26f3bf 100644 (file)
@@ -3,6 +3,7 @@ obj-$(CONFIG_NET_DSA) += dsa_core.o
 dsa_core-y += dsa.o slave.o
 
 # tagging formats
+dsa_core-$(CONFIG_NET_DSA_TAG_BRCM) += tag_brcm.o
 dsa_core-$(CONFIG_NET_DSA_TAG_DSA) += tag_dsa.o
 dsa_core-$(CONFIG_NET_DSA_TAG_EDSA) += tag_edsa.o
 dsa_core-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o
index 0a49632fac478f1ac17b3f0159cbdf748fe25110..484f695351a7fe95b9615fc4fcb6e988cc073768 100644 (file)
@@ -144,6 +144,11 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
                goto out;
        }
 
+       /* Make the built-in MII bus mask match the number of ports,
+        * switch drivers can override this later
+        */
+       ds->phys_mii_mask = ds->phys_port_mask;
+
        /*
         * If the CPU connects to this switch, set the switch tree
         * tagging protocol to the preferred tagging format of this
@@ -410,6 +415,7 @@ static int dsa_of_probe(struct platform_device *pdev)
                chip_index++;
                cd = &pd->chip[chip_index];
 
+               cd->of_node = child;
                cd->mii_bus = &mdio_bus->dev;
 
                sw_addr = of_get_property(child, "reg", NULL);
@@ -431,6 +437,8 @@ static int dsa_of_probe(struct platform_device *pdev)
                        if (!port_name)
                                continue;
 
+                       cd->port_dn[port_index] = port;
+
                        cd->port_names[port_index] = kstrdup(port_name,
                                        GFP_KERNEL);
                        if (!cd->port_names[port_index]) {
@@ -608,7 +616,26 @@ static void dsa_shutdown(struct platform_device *pdev)
 {
 }
 
+static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev,
+                         struct packet_type *pt, struct net_device *orig_dev)
+{
+       struct dsa_switch_tree *dst = dev->dsa_ptr;
+
+       if (unlikely(dst == NULL)) {
+               kfree_skb(skb);
+               return 0;
+       }
+
+       return dst->ops->rcv(skb, dev, pt, orig_dev);
+}
+
+struct packet_type dsa_pack_type __read_mostly = {
+       .type   = cpu_to_be16(ETH_P_XDSA),
+       .func   = dsa_switch_rcv,
+};
+
 static const struct of_device_id dsa_of_match_table[] = {
+       { .compatible = "brcm,bcm7445-switch-v4.0" },
        { .compatible = "marvell,dsa", },
        {}
 };
@@ -633,30 +660,15 @@ static int __init dsa_init_module(void)
        if (rc)
                return rc;
 
-#ifdef CONFIG_NET_DSA_TAG_DSA
-       dev_add_pack(&dsa_packet_type);
-#endif
-#ifdef CONFIG_NET_DSA_TAG_EDSA
-       dev_add_pack(&edsa_packet_type);
-#endif
-#ifdef CONFIG_NET_DSA_TAG_TRAILER
-       dev_add_pack(&trailer_packet_type);
-#endif
+       dev_add_pack(&dsa_pack_type);
+
        return 0;
 }
 module_init(dsa_init_module);
 
 static void __exit dsa_cleanup_module(void)
 {
-#ifdef CONFIG_NET_DSA_TAG_TRAILER
-       dev_remove_pack(&trailer_packet_type);
-#endif
-#ifdef CONFIG_NET_DSA_TAG_EDSA
-       dev_remove_pack(&edsa_packet_type);
-#endif
-#ifdef CONFIG_NET_DSA_TAG_DSA
-       dev_remove_pack(&dsa_packet_type);
-#endif
+       dev_remove_pack(&dsa_pack_type);
        platform_driver_unregister(&dsa_driver);
 }
 module_exit(dsa_cleanup_module);
index d4cf5cc747e3569f4d41855d9894b7a4fa98253d..98afed4d92baa4859615d91805a524b1bc68f25e 100644 (file)
@@ -33,6 +33,10 @@ struct dsa_slave_priv {
         * to this port.
         */
        struct phy_device       *phy;
+       phy_interface_t         phy_interface;
+       int                     old_link;
+       int                     old_pause;
+       int                     old_duplex;
 };
 
 /* dsa.c */
@@ -45,16 +49,16 @@ struct net_device *dsa_slave_create(struct dsa_switch *ds,
                                    int port, char *name);
 
 /* tag_dsa.c */
-netdev_tx_t dsa_xmit(struct sk_buff *skb, struct net_device *dev);
-extern struct packet_type dsa_packet_type;
+extern const struct dsa_device_ops dsa_netdev_ops;
 
 /* tag_edsa.c */
-netdev_tx_t edsa_xmit(struct sk_buff *skb, struct net_device *dev);
-extern struct packet_type edsa_packet_type;
+extern const struct dsa_device_ops edsa_netdev_ops;
 
 /* tag_trailer.c */
-netdev_tx_t trailer_xmit(struct sk_buff *skb, struct net_device *dev);
-extern struct packet_type trailer_packet_type;
+extern const struct dsa_device_ops trailer_netdev_ops;
+
+/* tag_brcm.c */
+extern const struct dsa_device_ops brcm_netdev_ops;
 
 
 #endif
index 45a1e34c89e0d975dd9f361a73a5617d69a10301..7333a4aebb7de2bdecf7496c681f7d4b6392f5d3 100644 (file)
@@ -12,6 +12,8 @@
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
 #include <linux/phy.h>
+#include <linux/of_net.h>
+#include <linux/of_mdio.h>
 #include "dsa_priv.h"
 
 /* slave mii_bus handling ***************************************************/
@@ -19,7 +21,7 @@ static int dsa_slave_phy_read(struct mii_bus *bus, int addr, int reg)
 {
        struct dsa_switch *ds = bus->priv;
 
-       if (ds->phys_port_mask & (1 << addr))
+       if (ds->phys_mii_mask & (1 << addr))
                return ds->drv->phy_read(ds, addr, reg);
 
        return 0xffff;
@@ -29,7 +31,7 @@ static int dsa_slave_phy_write(struct mii_bus *bus, int addr, int reg, u16 val)
 {
        struct dsa_switch *ds = bus->priv;
 
-       if (ds->phys_port_mask & (1 << addr))
+       if (ds->phys_mii_mask & (1 << addr))
                return ds->drv->phy_write(ds, addr, reg, val);
 
        return 0;
@@ -171,6 +173,25 @@ static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
        return -EOPNOTSUPP;
 }
 
+static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch_tree *dst = p->parent->dst;
+
+       return dst->ops->xmit(skb, dev);
+}
+
+static netdev_tx_t dsa_slave_notag_xmit(struct sk_buff *skb,
+                                       struct net_device *dev)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+
+       skb->dev = p->parent->dst->master_netdev;
+       dev_queue_xmit(skb);
+
+       return NETDEV_TX_OK;
+}
+
 
 /* ethtool operations *******************************************************/
 static int
@@ -293,44 +314,107 @@ static const struct ethtool_ops dsa_slave_ethtool_ops = {
        .get_sset_count         = dsa_slave_get_sset_count,
 };
 
-#ifdef CONFIG_NET_DSA_TAG_DSA
-static const struct net_device_ops dsa_netdev_ops = {
-       .ndo_init               = dsa_slave_init,
-       .ndo_open               = dsa_slave_open,
-       .ndo_stop               = dsa_slave_close,
-       .ndo_start_xmit         = dsa_xmit,
-       .ndo_change_rx_flags    = dsa_slave_change_rx_flags,
-       .ndo_set_rx_mode        = dsa_slave_set_rx_mode,
-       .ndo_set_mac_address    = dsa_slave_set_mac_address,
-       .ndo_do_ioctl           = dsa_slave_ioctl,
-};
-#endif
-#ifdef CONFIG_NET_DSA_TAG_EDSA
-static const struct net_device_ops edsa_netdev_ops = {
+static const struct net_device_ops dsa_slave_netdev_ops = {
        .ndo_init               = dsa_slave_init,
        .ndo_open               = dsa_slave_open,
        .ndo_stop               = dsa_slave_close,
-       .ndo_start_xmit         = edsa_xmit,
+       .ndo_start_xmit         = dsa_slave_xmit,
        .ndo_change_rx_flags    = dsa_slave_change_rx_flags,
        .ndo_set_rx_mode        = dsa_slave_set_rx_mode,
        .ndo_set_mac_address    = dsa_slave_set_mac_address,
        .ndo_do_ioctl           = dsa_slave_ioctl,
 };
-#endif
-#ifdef CONFIG_NET_DSA_TAG_TRAILER
-static const struct net_device_ops trailer_netdev_ops = {
-       .ndo_init               = dsa_slave_init,
-       .ndo_open               = dsa_slave_open,
-       .ndo_stop               = dsa_slave_close,
-       .ndo_start_xmit         = trailer_xmit,
-       .ndo_change_rx_flags    = dsa_slave_change_rx_flags,
-       .ndo_set_rx_mode        = dsa_slave_set_rx_mode,
-       .ndo_set_mac_address    = dsa_slave_set_mac_address,
-       .ndo_do_ioctl           = dsa_slave_ioctl,
+
+static const struct dsa_device_ops notag_netdev_ops = {
+       .xmit   = dsa_slave_notag_xmit,
+       .rcv    = NULL,
 };
-#endif
+
+static void dsa_slave_adjust_link(struct net_device *dev)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+       unsigned int status_changed = 0;
+
+       if (p->old_link != p->phy->link) {
+               status_changed = 1;
+               p->old_link = p->phy->link;
+       }
+
+       if (p->old_duplex != p->phy->duplex) {
+               status_changed = 1;
+               p->old_duplex = p->phy->duplex;
+       }
+
+       if (p->old_pause != p->phy->pause) {
+               status_changed = 1;
+               p->old_pause = p->phy->pause;
+       }
+
+       if (ds->drv->adjust_link && status_changed)
+               ds->drv->adjust_link(ds, p->port, p->phy);
+
+       if (status_changed)
+               phy_print_status(p->phy);
+}
+
+static int dsa_slave_fixed_link_update(struct net_device *dev,
+                                      struct fixed_phy_status *status)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       struct dsa_switch *ds = p->parent;
+
+       if (ds->drv->fixed_link_update)
+               ds->drv->fixed_link_update(ds, p->port, status);
+
+       return 0;
+}
 
 /* slave device setup *******************************************************/
+static void dsa_slave_phy_setup(struct dsa_slave_priv *p,
+                               struct net_device *slave_dev)
+{
+       struct dsa_switch *ds = p->parent;
+       struct dsa_chip_data *cd = ds->pd;
+       struct device_node *phy_dn, *port_dn;
+       bool phy_is_fixed = false;
+       int ret;
+
+       port_dn = cd->port_dn[p->port];
+       p->phy_interface = of_get_phy_mode(port_dn);
+
+       phy_dn = of_parse_phandle(port_dn, "phy-handle", 0);
+       if (of_phy_is_fixed_link(port_dn)) {
+               /* In the case of a fixed PHY, the DT node associated
+                * to the fixed PHY is the Port DT node
+                */
+               ret = of_phy_register_fixed_link(port_dn);
+               if (ret) {
+                       pr_err("failed to register fixed PHY\n");
+                       return;
+               }
+               phy_is_fixed = true;
+               phy_dn = port_dn;
+       }
+
+       if (phy_dn)
+               p->phy = of_phy_connect(slave_dev, phy_dn,
+                                       dsa_slave_adjust_link, 0,
+                                       p->phy_interface);
+
+       if (p->phy && phy_is_fixed)
+               fixed_phy_set_link_update(p->phy, dsa_slave_fixed_link_update);
+
+       /* We could not connect to a designated PHY, so use the switch internal
+        * MDIO bus instead
+        */
+       if (!p->phy)
+               p->phy = ds->slave_mii_bus->phy_map[p->port];
+       else
+               pr_info("attached PHY at address %d [%s]\n",
+                       p->phy->addr, p->phy->drv->name);
+}
+
 struct net_device *
 dsa_slave_create(struct dsa_switch *ds, struct device *parent,
                 int port, char *name)
@@ -349,35 +433,48 @@ dsa_slave_create(struct dsa_switch *ds, struct device *parent,
        slave_dev->ethtool_ops = &dsa_slave_ethtool_ops;
        eth_hw_addr_inherit(slave_dev, master);
        slave_dev->tx_queue_len = 0;
+       slave_dev->netdev_ops = &dsa_slave_netdev_ops;
 
        switch (ds->dst->tag_protocol) {
 #ifdef CONFIG_NET_DSA_TAG_DSA
        case htons(ETH_P_DSA):
-               slave_dev->netdev_ops = &dsa_netdev_ops;
+               ds->dst->ops = &dsa_netdev_ops;
                break;
 #endif
 #ifdef CONFIG_NET_DSA_TAG_EDSA
        case htons(ETH_P_EDSA):
-               slave_dev->netdev_ops = &edsa_netdev_ops;
+               ds->dst->ops = &edsa_netdev_ops;
                break;
 #endif
 #ifdef CONFIG_NET_DSA_TAG_TRAILER
        case htons(ETH_P_TRAILER):
-               slave_dev->netdev_ops = &trailer_netdev_ops;
+               ds->dst->ops = &trailer_netdev_ops;
+               break;
+#endif
+#ifdef CONFIG_NET_DSA_TAG_BRCM
+       case htons(ETH_P_BRCMTAG):
+               ds->dst->ops = &brcm_netdev_ops;
                break;
 #endif
        default:
-               BUG();
+               ds->dst->ops = &notag_netdev_ops;
+               break;
        }
 
        SET_NETDEV_DEV(slave_dev, parent);
+       slave_dev->dev.of_node = ds->pd->port_dn[port];
        slave_dev->vlan_features = master->vlan_features;
 
        p = netdev_priv(slave_dev);
        p->dev = slave_dev;
        p->parent = ds;
        p->port = port;
-       p->phy = ds->slave_mii_bus->phy_map[port];
+
+       p->old_pause = -1;
+       p->old_link = -1;
+       p->old_duplex = -1;
+
+       dsa_slave_phy_setup(p, slave_dev);
 
        ret = register_netdev(slave_dev);
        if (ret) {
diff --git a/net/dsa/tag_brcm.c b/net/dsa/tag_brcm.c
new file mode 100644 (file)
index 0000000..e0b759e
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Broadcom tag support
+ *
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+#include "dsa_priv.h"
+
+/* This tag length is 4 bytes, older ones were 6 bytes, we do not
+ * handle them
+ */
+#define BRCM_TAG_LEN   4
+
+/* Tag is constructed and desconstructed using byte by byte access
+ * because the tag is placed after the MAC Source Address, which does
+ * not make it 4-bytes aligned, so this might cause unaligned accesses
+ * on most systems where this is used.
+ */
+
+/* Ingress and egress opcodes */
+#define BRCM_OPCODE_SHIFT      5
+#define BRCM_OPCODE_MASK       0x7
+
+/* Ingress fields */
+/* 1st byte in the tag */
+#define BRCM_IG_TC_SHIFT       2
+#define BRCM_IG_TC_MASK                0x7
+/* 2nd byte in the tag */
+#define BRCM_IG_TE_MASK                0x3
+#define BRCM_IG_TS_SHIFT       7
+/* 3rd byte in the tag */
+#define BRCM_IG_DSTMAP2_MASK   1
+#define BRCM_IG_DSTMAP1_MASK   0xff
+
+/* Egress fields */
+
+/* 2nd byte in the tag */
+#define BRCM_EG_CID_MASK       0xff
+
+/* 3rd byte in the tag */
+#define BRCM_EG_RC_MASK                0xff
+#define  BRCM_EG_RC_RSVD       (3 << 6)
+#define  BRCM_EG_RC_EXCEPTION  (1 << 5)
+#define  BRCM_EG_RC_PROT_SNOOP (1 << 4)
+#define  BRCM_EG_RC_PROT_TERM  (1 << 3)
+#define  BRCM_EG_RC_SWITCH     (1 << 2)
+#define  BRCM_EG_RC_MAC_LEARN  (1 << 1)
+#define  BRCM_EG_RC_MIRROR     (1 << 0)
+#define BRCM_EG_TC_SHIFT       5
+#define BRCM_EG_TC_MASK                0x7
+#define BRCM_EG_PID_MASK       0x1f
+
+static netdev_tx_t brcm_tag_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+       struct dsa_slave_priv *p = netdev_priv(dev);
+       u8 *brcm_tag;
+
+       dev->stats.tx_packets++;
+       dev->stats.tx_bytes += skb->len;
+
+       if (skb_cow_head(skb, BRCM_TAG_LEN) < 0)
+               goto out_free;
+
+       skb_push(skb, BRCM_TAG_LEN);
+
+       memmove(skb->data, skb->data + BRCM_TAG_LEN, 2 * ETH_ALEN);
+
+       /* Build the tag after the MAC Source Address */
+       brcm_tag = skb->data + 2 * ETH_ALEN;
+
+       /* Set the ingress opcode, traffic class, tag enforcment is
+        * deprecated
+        */
+       brcm_tag[0] = (1 << BRCM_OPCODE_SHIFT) |
+                       ((skb->priority << BRCM_IG_TC_SHIFT) & BRCM_IG_TC_MASK);
+       brcm_tag[1] = 0;
+       brcm_tag[2] = 0;
+       if (p->port == 8)
+               brcm_tag[2] = BRCM_IG_DSTMAP2_MASK;
+       brcm_tag[3] = (1 << p->port) & BRCM_IG_DSTMAP1_MASK;
+
+       /* Queue the SKB for transmission on the parent interface, but
+        * do not modify its EtherType
+        */
+       skb->protocol = htons(ETH_P_BRCMTAG);
+       skb->dev = p->parent->dst->master_netdev;
+       dev_queue_xmit(skb);
+
+       return NETDEV_TX_OK;
+
+out_free:
+       kfree_skb(skb);
+       return NETDEV_TX_OK;
+}
+
+static int brcm_tag_rcv(struct sk_buff *skb, struct net_device *dev,
+                       struct packet_type *pt, struct net_device *orig_dev)
+{
+       struct dsa_switch_tree *dst = dev->dsa_ptr;
+       struct dsa_switch *ds;
+       int source_port;
+       u8 *brcm_tag;
+
+       if (unlikely(dst == NULL))
+               goto out_drop;
+
+       ds = dst->ds[0];
+
+       skb = skb_unshare(skb, GFP_ATOMIC);
+       if (skb == NULL)
+               goto out;
+
+       if (unlikely(!pskb_may_pull(skb, BRCM_TAG_LEN)))
+               goto out_drop;
+
+       /* skb->data points to the EtherType, the tag is right before it */
+       brcm_tag = skb->data - 2;
+
+       /* The opcode should never be different than 0b000 */
+       if (unlikely((brcm_tag[0] >> BRCM_OPCODE_SHIFT) & BRCM_OPCODE_MASK))
+               goto out_drop;
+
+       /* We should never see a reserved reason code without knowing how to
+        * handle it
+        */
+       WARN_ON(brcm_tag[2] & BRCM_EG_RC_RSVD);
+
+       /* Locate which port this is coming from */
+       source_port = brcm_tag[3] & BRCM_EG_PID_MASK;
+
+       /* Validate port against switch setup, either the port is totally */
+       if (source_port >= DSA_MAX_PORTS || ds->ports[source_port] == NULL)
+               goto out_drop;
+
+       /* Remove Broadcom tag and update checksum */
+       skb_pull_rcsum(skb, BRCM_TAG_LEN);
+
+       /* Move the Ethernet DA and SA */
+       memmove(skb->data - ETH_HLEN,
+               skb->data - ETH_HLEN - BRCM_TAG_LEN,
+               2 * ETH_ALEN);
+
+       skb_push(skb, ETH_HLEN);
+       skb->pkt_type = PACKET_HOST;
+       skb->dev = ds->ports[source_port];
+       skb->protocol = eth_type_trans(skb, skb->dev);
+
+       skb->dev->stats.rx_packets++;
+       skb->dev->stats.rx_bytes += skb->len;
+
+       netif_receive_skb(skb);
+
+       return 0;
+
+out_drop:
+       kfree_skb(skb);
+out:
+       return 0;
+}
+
+const struct dsa_device_ops brcm_netdev_ops = {
+       .xmit   = brcm_tag_xmit,
+       .rcv    = brcm_tag_rcv,
+};
index cacce1e22f9caa029c2374cd93c2e071932f365c..d7dbc5bda5c0f587571c8d15d37c8705c493bfb4 100644 (file)
@@ -16,7 +16,7 @@
 
 #define DSA_HLEN       4
 
-netdev_tx_t dsa_xmit(struct sk_buff *skb, struct net_device *dev)
+static netdev_tx_t dsa_xmit(struct sk_buff *skb, struct net_device *dev)
 {
        struct dsa_slave_priv *p = netdev_priv(dev);
        u8 *dsa_header;
@@ -186,7 +186,7 @@ out:
        return 0;
 }
 
-struct packet_type dsa_packet_type __read_mostly = {
-       .type   = cpu_to_be16(ETH_P_DSA),
-       .func   = dsa_rcv,
+const struct dsa_device_ops dsa_netdev_ops = {
+       .xmit   = dsa_xmit,
+       .rcv    = dsa_rcv,
 };
index e70c43c25e64c9310d3d5a61b407d8eeb76402d2..6b30abe89183f4de5e8ff7f105be1b593e749d79 100644 (file)
@@ -17,7 +17,7 @@
 #define DSA_HLEN       4
 #define EDSA_HLEN      8
 
-netdev_tx_t edsa_xmit(struct sk_buff *skb, struct net_device *dev)
+static netdev_tx_t edsa_xmit(struct sk_buff *skb, struct net_device *dev)
 {
        struct dsa_slave_priv *p = netdev_priv(dev);
        u8 *edsa_header;
@@ -205,7 +205,7 @@ out:
        return 0;
 }
 
-struct packet_type edsa_packet_type __read_mostly = {
-       .type   = cpu_to_be16(ETH_P_EDSA),
-       .func   = edsa_rcv,
+const struct dsa_device_ops edsa_netdev_ops = {
+       .xmit   = edsa_xmit,
+       .rcv    = edsa_rcv,
 };
index 94bc260d015d11f1a321ca3f3876c7b901716302..5fe9444842c573b7b251266a166719887068c739 100644 (file)
@@ -14,7 +14,7 @@
 #include <linux/slab.h>
 #include "dsa_priv.h"
 
-netdev_tx_t trailer_xmit(struct sk_buff *skb, struct net_device *dev)
+static netdev_tx_t trailer_xmit(struct sk_buff *skb, struct net_device *dev)
 {
        struct dsa_slave_priv *p = netdev_priv(dev);
        struct sk_buff *nskb;
@@ -114,7 +114,7 @@ out:
        return 0;
 }
 
-struct packet_type trailer_packet_type __read_mostly = {
-       .type   = cpu_to_be16(ETH_P_TRAILER),
-       .func   = trailer_rcv,
+const struct dsa_device_ops trailer_netdev_ops = {
+       .xmit   = trailer_xmit,
+       .rcv    = trailer_rcv,
 };
index f405e05924078b2d30f45139cd698843a16256da..5cebca134585862303114735fe64ceaf5e44c81f 100644 (file)
@@ -181,11 +181,8 @@ __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)
         * variants has been configured on the receiving interface,
         * and if so, set skb->protocol without looking at the packet.
         */
-       if (unlikely(netdev_uses_dsa_tags(dev)))
-               return htons(ETH_P_DSA);
-
-       if (unlikely(netdev_uses_trailer_tags(dev)))
-               return htons(ETH_P_TRAILER);
+       if (unlikely(netdev_uses_dsa(dev)))
+               return htons(ETH_P_XDSA);
 
        if (likely(ntohs(eth->h_proto) >= ETH_P_802_3_MIN))
                return eth->h_proto;