Add support for the Marvell 88E6190 11 ports switch.

With more ports, some of the registers are shifted a bit to accommodate.

This switch also adds two high speed Serdes/SGMII interfaces (2.5 Gb/s).

Sponsored by:	Rubicon Communications, LLC (Netgate)
This commit is contained in:
Luiz Otavio O Souza 2019-07-01 13:41:37 +00:00
parent b46517aa7b
commit 9aba06377d
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=349578
2 changed files with 186 additions and 48 deletions

View File

@ -106,6 +106,8 @@ static int e6000sw_parse_fixed_link(e6000sw_softc_t *, phandle_t, uint32_t);
static int e6000sw_parse_ethernet(e6000sw_softc_t *, phandle_t, uint32_t);
static int e6000sw_attach(device_t);
static int e6000sw_detach(device_t);
static int e6000sw_read_xmdio(device_t, int, int, int);
static int e6000sw_write_xmdio(device_t, int, int, int, int);
static int e6000sw_readphy(device_t, int, int);
static int e6000sw_writephy(device_t, int, int, int);
static etherswitch_info_t* e6000sw_getinfo(device_t);
@ -205,16 +207,26 @@ e6000sw_probe(device_t dev)
const char *description;
phandle_t switch_node;
sc = device_get_softc(dev);
switch_node = ofw_bus_find_compatible(OF_finddevice("/"),
"marvell,mv88e6085");
if (switch_node == 0) {
switch_node = ofw_bus_find_compatible(OF_finddevice("/"),
"marvell,mv88e6190");
if (switch_node == 0)
return (ENXIO);
if (switch_node == 0)
return (ENXIO);
/*
* Trust DTS and fix the port register offset for the MV88E6190
* detection bellow.
*/
sc->swid = MV88E6190;
}
if (bootverbose)
device_printf(dev, "Found switch_node: 0x%x\n", switch_node);
sc = device_get_softc(dev);
sc->dev = dev;
sc->node = switch_node;
@ -230,7 +242,7 @@ e6000sw_probe(device_t dev)
*/
sx_init(&sc->sx, "e6000sw_tmp");
E6000SW_LOCK(sc);
sc->swid = e6000sw_readreg(sc, REG_PORT(0), SWITCH_ID) & 0xfff0;
sc->swid = e6000sw_readreg(sc, REG_PORT(sc, 0), SWITCH_ID) & 0xfff0;
E6000SW_UNLOCK(sc);
sx_destroy(&sc->sx);
@ -257,6 +269,10 @@ e6000sw_probe(device_t dev)
description = "Marvell 88E6176";
sc->num_ports = 7;
break;
case MV88E6190:
description = "Marvell 88E6190";
sc->num_ports = 11;
break;
default:
device_printf(dev, "Unrecognized device, id 0x%x.\n", sc->swid);
return (ENXIO);
@ -278,18 +294,16 @@ e6000sw_parse_fixed_link(e6000sw_softc_t *sc, phandle_t node, uint32_t port)
if (fixed_link != 0) {
sc->fixed_mask |= (1 << port);
if (OF_getencprop(fixed_link, "speed", &speed, sizeof(speed))> 0) {
if (speed == 2500 &&
(MVSWITCH(sc, MV88E6141) ||
MVSWITCH(sc, MV88E6341)))
sc->fixed25_mask |= (1 << port);
} else {
device_printf(sc->dev,
if (OF_getencprop(fixed_link,
"speed", &speed, sizeof(speed)) < 0) {
device_printf(sc->dev,
"Port %d has a fixed-link node without a speed "
"property\n", port);
return (ENXIO);
return (ENXIO);
}
if (speed == 2500 && (MVSWITCH(sc, MV88E6141) ||
MVSWITCH(sc, MV88E6341) || MVSWITCH(sc, MV88E6190)))
sc->fixed25_mask |= (1 << port);
}
return (0);
@ -383,9 +397,36 @@ e6000sw_attach_miibus(e6000sw_softc_t *sc, int port)
return (0);
}
static void
e6000sw_serdes_power(device_t dev, int port, bool sgmii)
{
uint32_t reg;
/* SGMII */
reg = e6000sw_read_xmdio(dev, port, E6000SW_SERDES_DEV,
E6000SW_SERDES_SGMII_CTL);
if (sgmii)
reg &= ~E6000SW_SERDES_PDOWN;
else
reg |= E6000SW_SERDES_PDOWN;
e6000sw_write_xmdio(dev, port, E6000SW_SERDES_DEV,
E6000SW_SERDES_SGMII_CTL, reg);
/* 10GBASE-R/10GBASE-X4/X2 */
reg = e6000sw_read_xmdio(dev, port, E6000SW_SERDES_DEV,
E6000SW_SERDES_PCS_CTL1);
if (sgmii)
reg |= E6000SW_SERDES_PDOWN;
else
reg &= ~E6000SW_SERDES_PDOWN;
e6000sw_write_xmdio(dev, port, E6000SW_SERDES_DEV,
E6000SW_SERDES_PCS_CTL1, reg);
}
static int
e6000sw_attach(device_t dev)
{
bool sgmii;
e6000sw_softc_t *sc;
phandle_t child, ports;
int err, port;
@ -436,28 +477,47 @@ e6000sw_attach(device_t dev)
if (e6000sw_is_fixedport(sc, port)) {
/* Link must be down to change speed force value. */
reg = e6000sw_readreg(sc, REG_PORT(port), PSC_CONTROL);
reg = e6000sw_readreg(sc, REG_PORT(sc, port),
PSC_CONTROL);
reg &= ~PSC_CONTROL_LINK_UP;
reg |= PSC_CONTROL_FORCED_LINK;
e6000sw_writereg(sc, REG_PORT(port), PSC_CONTROL, reg);
e6000sw_writereg(sc, REG_PORT(sc, port), PSC_CONTROL,
reg);
/*
* Force speed, full-duplex, EEE off and flow-control
* on.
*/
reg &= ~(PSC_CONTROL_SPD2500 | PSC_CONTROL_ALT_SPD |
PSC_CONTROL_FORCED_FC | PSC_CONTROL_FC_ON |
PSC_CONTROL_FORCED_EEE);
if (e6000sw_is_fixed25port(sc, port))
reg |= PSC_CONTROL_SPD2500;
else
reg |= PSC_CONTROL_SPD1000;
if (MVSWITCH(sc, MV88E6190) &&
e6000sw_is_fixed25port(sc, port))
reg |= PSC_CONTROL_ALT_SPD;
reg |= PSC_CONTROL_FORCED_DPX | PSC_CONTROL_FULLDPX |
PSC_CONTROL_FORCED_LINK | PSC_CONTROL_LINK_UP |
PSC_CONTROL_FORCED_FC | PSC_CONTROL_FC_ON |
PSC_CONTROL_FORCED_SPD;
if (MVSWITCH(sc, MV88E6141) || MVSWITCH(sc, MV88E6341))
reg |= PSC_CONTROL_FORCED_EEE;
e6000sw_writereg(sc, REG_PORT(port), PSC_CONTROL, reg);
if (!MVSWITCH(sc, MV88E6190))
reg |= PSC_CONTROL_FORCED_FC | PSC_CONTROL_FC_ON;
if (MVSWITCH(sc, MV88E6141) ||
MVSWITCH(sc, MV88E6341) ||
MVSWITCH(sc, MV88E6190))
reg |= PSC_CONTROL_FORCED_EEE;
e6000sw_writereg(sc, REG_PORT(sc, port), PSC_CONTROL,
reg);
/* Power on the SERDES interfaces. */
if (MVSWITCH(sc, MV88E6190) &&
(port == 9 || port == 10)) {
if (e6000sw_is_fixed25port(sc, port))
sgmii = false;
else
sgmii = true;
e6000sw_serdes_power(sc->dev, port, sgmii);
}
}
/* Don't attach miibus at CPU/fixed ports */
@ -510,6 +570,79 @@ e6000sw_waitready(e6000sw_softc_t *sc, uint32_t phy, uint32_t reg,
return (1);
}
/* XMDIO/Clause 45 access. */
static int
e6000sw_read_xmdio(device_t dev, int phy, int devaddr, int devreg)
{
e6000sw_softc_t *sc;
uint32_t reg;
sc = device_get_softc(dev);
E6000SW_LOCK_ASSERT(sc, SA_XLOCKED);
if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
device_printf(dev, "Timeout while waiting for switch\n");
return (ETIMEDOUT);
}
reg = devaddr & SMI_CMD_REG_ADDR_MASK;
reg |= (phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK;
/* Load C45 register address. */
e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, devreg);
e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG,
reg | SMI_CMD_OP_C45_ADDR);
if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
device_printf(dev, "Timeout while waiting for switch\n");
return (ETIMEDOUT);
}
/* Start C45 read operation. */
e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG,
reg | SMI_CMD_OP_C45_READ);
if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
device_printf(dev, "Timeout while waiting for switch\n");
return (ETIMEDOUT);
}
/* Read C45 data. */
reg = e6000sw_readreg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG);
return (reg & PHY_DATA_MASK);
}
static int
e6000sw_write_xmdio(device_t dev, int phy, int devaddr, int devreg, int val)
{
e6000sw_softc_t *sc;
uint32_t reg;
sc = device_get_softc(dev);
E6000SW_LOCK_ASSERT(sc, SA_XLOCKED);
if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
device_printf(dev, "Timeout while waiting for switch\n");
return (ETIMEDOUT);
}
reg = devaddr & SMI_CMD_REG_ADDR_MASK;
reg |= (phy << SMI_CMD_DEV_ADDR) & SMI_CMD_DEV_ADDR_MASK;
/* Load C45 register address. */
e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, devreg);
e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG,
reg | SMI_CMD_OP_C45_ADDR);
if (E6000SW_WAITREADY2(sc, SMI_PHY_CMD_REG, SMI_CMD_BUSY)) {
device_printf(dev, "Timeout while waiting for switch\n");
return (ETIMEDOUT);
}
/* Load data and start the C45 write operation. */
e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_DATA_REG, devreg);
e6000sw_writereg(sc, REG_GLOBAL2, SMI_PHY_CMD_REG,
reg | SMI_CMD_OP_C45_WRITE);
return (0);
}
/*
* PHY registers are paged. Put page index in reg 22 (accessible from every
* page), then access specific register.
@ -670,7 +803,7 @@ e6000sw_getport(device_t dev, etherswitch_port_t *p)
e6000sw_get_pvid(sc, p->es_port, &p->es_pvid);
/* Port flags. */
reg = e6000sw_readreg(sc, REG_PORT(p->es_port), PORT_CONTROL2);
reg = e6000sw_readreg(sc, REG_PORT(sc, p->es_port), PORT_CONTROL2);
if (reg & PORT_CONTROL2_DISC_TAGGED)
p->es_flags |= ETHERSWITCH_PORT_DROPTAGGED;
if (reg & PORT_CONTROL2_DISC_UNTAGGED)
@ -717,7 +850,7 @@ e6000sw_setport(device_t dev, etherswitch_port_t *p)
return (0);
/* Port flags. */
reg = e6000sw_readreg(sc, REG_PORT(p->es_port), PORT_CONTROL2);
reg = e6000sw_readreg(sc, REG_PORT(sc, p->es_port), PORT_CONTROL2);
if (p->es_flags & ETHERSWITCH_PORT_DROPTAGGED)
reg |= PORT_CONTROL2_DISC_TAGGED;
else
@ -726,7 +859,7 @@ e6000sw_setport(device_t dev, etherswitch_port_t *p)
reg |= PORT_CONTROL2_DISC_UNTAGGED;
else
reg &= ~PORT_CONTROL2_DISC_UNTAGGED;
e6000sw_writereg(sc, REG_PORT(p->es_port), PORT_CONTROL2, reg);
e6000sw_writereg(sc, REG_PORT(sc, p->es_port), PORT_CONTROL2, reg);
err = 0;
E6000SW_LOCK(sc);
@ -748,15 +881,15 @@ e6000sw_port_vlan_assign(e6000sw_softc_t *sc, int port, uint32_t fid,
{
uint32_t reg;
reg = e6000sw_readreg(sc, REG_PORT(port), PORT_VLAN_MAP);
reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VLAN_MAP);
reg &= ~(PORT_MASK(sc) | PORT_VLAN_MAP_FID_MASK);
reg |= members & PORT_MASK(sc) & ~(1 << port);
reg |= (fid << PORT_VLAN_MAP_FID) & PORT_VLAN_MAP_FID_MASK;
e6000sw_writereg(sc, REG_PORT(port), PORT_VLAN_MAP, reg);
reg = e6000sw_readreg(sc, REG_PORT(port), PORT_CONTROL1);
e6000sw_writereg(sc, REG_PORT(sc, port), PORT_VLAN_MAP, reg);
reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL1);
reg &= ~PORT_CONTROL1_FID_MASK;
reg |= (fid >> 4) & PORT_CONTROL1_FID_MASK;
e6000sw_writereg(sc, REG_PORT(port), PORT_CONTROL1, reg);
e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL1, reg);
}
static int
@ -767,8 +900,8 @@ e6000sw_init_vlan(struct e6000sw_softc *sc)
/* Disable all ports */
for (port = 0; port < sc->num_ports; port++) {
ret = e6000sw_readreg(sc, REG_PORT(port), PORT_CONTROL);
e6000sw_writereg(sc, REG_PORT(port), PORT_CONTROL,
ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL);
e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL,
(ret & ~PORT_CONTROL_ENABLE));
}
@ -777,23 +910,23 @@ e6000sw_init_vlan(struct e6000sw_softc *sc)
for (port = 0; port < sc->num_ports; port++) {
/* Reset the egress and frame mode. */
ret = e6000sw_readreg(sc, REG_PORT(port), PORT_CONTROL);
ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL);
ret &= ~(PORT_CONTROL_EGRESS | PORT_CONTROL_FRAME);
e6000sw_writereg(sc, REG_PORT(port), PORT_CONTROL, ret);
e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL, ret);
/* Set the the 802.1q mode. */
ret = e6000sw_readreg(sc, REG_PORT(port), PORT_CONTROL2);
ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL2);
ret &= ~PORT_CONTROL2_DOT1Q;
if (sc->vlan_mode == ETHERSWITCH_VLAN_DOT1Q)
ret |= PORT_CONTROL2_DOT1Q;
e6000sw_writereg(sc, REG_PORT(port), PORT_CONTROL2, ret);
e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL2, ret);
}
for (port = 0; port < sc->num_ports; port++) {
if (!e6000sw_is_portenabled(sc, port))
continue;
ret = e6000sw_readreg(sc, REG_PORT(port), PORT_VID);
ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VID);
/* Set port priority */
ret &= ~PORT_VID_PRIORITY_MASK;
@ -804,7 +937,7 @@ e6000sw_init_vlan(struct e6000sw_softc *sc)
ret |= 1;
else
ret |= (port + 1);
e6000sw_writereg(sc, REG_PORT(port), PORT_VID, ret);
e6000sw_writereg(sc, REG_PORT(sc, port), PORT_VID, ret);
}
/* Assign the member ports to each origin port. */
@ -835,8 +968,8 @@ e6000sw_init_vlan(struct e6000sw_softc *sc)
for (port = 0; port < sc->num_ports; port++) {
if (!e6000sw_is_portenabled(sc, port))
continue;
ret = e6000sw_readreg(sc, REG_PORT(port), PORT_CONTROL);
e6000sw_writereg(sc, REG_PORT(port), PORT_CONTROL,
ret = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL);
e6000sw_writereg(sc, REG_PORT(sc, port), PORT_CONTROL,
(ret | PORT_CONTROL_ENABLE));
}
@ -873,9 +1006,11 @@ e6000sw_set_vlan_mode(struct e6000sw_softc *sc, uint32_t mode)
static int
e6000sw_readreg_wrapper(device_t dev, int addr_reg)
{
e6000sw_softc_t *sc;
sc = device_get_softc(dev);
if ((addr_reg > (REG_GLOBAL2 * 32 + REG_NUM_MAX)) ||
(addr_reg < (REG_PORT(0) * 32))) {
(addr_reg < (REG_PORT(sc, 0) * 32))) {
device_printf(dev, "Wrong register address.\n");
return (EINVAL);
}
@ -887,9 +1022,11 @@ e6000sw_readreg_wrapper(device_t dev, int addr_reg)
static int
e6000sw_writereg_wrapper(device_t dev, int addr_reg, int val)
{
e6000sw_softc_t *sc;
sc = device_get_softc(dev);
if ((addr_reg > (REG_GLOBAL2 * 32 + REG_NUM_MAX)) ||
(addr_reg < (REG_PORT(0) * 32))) {
(addr_reg < (REG_PORT(sc, 0) * 32))) {
device_printf(dev, "Wrong register address.\n");
return (EINVAL);
}
@ -1049,11 +1186,11 @@ e6000sw_get_port_vlan(e6000sw_softc_t *sc, etherswitch_vlangroup_t *vg)
return (0);
}
reg = e6000sw_readreg(sc, REG_PORT(port), PORT_VLAN_MAP);
reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VLAN_MAP);
vg->es_untagged_ports = vg->es_member_ports = reg & PORT_MASK(sc);
vg->es_vid = port | ETHERSWITCH_VID_VALID;
vg->es_fid = (reg & PORT_VLAN_MAP_FID_MASK) >> PORT_VLAN_MAP_FID;
reg = e6000sw_readreg(sc, REG_PORT(port), PORT_CONTROL1);
reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_CONTROL1);
vg->es_fid |= (reg & PORT_CONTROL1_FID_MASK) << 4;
return (0);
@ -1268,10 +1405,10 @@ e6000sw_set_pvid(e6000sw_softc_t *sc, int port, int pvid)
{
uint32_t reg;
reg = e6000sw_readreg(sc, REG_PORT(port), PORT_VID);
reg = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VID);
reg &= ~PORT_VID_DEF_VID_MASK;
reg |= (pvid & PORT_VID_DEF_VID_MASK);
e6000sw_writereg(sc, REG_PORT(port), PORT_VID, reg);
e6000sw_writereg(sc, REG_PORT(sc, port), PORT_VID, reg);
}
static __inline int
@ -1281,7 +1418,7 @@ e6000sw_get_pvid(e6000sw_softc_t *sc, int port, int *pvid)
if (pvid == NULL)
return (ENXIO);
*pvid = e6000sw_readreg(sc, REG_PORT(port), PORT_VID) &
*pvid = e6000sw_readreg(sc, REG_PORT(sc, port), PORT_VID) &
PORT_VID_DEF_VID_MASK;
return (0);
@ -1346,7 +1483,7 @@ e6000sw_tick(void *arg)
if (mii == NULL)
continue;
portstatus = e6000sw_readreg(sc, REG_PORT(port),
portstatus = e6000sw_readreg(sc, REG_PORT(sc, port),
PORT_STATUS);
e6000sw_update_ifmedia(portstatus,

View File

@ -48,6 +48,7 @@ struct atu_opt {
#define MV88E6352 0x3520
#define MV88E6172 0x1720
#define MV88E6176 0x1760
#define MV88E6190 0x1900
#define MVSWITCH(_sc, id) ((_sc)->swid == (id))
#define MVSWITCH_MULTICHIP(_sc) ((_sc)->sw_addr != 0)
@ -57,7 +58,7 @@ struct atu_opt {
*/
#define REG_GLOBAL 0x1b
#define REG_GLOBAL2 0x1c
#define REG_PORT(p) (0x10 + (p))
#define REG_PORT(_sc, p) ((MVSWITCH((_sc), MV88E6190) ? 0 : 0x10) + (p))
#define REG_NUM_MAX 31
@ -139,13 +140,13 @@ struct atu_opt {
#define VTU_DATA 7
#define VTU_DATA2 8
#define VTU_FID_MASK(_sc) 0xff
#define VTU_FID_MASK(_sc) (MVSWITCH((_sc), MV88E6190) ? 0xfff : 0xff)
#define VTU_FID_POLICY (1 << 12)
#define VTU_PORT_UNMODIFIED 0
#define VTU_PORT_UNTAGGED 1
#define VTU_PORT_TAGGED 2
#define VTU_PORT_DISCARD 3
#define VTU_PPREG(_sc) 4
#define VTU_PPREG(_sc) (MVSWITCH((_sc), MV88E6190) ? 8 : 4)
#define VTU_PORT(_sc, p) (((p) % VTU_PPREG(_sc)) * (16 / VTU_PPREG(_sc)))
#define VTU_PORT_MASK 3
#define VTU_BUSY (1 << 15)
@ -175,7 +176,7 @@ struct atu_opt {
#define ATU_MAC_ADDR45 15
#define ATU_DATA_LAG (1 << 15)
#define ATU_PORT_MASK(_sc) 0xff0
#define ATU_PORT_MASK(_sc) (MVSWITCH((_sc), MV88E6190) ? 0xfff0 : 0xff0)
#define ATU_PORT_SHIFT 4
#define ATU_LAG_MASK 0xf0
#define ATU_LAG_SHIFT 4