freebsd-dev/sys/mips/cavium/octe/mv88e61xxphy.c
2020-09-01 21:21:19 +00:00

632 lines
17 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2010 Juli Mallett <jmallett@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* Driver for the Marvell 88E61xx family of switch PHYs
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/sysctl.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_media.h>
#include "miibus_if.h"
#include "mv88e61xxphyreg.h"
struct mv88e61xxphy_softc;
struct mv88e61xxphy_port_softc {
struct mv88e61xxphy_softc *sc_switch;
unsigned sc_port;
unsigned sc_domain;
unsigned sc_vlan;
unsigned sc_priority;
unsigned sc_flags;
};
#define MV88E61XXPHY_PORT_FLAG_VTU_UPDATE (0x0001)
struct mv88e61xxphy_softc {
device_t sc_dev;
struct mv88e61xxphy_port_softc sc_ports[MV88E61XX_PORTS];
};
enum mv88e61xxphy_vtu_membership_type {
MV88E61XXPHY_VTU_UNMODIFIED,
MV88E61XXPHY_VTU_UNTAGGED,
MV88E61XXPHY_VTU_TAGGED,
MV88E61XXPHY_VTU_DISCARDED,
};
enum mv88e61xxphy_sysctl_link_type {
MV88E61XXPHY_LINK_SYSCTL_DUPLEX,
MV88E61XXPHY_LINK_SYSCTL_LINK,
MV88E61XXPHY_LINK_SYSCTL_MEDIA,
};
enum mv88e61xxphy_sysctl_port_type {
MV88E61XXPHY_PORT_SYSCTL_DOMAIN,
MV88E61XXPHY_PORT_SYSCTL_VLAN,
MV88E61XXPHY_PORT_SYSCTL_PRIORITY,
};
/*
* Register access macros.
*/
#define MV88E61XX_READ(sc, phy, reg) \
MIIBUS_READREG(device_get_parent((sc)->sc_dev), (phy), (reg))
#define MV88E61XX_WRITE(sc, phy, reg, val) \
MIIBUS_WRITEREG(device_get_parent((sc)->sc_dev), (phy), (reg), (val))
#define MV88E61XX_READ_PORT(psc, reg) \
MV88E61XX_READ((psc)->sc_switch, MV88E61XX_PORT((psc)->sc_port), (reg))
#define MV88E61XX_WRITE_PORT(psc, reg, val) \
MV88E61XX_WRITE((psc)->sc_switch, MV88E61XX_PORT((psc)->sc_port), (reg), (val))
static int mv88e61xxphy_probe(device_t);
static int mv88e61xxphy_attach(device_t);
static void mv88e61xxphy_init(struct mv88e61xxphy_softc *);
static void mv88e61xxphy_init_port(struct mv88e61xxphy_port_softc *);
static void mv88e61xxphy_init_vtu(struct mv88e61xxphy_softc *);
static int mv88e61xxphy_sysctl_link_proc(SYSCTL_HANDLER_ARGS);
static int mv88e61xxphy_sysctl_port_proc(SYSCTL_HANDLER_ARGS);
static void mv88e61xxphy_vtu_load(struct mv88e61xxphy_softc *, uint16_t);
static void mv88e61xxphy_vtu_set_membership(struct mv88e61xxphy_softc *, unsigned, enum mv88e61xxphy_vtu_membership_type);
static void mv88e61xxphy_vtu_wait(struct mv88e61xxphy_softc *);
static int
mv88e61xxphy_probe(device_t dev)
{
uint16_t val;
val = MIIBUS_READREG(device_get_parent(dev), MV88E61XX_PORT(0),
MV88E61XX_PORT_REVISION);
switch (val >> 4) {
case 0x121:
device_set_desc(dev, "Marvell Link Street 88E6123 3-Port Gigabit Switch");
return (0);
case 0x161:
device_set_desc(dev, "Marvell Link Street 88E6161 6-Port Gigabit Switch");
return (0);
case 0x165:
device_set_desc(dev, "Marvell Link Street 88E6161 6-Port Advanced Gigabit Switch");
return (0);
default:
return (ENXIO);
}
}
static int
mv88e61xxphy_attach(device_t dev)
{
char portbuf[] = "N";
struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
struct sysctl_oid *tree = device_get_sysctl_tree(dev);
struct sysctl_oid_list *child = SYSCTL_CHILDREN(tree);
struct sysctl_oid *port_node, *portN_node;
struct sysctl_oid_list *port_tree, *portN_tree;
struct mv88e61xxphy_softc *sc;
unsigned port;
sc = device_get_softc(dev);
sc->sc_dev = dev;
/*
* Initialize port softcs.
*/
for (port = 0; port < MV88E61XX_PORTS; port++) {
struct mv88e61xxphy_port_softc *psc;
psc = &sc->sc_ports[port];
psc->sc_switch = sc;
psc->sc_port = port;
psc->sc_domain = 0; /* One broadcast domain by default. */
psc->sc_vlan = port + 1; /* Tag VLANs by default. */
psc->sc_priority = 0; /* No default special priority. */
psc->sc_flags = 0;
}
/*
* Add per-port sysctl tree/handlers.
*/
port_node = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "port",
CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "Switch Ports");
port_tree = SYSCTL_CHILDREN(port_node);
for (port = 0; port < MV88E61XX_PORTS; port++) {
struct mv88e61xxphy_port_softc *psc;
psc = &sc->sc_ports[port];
portbuf[0] = '0' + port;
portN_node = SYSCTL_ADD_NODE(ctx, port_tree, OID_AUTO, portbuf,
CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "Switch Port");
portN_tree = SYSCTL_CHILDREN(portN_node);
SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "duplex",
CTLFLAG_RD | CTLTYPE_INT | CTLFLAG_NEEDGIANT, psc,
MV88E61XXPHY_LINK_SYSCTL_DUPLEX,
mv88e61xxphy_sysctl_link_proc, "IU",
"Media duplex status (0 = half duplex; 1 = full duplex)");
SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "link",
CTLFLAG_RD | CTLTYPE_INT | CTLFLAG_NEEDGIANT, psc,
MV88E61XXPHY_LINK_SYSCTL_LINK,
mv88e61xxphy_sysctl_link_proc, "IU",
"Link status (0 = down; 1 = up)");
SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "media",
CTLFLAG_RD | CTLTYPE_INT | CTLFLAG_NEEDGIANT, psc,
MV88E61XXPHY_LINK_SYSCTL_MEDIA,
mv88e61xxphy_sysctl_link_proc, "IU",
"Media speed (0 = unknown; 10 = 10Mbps; 100 = 100Mbps; 1000 = 1Gbps)");
SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "domain",
CTLFLAG_RW | CTLTYPE_INT | CTLFLAG_NEEDGIANT, psc,
MV88E61XXPHY_PORT_SYSCTL_DOMAIN,
mv88e61xxphy_sysctl_port_proc, "IU",
"Broadcast domain (ports can only talk to other ports in the same domain)");
SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "vlan",
CTLFLAG_RW | CTLTYPE_INT | CTLFLAG_NEEDGIANT, psc,
MV88E61XXPHY_PORT_SYSCTL_VLAN,
mv88e61xxphy_sysctl_port_proc, "IU",
"Tag packets from/for this port with a given VLAN.");
SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "priority",
CTLFLAG_RW | CTLTYPE_INT | CTLFLAG_NEEDGIANT, psc,
MV88E61XXPHY_PORT_SYSCTL_PRIORITY,
mv88e61xxphy_sysctl_port_proc, "IU",
"Default packet priority for this port.");
}
mv88e61xxphy_init(sc);
return (0);
}
static void
mv88e61xxphy_init(struct mv88e61xxphy_softc *sc)
{
unsigned port;
uint16_t val;
unsigned i;
/* Disable all ports. */
for (port = 0; port < MV88E61XX_PORTS; port++) {
struct mv88e61xxphy_port_softc *psc;
psc = &sc->sc_ports[port];
val = MV88E61XX_READ_PORT(psc, MV88E61XX_PORT_CONTROL);
val &= ~0x3;
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL, val);
}
DELAY(2000);
/* Reset the switch. */
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_CONTROL, 0xc400);
for (i = 0; i < 100; i++) {
val = MV88E61XX_READ(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_STATUS);
if ((val & 0xc800) == 0xc800)
break;
DELAY(10);
}
if (i == 100) {
device_printf(sc->sc_dev, "%s: switch reset timed out.\n", __func__);
return;
}
/* Disable PPU. */
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_CONTROL, 0x0000);
/* Configure host port and send monitor frames to it. */
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_MONITOR,
(MV88E61XX_HOST_PORT << 12) | (MV88E61XX_HOST_PORT << 8) |
(MV88E61XX_HOST_PORT << 4));
/* Disable remote management. */
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_REMOTE_MGMT, 0x0000);
/* Send all specifically-addressed frames to the host port. */
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL2, MV88E61XX_GLOBAL2_MANAGE_2X, 0xffff);
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL2, MV88E61XX_GLOBAL2_MANAGE_0X, 0xffff);
/* Remove provider-supplied tag and use it for switching. */
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL2, MV88E61XX_GLOBAL2_CONTROL2,
MV88E61XX_GLOBAL2_CONTROL2_REMOVE_PTAG);
/* Configure all ports. */
for (port = 0; port < MV88E61XX_PORTS; port++) {
struct mv88e61xxphy_port_softc *psc;
psc = &sc->sc_ports[port];
mv88e61xxphy_init_port(psc);
}
/* Reprogram VLAN table (VTU.) */
mv88e61xxphy_init_vtu(sc);
/* Enable all ports. */
for (port = 0; port < MV88E61XX_PORTS; port++) {
struct mv88e61xxphy_port_softc *psc;
psc = &sc->sc_ports[port];
val = MV88E61XX_READ_PORT(psc, MV88E61XX_PORT_CONTROL);
val |= 0x3;
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL, val);
}
}
static void
mv88e61xxphy_init_port(struct mv88e61xxphy_port_softc *psc)
{
struct mv88e61xxphy_softc *sc;
unsigned allow_mask;
sc = psc->sc_switch;
/* Set media type and flow control. */
if (psc->sc_port != MV88E61XX_HOST_PORT) {
/* Don't force any media type or flow control. */
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_FORCE_MAC, 0x0003);
} else {
/* Make CPU port 1G FDX. */
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_FORCE_MAC, 0x003e);
}
/* Don't limit flow control pauses. */
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_PAUSE_CONTROL, 0x0000);
/* Set various port functions per Linux. */
if (psc->sc_port != MV88E61XX_HOST_PORT) {
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL, 0x04bc);
} else {
/*
* Send frames for unknown unicast and multicast groups to
* host, too.
*/
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL, 0x063f);
}
if (psc->sc_port != MV88E61XX_HOST_PORT) {
/* Disable trunking. */
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL2, 0x0000);
} else {
/* Disable trunking and send learn messages to host. */
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL2, 0x8000);
}
/*
* Port-based VLAN map; isolates MAC tables and forces ports to talk
* only to the host.
*
* Always allow the host to send to all ports and allow all ports to
* send to the host.
*/
if (psc->sc_port != MV88E61XX_HOST_PORT) {
allow_mask = 1 << MV88E61XX_HOST_PORT;
} else {
allow_mask = (1 << MV88E61XX_PORTS) - 1;
allow_mask &= ~(1 << MV88E61XX_HOST_PORT);
}
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_VLAN_MAP,
(psc->sc_domain << 12) | allow_mask);
/* VLAN tagging. Set default priority and VLAN tag (or none.) */
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_VLAN,
(psc->sc_priority << 14) | psc->sc_vlan);
if (psc->sc_port == MV88E61XX_HOST_PORT) {
/* Set provider ingress tag. */
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_PROVIDER_PROTO,
ETHERTYPE_VLAN);
/* Set provider egress tag. */
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_ETHER_PROTO,
ETHERTYPE_VLAN);
/* Use secure 802.1q mode and accept only tagged frames. */
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_FILTER,
MV88E61XX_PORT_FILTER_MAP_DEST |
MV88E61XX_PORT_FILTER_8021Q_SECURE |
MV88E61XX_PORT_FILTER_DISCARD_UNTAGGED);
} else {
/* Don't allow tagged frames. */
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_FILTER,
MV88E61XX_PORT_FILTER_MAP_DEST |
MV88E61XX_PORT_FILTER_DISCARD_TAGGED);
}
}
static void
mv88e61xxphy_init_vtu(struct mv88e61xxphy_softc *sc)
{
unsigned port;
/*
* Start flush of the VTU.
*/
mv88e61xxphy_vtu_wait(sc);
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_VTU_OP,
MV88E61XX_GLOBAL_VTU_OP_BUSY | MV88E61XX_GLOBAL_VTU_OP_OP_FLUSH);
/*
* Queue each port's VLAN to be programmed.
*/
for (port = 0; port < MV88E61XX_PORTS; port++) {
struct mv88e61xxphy_port_softc *psc;
psc = &sc->sc_ports[port];
psc->sc_flags &= ~MV88E61XXPHY_PORT_FLAG_VTU_UPDATE;
if (psc->sc_vlan == 0)
continue;
psc->sc_flags |= MV88E61XXPHY_PORT_FLAG_VTU_UPDATE;
}
/*
* Program each VLAN that is in use.
*/
for (port = 0; port < MV88E61XX_PORTS; port++) {
struct mv88e61xxphy_port_softc *psc;
psc = &sc->sc_ports[port];
if ((psc->sc_flags & MV88E61XXPHY_PORT_FLAG_VTU_UPDATE) == 0)
continue;
mv88e61xxphy_vtu_load(sc, psc->sc_vlan);
}
/*
* Wait for last pending VTU operation to complete.
*/
mv88e61xxphy_vtu_wait(sc);
}
static int
mv88e61xxphy_sysctl_link_proc(SYSCTL_HANDLER_ARGS)
{
struct mv88e61xxphy_port_softc *psc = arg1;
enum mv88e61xxphy_sysctl_link_type type = arg2;
uint16_t val;
unsigned out;
val = MV88E61XX_READ_PORT(psc, MV88E61XX_PORT_STATUS);
switch (type) {
case MV88E61XXPHY_LINK_SYSCTL_DUPLEX:
if ((val & MV88E61XX_PORT_STATUS_DUPLEX) != 0)
out = 1;
else
out = 0;
break;
case MV88E61XXPHY_LINK_SYSCTL_LINK:
if ((val & MV88E61XX_PORT_STATUS_LINK) != 0)
out = 1;
else
out = 0;
break;
case MV88E61XXPHY_LINK_SYSCTL_MEDIA:
switch (val & MV88E61XX_PORT_STATUS_MEDIA) {
case MV88E61XX_PORT_STATUS_MEDIA_10M:
out = 10;
break;
case MV88E61XX_PORT_STATUS_MEDIA_100M:
out = 100;
break;
case MV88E61XX_PORT_STATUS_MEDIA_1G:
out = 1000;
break;
default:
out = 0;
break;
}
break;
default:
return (EINVAL);
}
return (sysctl_handle_int(oidp, NULL, out, req));
}
static int
mv88e61xxphy_sysctl_port_proc(SYSCTL_HANDLER_ARGS)
{
struct mv88e61xxphy_port_softc *psc = arg1;
enum mv88e61xxphy_sysctl_port_type type = arg2;
struct mv88e61xxphy_softc *sc = psc->sc_switch;
unsigned max, val, *valp;
int error;
switch (type) {
case MV88E61XXPHY_PORT_SYSCTL_DOMAIN:
valp = &psc->sc_domain;
max = 0xf;
break;
case MV88E61XXPHY_PORT_SYSCTL_VLAN:
valp = &psc->sc_vlan;
max = 0x1000;
break;
case MV88E61XXPHY_PORT_SYSCTL_PRIORITY:
valp = &psc->sc_priority;
max = 3;
break;
default:
return (EINVAL);
}
val = *valp;
error = sysctl_handle_int(oidp, &val, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
/* Bounds check value. */
if (val >= max)
return (EINVAL);
/* Reinitialize switch with new value. */
*valp = val;
mv88e61xxphy_init(sc);
return (0);
}
static void
mv88e61xxphy_vtu_load(struct mv88e61xxphy_softc *sc, uint16_t vid)
{
unsigned port;
/*
* Wait for previous operation to complete.
*/
mv88e61xxphy_vtu_wait(sc);
/*
* Set VID.
*/
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_VTU_VID,
MV88E61XX_GLOBAL_VTU_VID_VALID | vid);
/*
* Add ports to this VTU.
*/
for (port = 0; port < MV88E61XX_PORTS; port++) {
struct mv88e61xxphy_port_softc *psc;
psc = &sc->sc_ports[port];
if (psc->sc_vlan == vid) {
/*
* Send this port its VLAN traffic untagged.
*/
psc->sc_flags &= ~MV88E61XXPHY_PORT_FLAG_VTU_UPDATE;
mv88e61xxphy_vtu_set_membership(sc, port, MV88E61XXPHY_VTU_UNTAGGED);
} else if (psc->sc_port == MV88E61XX_HOST_PORT) {
/*
* The host sees all VLANs tagged.
*/
mv88e61xxphy_vtu_set_membership(sc, port, MV88E61XXPHY_VTU_TAGGED);
} else {
/*
* This port isn't on this VLAN.
*/
mv88e61xxphy_vtu_set_membership(sc, port, MV88E61XXPHY_VTU_DISCARDED);
}
}
/*
* Start adding this entry.
*/
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_VTU_OP,
MV88E61XX_GLOBAL_VTU_OP_BUSY |
MV88E61XX_GLOBAL_VTU_OP_OP_VTU_LOAD);
}
static void
mv88e61xxphy_vtu_set_membership(struct mv88e61xxphy_softc *sc, unsigned port,
enum mv88e61xxphy_vtu_membership_type type)
{
unsigned shift, reg;
uint16_t bits;
uint16_t val;
switch (type) {
case MV88E61XXPHY_VTU_UNMODIFIED:
bits = 0;
break;
case MV88E61XXPHY_VTU_UNTAGGED:
bits = 1;
break;
case MV88E61XXPHY_VTU_TAGGED:
bits = 2;
break;
case MV88E61XXPHY_VTU_DISCARDED:
bits = 3;
break;
default:
return;
}
if (port < 4) {
reg = MV88E61XX_GLOBAL_VTU_DATA_P0P3;
shift = port * 4;
} else {
reg = MV88E61XX_GLOBAL_VTU_DATA_P4P5;
shift = (port - 4) * 4;
}
val = MV88E61XX_READ(sc, MV88E61XX_GLOBAL, reg);
val |= bits << shift;
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, reg, val);
}
static void
mv88e61xxphy_vtu_wait(struct mv88e61xxphy_softc *sc)
{
uint16_t val;
for (;;) {
val = MV88E61XX_READ(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_VTU_OP);
if ((val & MV88E61XX_GLOBAL_VTU_OP_BUSY) == 0)
return;
}
}
static device_method_t mv88e61xxphy_methods[] = {
/* device interface */
DEVMETHOD(device_probe, mv88e61xxphy_probe),
DEVMETHOD(device_attach, mv88e61xxphy_attach),
DEVMETHOD(device_detach, bus_generic_detach),
DEVMETHOD(device_shutdown, bus_generic_shutdown),
{ 0, 0 }
};
static devclass_t mv88e61xxphy_devclass;
static driver_t mv88e61xxphy_driver = {
"mv88e61xxphy",
mv88e61xxphy_methods,
sizeof(struct mv88e61xxphy_softc)
};
DRIVER_MODULE(mv88e61xxphy, octe, mv88e61xxphy_driver, mv88e61xxphy_devclass, 0, 0);