Commit 0ce4866f authored by David S. Miller's avatar David S. Miller

Merge branch 'sf2'

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: default avatarDavid S. Miller <davem@davemloft.net>
parents abc4da45 879bd83d
* 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>;
};
};
* 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>;
};
...
};
};
};
...@@ -39,6 +39,22 @@ Optionnal property: ...@@ -39,6 +39,22 @@ Optionnal property:
This property is only used when switches are being This property is only used when switches are being
chained/cascaded together. 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: Example:
dsa@0 { dsa@0 {
...@@ -58,6 +74,7 @@ Example: ...@@ -58,6 +74,7 @@ Example:
port@0 { port@0 {
reg = <0>; reg = <0>;
label = "lan1"; label = "lan1";
phy-handle = <&phy0>;
}; };
port@1 { port@1 {
......
...@@ -36,4 +36,15 @@ config NET_DSA_MV88E6123_61_65 ...@@ -36,4 +36,15 @@ config NET_DSA_MV88E6123_61_65
This enables support for the Marvell 88E6123/6161/6165 This enables support for the Marvell 88E6123/6161/6165
ethernet switch chips. 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 endmenu
...@@ -7,3 +7,4 @@ endif ...@@ -7,3 +7,4 @@ endif
ifdef CONFIG_NET_DSA_MV88E6131 ifdef CONFIG_NET_DSA_MV88E6131
mv88e6xxx_drv-y += mv88e6131.o mv88e6xxx_drv-y += mv88e6131.o
endif endif
obj-$(CONFIG_NET_DSA_BCM_SF2) += bcm_sf2.o
/*
* 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");
/*
* 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 */
/*
* 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 */
...@@ -205,6 +205,14 @@ config MDIO_BUS_MUX_MMIOREG ...@@ -205,6 +205,14 @@ config MDIO_BUS_MUX_MMIOREG
Currently, only 8-bit registers are supported. 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 endif # PHYLIB
config MICREL_KS8995MA config MICREL_KS8995MA
......
...@@ -34,3 +34,4 @@ obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o ...@@ -34,3 +34,4 @@ obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o
obj-$(CONFIG_AMD_XGBE_PHY) += amd-xgbe-phy.o obj-$(CONFIG_AMD_XGBE_PHY) += amd-xgbe-phy.o
obj-$(CONFIG_MDIO_BCM_UNIMAC) += mdio-bcm-unimac.o
/*
* 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");
...@@ -1781,24 +1781,13 @@ void dev_net_set(struct net_device *dev, struct net *net) ...@@ -1781,24 +1781,13 @@ void dev_net_set(struct net_device *dev, struct net *net)
#endif #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) if (dev->dsa_ptr != NULL)
return dsa_uses_dsa_tags(dev->dsa_ptr); return dsa_uses_tagged_protocol(dev->dsa_ptr);
#endif #endif
return false;
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;
} }
/** /**
...@@ -1933,6 +1922,13 @@ struct udp_offload { ...@@ -1933,6 +1922,13 @@ struct udp_offload {
struct offload_callbacks callbacks; 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) */ /* often modified stats are per cpu, other are shared (netdev->stats) */
struct pcpu_sw_netstats { struct pcpu_sw_netstats {
u64 rx_packets; u64 rx_packets;
......
...@@ -18,6 +18,9 @@ extern int fixed_phy_register(unsigned int irq, ...@@ -18,6 +18,9 @@ extern int fixed_phy_register(unsigned int irq,
struct fixed_phy_status *status, struct fixed_phy_status *status,
struct device_node *np); struct device_node *np);
extern void fixed_phy_del(int phy_addr); 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 #else
static inline int fixed_phy_add(unsigned int irq, int phy_id, static inline int fixed_phy_add(unsigned int irq, int phy_id,
struct fixed_phy_status *status) struct fixed_phy_status *status)
...@@ -34,14 +37,12 @@ static inline int fixed_phy_del(int phy_addr) ...@@ -34,14 +37,12 @@ static inline int fixed_phy_del(int phy_addr)
{ {
return -ENODEV; return -ENODEV;
} }
#endif /* CONFIG_FIXED_PHY */ static inline int fixed_phy_set_link_update(struct phy_device *phydev,
/*
* 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,
int (*link_update)(struct net_device *, int (*link_update)(struct net_device *,
struct fixed_phy_status *)); struct fixed_phy_status *))
{
return -ENODEV;
}
#endif /* CONFIG_FIXED_PHY */
#endif /* __PHY_FIXED_H */ #endif /* __PHY_FIXED_H */
...@@ -15,6 +15,14 @@ ...@@ -15,6 +15,14 @@
#include <linux/list.h> #include <linux/list.h>
#include <linux/timer.h> #include <linux/timer.h>
#include <linux/workqueue.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_SWITCHES 4
#define DSA_MAX_PORTS 12 #define DSA_MAX_PORTS 12
...@@ -26,6 +34,12 @@ struct dsa_chip_data { ...@@ -26,6 +34,12 @@ struct dsa_chip_data {
struct device *mii_bus; struct device *mii_bus;
int sw_addr; 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 * The names of the switch's ports. Use "cpu" to
* designate the switch port that the cpu is connected to, * designate the switch port that the cpu is connected to,
...@@ -34,6 +48,7 @@ struct dsa_chip_data { ...@@ -34,6 +48,7 @@ struct dsa_chip_data {
* or any other string to indicate this is a physical port. * or any other string to indicate this is a physical port.
*/ */
char *port_names[DSA_MAX_PORTS]; char *port_names[DSA_MAX_PORTS];
struct device_node *port_dn[DSA_MAX_PORTS];
/* /*
* An array (with nr_chips elements) of which element [a] * An array (with nr_chips elements) of which element [a]
...@@ -59,6 +74,8 @@ struct dsa_platform_data { ...@@ -59,6 +74,8 @@ struct dsa_platform_data {
struct dsa_chip_data *chip; struct dsa_chip_data *chip;
}; };
struct dsa_device_ops;
struct dsa_switch_tree { struct dsa_switch_tree {
/* /*
* Configuration data for the platform device that owns * Configuration data for the platform device that owns
...@@ -71,6 +88,7 @@ struct dsa_switch_tree { ...@@ -71,6 +88,7 @@ struct dsa_switch_tree {
* protocol to use. * protocol to use.
*/ */
struct net_device *master_netdev; struct net_device *master_netdev;
const struct dsa_device_ops *ops;
__be16 tag_protocol; __be16 tag_protocol;
/* /*
...@@ -119,6 +137,7 @@ struct dsa_switch { ...@@ -119,6 +137,7 @@ struct dsa_switch {
*/ */
u32 dsa_port_mask; u32 dsa_port_mask;
u32 phys_port_mask; u32 phys_port_mask;
u32 phys_mii_mask;
struct mii_bus *slave_mii_bus; struct mii_bus *slave_mii_bus;
struct net_device *ports[DSA_MAX_PORTS]; struct net_device *ports[DSA_MAX_PORTS];
}; };
...@@ -169,6 +188,14 @@ struct dsa_switch_driver { ...@@ -169,6 +188,14 @@ struct dsa_switch_driver {
*/ */
void (*poll_link)(struct dsa_switch *ds); 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. * ethtool hardware statistics.
*/ */
...@@ -186,21 +213,9 @@ static inline void *ds_to_priv(struct dsa_switch *ds) ...@@ -186,21 +213,9 @@ static inline void *ds_to_priv(struct dsa_switch *ds)
return (void *)(ds + 1); return (void *)(ds + 1);
} }
/* static inline bool dsa_uses_tagged_protocol(struct dsa_switch_tree *dst)
* 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)
{ {
return !!(dst->tag_protocol == htons(ETH_P_TRAILER)); return dst->tag_protocol != 0;
} }
#endif #endif
...@@ -128,6 +128,7 @@ ...@@ -128,6 +128,7 @@
#define ETH_P_PHONET 0x00F5 /* Nokia Phonet frames */ #define ETH_P_PHONET 0x00F5 /* Nokia Phonet frames */
#define ETH_P_IEEE802154 0x00F6 /* IEEE802.15.4 frame */ #define ETH_P_IEEE802154 0x00F6 /* IEEE802.15.4 frame */
#define ETH_P_CAIF 0x00F7 /* ST-Ericsson CAIF protocol */ #define ETH_P_CAIF 0x00F7 /* ST-Ericsson CAIF protocol */
#define ETH_P_XDSA 0x00F8 /* Multiplexed DSA protocol */
/* /*
* This is an Ethernet frame header. * This is an Ethernet frame header.
......
...@@ -12,6 +12,9 @@ config NET_DSA ...@@ -12,6 +12,9 @@ config NET_DSA
if NET_DSA if NET_DSA
# tagging formats # tagging formats
config NET_DSA_TAG_BRCM
bool
config NET_DSA_TAG_DSA config NET_DSA_TAG_DSA
bool bool
......
...@@ -3,6 +3,7 @@ obj-$(CONFIG_NET_DSA) += dsa_core.o ...@@ -3,6 +3,7 @@ obj-$(CONFIG_NET_DSA) += dsa_core.o
dsa_core-y += dsa.o slave.o dsa_core-y += dsa.o slave.o
# tagging formats # 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_DSA) += tag_dsa.o
dsa_core-$(CONFIG_NET_DSA_TAG_EDSA) += tag_edsa.o dsa_core-$(CONFIG_NET_DSA_TAG_EDSA) += tag_edsa.o
dsa_core-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o dsa_core-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o
...@@ -144,6 +144,11 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index, ...@@ -144,6 +144,11 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
goto out; 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 * If the CPU connects to this switch, set the switch tree
* tagging protocol to the preferred tagging format of this * tagging protocol to the preferred tagging format of this
...@@ -410,6 +415,7 @@ static int dsa_of_probe(struct platform_device *pdev) ...@@ -410,6 +415,7 @@ static int dsa_of_probe(struct platform_device *pdev)
chip_index++; chip_index++;
cd = &pd->chip[chip_index]; cd = &pd->chip[chip_index];
cd->of_node = child;
cd->mii_bus = &mdio_bus->dev; cd->mii_bus = &mdio_bus->dev;
sw_addr = of_get_property(child, "reg", NULL); sw_addr = of_get_property(child, "reg", NULL);
...@@ -431,6 +437,8 @@ static int dsa_of_probe(struct platform_device *pdev) ...@@ -431,6 +437,8 @@ static int dsa_of_probe(struct platform_device *pdev)
if (!port_name) if (!port_name)
continue; continue;
cd->port_dn[port_index] = port;
cd->port_names[port_index] = kstrdup(port_name, cd->port_names[port_index] = kstrdup(port_name,
GFP_KERNEL); GFP_KERNEL);
if (!cd->port_names[port_index]) { if (!cd->port_names[port_index]) {
...@@ -608,7 +616,26 @@ static void dsa_shutdown(struct platform_device *pdev) ...@@ -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[] = { static const struct of_device_id dsa_of_match_table[] = {
{ .compatible = "brcm,bcm7445-switch-v4.0" },
{ .compatible = "marvell,dsa", }, { .compatible = "marvell,dsa", },
{} {}
}; };
...@@ -633,30 +660,15 @@ static int __init dsa_init_module(void) ...@@ -633,30 +660,15 @@ static int __init dsa_init_module(void)
if (rc) if (rc)
return rc; return rc;
#ifdef CONFIG_NET_DSA_TAG_DSA dev_add_pack(&dsa_pack_type);
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
return 0; return 0;
} }
module_init(dsa_init_module); module_init(dsa_init_module);
static void __exit dsa_cleanup_module(void) static void __exit dsa_cleanup_module(void)
{ {
#ifdef CONFIG_NET_DSA_TAG_TRAILER dev_remove_pack(&dsa_pack_type);
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
platform_driver_unregister(&dsa_driver); platform_driver_unregister(&dsa_driver);
} }
module_exit(dsa_cleanup_module); module_exit(dsa_cleanup_module);
......
...@@ -33,6 +33,10 @@ struct dsa_slave_priv { ...@@ -33,6 +33,10 @@ struct dsa_slave_priv {
* to this port. * to this port.
*/ */
struct phy_device *phy; struct phy_device *phy;
phy_interface_t phy_interface;
int old_link;
int old_pause;
int old_duplex;
}; };
/* dsa.c */ /* dsa.c */
...@@ -45,16 +49,16 @@ struct net_device *dsa_slave_create(struct dsa_switch *ds, ...@@ -45,16 +49,16 @@ struct net_device *dsa_slave_create(struct dsa_switch *ds,
int port, char *name); int port, char *name);
/* tag_dsa.c */ /* tag_dsa.c */
netdev_tx_t dsa_xmit(struct sk_buff *skb, struct net_device *dev); extern const struct dsa_device_ops dsa_netdev_ops;
extern struct packet_type dsa_packet_type;
/* tag_edsa.c */ /* tag_edsa.c */
netdev_tx_t edsa_xmit(struct sk_buff *skb, struct net_device *dev); extern const struct dsa_device_ops edsa_netdev_ops;
extern struct packet_type edsa_packet_type;
/* tag_trailer.c */ /* tag_trailer.c */
netdev_tx_t trailer_xmit(struct sk_buff *skb, struct net_device *dev); extern const struct dsa_device_ops trailer_netdev_ops;
extern struct packet_type trailer_packet_type;
/* tag_brcm.c */
extern const struct dsa_device_ops brcm_netdev_ops;
#endif #endif
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/etherdevice.h> #include <linux/etherdevice.h>
#include <linux/phy.h> #include <linux/phy.h>
#include <linux/of_net.h>
#include <linux/of_mdio.h>
#include "dsa_priv.h" #include "dsa_priv.h"
/* slave mii_bus handling ***************************************************/ /* slave mii_bus handling ***************************************************/
...@@ -19,7 +21,7 @@ static int dsa_slave_phy_read(struct mii_bus *bus, int addr, int reg) ...@@ -19,7 +21,7 @@ static int dsa_slave_phy_read(struct mii_bus *bus, int addr, int reg)
{ {
struct dsa_switch *ds = bus->priv; 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 ds->drv->phy_read(ds, addr, reg);
return 0xffff; return 0xffff;
...@@ -29,7 +31,7 @@ static int dsa_slave_phy_write(struct mii_bus *bus, int addr, int reg, u16 val) ...@@ -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; 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 ds->drv->phy_write(ds, addr, reg, val);
return 0; return 0;
...@@ -171,6 +173,25 @@ static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) ...@@ -171,6 +173,25 @@ static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
return -EOPNOTSUPP; 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 *******************************************************/ /* ethtool operations *******************************************************/
static int static int
...@@ -293,44 +314,107 @@ static const struct ethtool_ops dsa_slave_ethtool_ops = { ...@@ -293,44 +314,107 @@ static const struct ethtool_ops dsa_slave_ethtool_ops = {
.get_sset_count = dsa_slave_get_sset_count, .get_sset_count = dsa_slave_get_sset_count,
}; };
#ifdef CONFIG_NET_DSA_TAG_DSA static const struct net_device_ops dsa_slave_netdev_ops = {
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 = {
.ndo_init = dsa_slave_init, .ndo_init = dsa_slave_init,
.ndo_open = dsa_slave_open, .ndo_open = dsa_slave_open,
.ndo_stop = dsa_slave_close, .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_change_rx_flags = dsa_slave_change_rx_flags,
.ndo_set_rx_mode = dsa_slave_set_rx_mode, .ndo_set_rx_mode = dsa_slave_set_rx_mode,
.ndo_set_mac_address = dsa_slave_set_mac_address, .ndo_set_mac_address = dsa_slave_set_mac_address,
.ndo_do_ioctl = dsa_slave_ioctl, .ndo_do_ioctl = dsa_slave_ioctl,
}; };
#endif
#ifdef CONFIG_NET_DSA_TAG_TRAILER static const struct dsa_device_ops notag_netdev_ops = {
static const struct net_device_ops trailer_netdev_ops = { .xmit = dsa_slave_notag_xmit,
.ndo_init = dsa_slave_init, .rcv = NULL,
.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,
}; };
#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 *******************************************************/ /* 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 * struct net_device *
dsa_slave_create(struct dsa_switch *ds, struct device *parent, dsa_slave_create(struct dsa_switch *ds, struct device *parent,
int port, char *name) int port, char *name)
...@@ -349,35 +433,48 @@ dsa_slave_create(struct dsa_switch *ds, struct device *parent, ...@@ -349,35 +433,48 @@ dsa_slave_create(struct dsa_switch *ds, struct device *parent,
slave_dev->ethtool_ops = &dsa_slave_ethtool_ops; slave_dev->ethtool_ops = &dsa_slave_ethtool_ops;
eth_hw_addr_inherit(slave_dev, master); eth_hw_addr_inherit(slave_dev, master);
slave_dev->tx_queue_len = 0; slave_dev->tx_queue_len = 0;
slave_dev->netdev_ops = &dsa_slave_netdev_ops;
switch (ds->dst->tag_protocol) { switch (ds->dst->tag_protocol) {
#ifdef CONFIG_NET_DSA_TAG_DSA #ifdef CONFIG_NET_DSA_TAG_DSA
case htons(ETH_P_DSA): case htons(ETH_P_DSA):
slave_dev->netdev_ops = &dsa_netdev_ops; ds->dst->ops = &dsa_netdev_ops;
break; break;
#endif #endif
#ifdef CONFIG_NET_DSA_TAG_EDSA #ifdef CONFIG_NET_DSA_TAG_EDSA
case htons(ETH_P_EDSA): case htons(ETH_P_EDSA):
slave_dev->netdev_ops = &edsa_netdev_ops; ds->dst->ops = &edsa_netdev_ops;
break; break;
#endif #endif
#ifdef CONFIG_NET_DSA_TAG_TRAILER #ifdef CONFIG_NET_DSA_TAG_TRAILER
case htons(ETH_P_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; break;
#endif #endif
default: default:
BUG(); ds->dst->ops = &notag_netdev_ops;
break;
} }
SET_NETDEV_DEV(slave_dev, parent); SET_NETDEV_DEV(slave_dev, parent);
slave_dev->dev.of_node = ds->pd->port_dn[port];
slave_dev->vlan_features = master->vlan_features; slave_dev->vlan_features = master->vlan_features;
p = netdev_priv(slave_dev); p = netdev_priv(slave_dev);
p->dev = slave_dev; p->dev = slave_dev;
p->parent = ds; p->parent = ds;
p->port = port; 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); ret = register_netdev(slave_dev);
if (ret) { if (ret) {
......
/*
* 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,
};
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
#define DSA_HLEN 4 #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); struct dsa_slave_priv *p = netdev_priv(dev);
u8 *dsa_header; u8 *dsa_header;
...@@ -186,7 +186,7 @@ static int dsa_rcv(struct sk_buff *skb, struct net_device *dev, ...@@ -186,7 +186,7 @@ static int dsa_rcv(struct sk_buff *skb, struct net_device *dev,
return 0; return 0;
} }
struct packet_type dsa_packet_type __read_mostly = { const struct dsa_device_ops dsa_netdev_ops = {
.type = cpu_to_be16(ETH_P_DSA), .xmit = dsa_xmit,
.func = dsa_rcv, .rcv = dsa_rcv,
}; };
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
#define DSA_HLEN 4 #define DSA_HLEN 4
#define EDSA_HLEN 8 #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); struct dsa_slave_priv *p = netdev_priv(dev);
u8 *edsa_header; u8 *edsa_header;
...@@ -205,7 +205,7 @@ static int edsa_rcv(struct sk_buff *skb, struct net_device *dev, ...@@ -205,7 +205,7 @@ static int edsa_rcv(struct sk_buff *skb, struct net_device *dev,
return 0; return 0;
} }
struct packet_type edsa_packet_type __read_mostly = { const struct dsa_device_ops edsa_netdev_ops = {
.type = cpu_to_be16(ETH_P_EDSA), .xmit = edsa_xmit,
.func = edsa_rcv, .rcv = edsa_rcv,
}; };
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include "dsa_priv.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 dsa_slave_priv *p = netdev_priv(dev);
struct sk_buff *nskb; struct sk_buff *nskb;
...@@ -114,7 +114,7 @@ static int trailer_rcv(struct sk_buff *skb, struct net_device *dev, ...@@ -114,7 +114,7 @@ static int trailer_rcv(struct sk_buff *skb, struct net_device *dev,
return 0; return 0;
} }
struct packet_type trailer_packet_type __read_mostly = { const struct dsa_device_ops trailer_netdev_ops = {
.type = cpu_to_be16(ETH_P_TRAILER), .xmit = trailer_xmit,
.func = trailer_rcv, .rcv = trailer_rcv,
}; };
...@@ -181,11 +181,8 @@ __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev) ...@@ -181,11 +181,8 @@ __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)
* variants has been configured on the receiving interface, * variants has been configured on the receiving interface,
* and if so, set skb->protocol without looking at the packet. * and if so, set skb->protocol without looking at the packet.
*/ */
if (unlikely(netdev_uses_dsa_tags(dev))) if (unlikely(netdev_uses_dsa(dev)))
return htons(ETH_P_DSA); return htons(ETH_P_XDSA);
if (unlikely(netdev_uses_trailer_tags(dev)))
return htons(ETH_P_TRAILER);
if (likely(ntohs(eth->h_proto) >= ETH_P_802_3_MIN)) if (likely(ntohs(eth->h_proto) >= ETH_P_802_3_MIN))
return eth->h_proto; return eth->h_proto;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment