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

Merge branch 'net-dsa-mv88e6xxx-Peridot-Topaz-SERDES-changes'

Marek Behún says:

====================
net: dsa: mv88e6xxx: Peridot/Topaz SERDES changes

this is the fifth version of changes for the Topaz/Peridot family of
switches. The patches apply on net-next.
Changes since v4:
 - added Reviewed-by and Tested-by tags on first 2 patches, the others
   are changed are affected by changes in patch 3/6, so I did not add
   the tags, except for 5/6, which is just macro renaming
 - patch 3 was changed: the serdes_get_lane returns 0 on success (lane
   was discovered), -ENODEV if not lane is present on the port, and
   other error if other error occured. Lane is put into a pointer of
   type u8
 - patches 4 and 6 were affected by this (error detecting from
   serdes_get_lane)
 - Andrew's complaint about the two additional parameters
   (allow_over_2500 and make_cmode_writable) was addressed, by Vivien's
   advice: I put a new method into chip operations structure, named
   port_set_cmode_writable. This is called from mv88e6xxx_port_setup_mac
   just before port_set_cmode. The method is implemented for Topaz.
   The check if cmodes over 2500 should be allowed on given port is now
   done in the specific port_set_cmode() that requires it, thus the
   allow_over_2500 argument is not needed

Again, tested on Turris Mox with Peridot, Topaz, and Peridot + Topaz.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents e93b4f03 7a3007d2
......@@ -10,6 +10,7 @@ mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_GLOBAL2) += global2_scratch.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += hwtstamp.o
mv88e6xxx-objs += phy.o
mv88e6xxx-objs += port.o
mv88e6xxx-objs += port_hidden.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += ptp.o
mv88e6xxx-objs += serdes.o
mv88e6xxx-objs += smi.o
......@@ -454,6 +454,12 @@ int mv88e6xxx_port_setup_mac(struct mv88e6xxx_chip *chip, int port, int link,
goto restore_link;
}
if (chip->info->ops->port_set_cmode_writable) {
err = chip->info->ops->port_set_cmode_writable(chip, port);
if (err && err != -EOPNOTSUPP)
goto restore_link;
}
if (chip->info->ops->port_set_cmode) {
err = chip->info->ops->port_set_cmode(chip, port, mode);
if (err && err != -EOPNOTSUPP)
......@@ -2317,60 +2323,6 @@ static int mv88e6xxx_stats_setup(struct mv88e6xxx_chip *chip)
return mv88e6xxx_g1_stats_clear(chip);
}
/* The mv88e6390 has some hidden registers used for debug and
* development. The errata also makes use of them.
*/
static int mv88e6390_hidden_write(struct mv88e6xxx_chip *chip, int port,
int reg, u16 val)
{
u16 ctrl;
int err;
err = mv88e6xxx_port_write(chip, PORT_RESERVED_1A_DATA_PORT,
PORT_RESERVED_1A, val);
if (err)
return err;
ctrl = PORT_RESERVED_1A_BUSY | PORT_RESERVED_1A_WRITE |
PORT_RESERVED_1A_BLOCK | port << PORT_RESERVED_1A_PORT_SHIFT |
reg;
return mv88e6xxx_port_write(chip, PORT_RESERVED_1A_CTRL_PORT,
PORT_RESERVED_1A, ctrl);
}
static int mv88e6390_hidden_wait(struct mv88e6xxx_chip *chip)
{
int bit = __bf_shf(PORT_RESERVED_1A_BUSY);
return mv88e6xxx_wait_bit(chip, PORT_RESERVED_1A_CTRL_PORT,
PORT_RESERVED_1A, bit, 0);
}
static int mv88e6390_hidden_read(struct mv88e6xxx_chip *chip, int port,
int reg, u16 *val)
{
u16 ctrl;
int err;
ctrl = PORT_RESERVED_1A_BUSY | PORT_RESERVED_1A_READ |
PORT_RESERVED_1A_BLOCK | port << PORT_RESERVED_1A_PORT_SHIFT |
reg;
err = mv88e6xxx_port_write(chip, PORT_RESERVED_1A_CTRL_PORT,
PORT_RESERVED_1A, ctrl);
if (err)
return err;
err = mv88e6390_hidden_wait(chip);
if (err)
return err;
return mv88e6xxx_port_read(chip, PORT_RESERVED_1A_DATA_PORT,
PORT_RESERVED_1A, val);
}
/* Check if the errata has already been applied. */
static bool mv88e6390_setup_errata_applied(struct mv88e6xxx_chip *chip)
{
......@@ -2379,7 +2331,7 @@ static bool mv88e6390_setup_errata_applied(struct mv88e6xxx_chip *chip)
u16 val;
for (port = 0; port < mv88e6xxx_num_ports(chip); port++) {
err = mv88e6390_hidden_read(chip, port, 0, &val);
err = mv88e6xxx_port_hidden_read(chip, 0xf, port, 0, &val);
if (err) {
dev_err(chip->dev,
"Error reading hidden register: %d\n", err);
......@@ -2412,7 +2364,7 @@ static int mv88e6390_setup_errata(struct mv88e6xxx_chip *chip)
}
for (port = 0; port < mv88e6xxx_num_ports(chip); port++) {
err = mv88e6390_hidden_write(chip, port, 0, 0x01c0);
err = mv88e6xxx_port_hidden_write(chip, 0xf, port, 0, 0x01c0);
if (err)
return err;
}
......@@ -2967,6 +2919,8 @@ static const struct mv88e6xxx_ops mv88e6141_ops = {
.port_disable_pri_override = mv88e6xxx_port_disable_pri_override,
.port_link_state = mv88e6352_port_link_state,
.port_get_cmode = mv88e6352_port_get_cmode,
.port_set_cmode_writable = mv88e6341_port_set_cmode_writable,
.port_set_cmode = mv88e6341_port_set_cmode,
.port_setup_message_port = mv88e6xxx_setup_message_port,
.stats_snapshot = mv88e6390_g1_stats_snapshot,
.stats_set_histogram = mv88e6095_g1_stats_set_histogram,
......@@ -2981,7 +2935,10 @@ static const struct mv88e6xxx_ops mv88e6141_ops = {
.reset = mv88e6352_g1_reset,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
.serdes_power = mv88e6341_serdes_power,
.serdes_power = mv88e6390_serdes_power,
.serdes_get_lane = mv88e6341_serdes_get_lane,
.serdes_irq_setup = mv88e6390_serdes_irq_setup,
.serdes_irq_free = mv88e6390_serdes_irq_free,
.gpio_ops = &mv88e6352_gpio_ops,
.phylink_validate = mv88e6341_phylink_validate,
};
......@@ -3309,6 +3266,7 @@ static const struct mv88e6xxx_ops mv88e6190_ops = {
.vtu_getnext = mv88e6390_g1_vtu_getnext,
.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
.serdes_power = mv88e6390_serdes_power,
.serdes_get_lane = mv88e6390_serdes_get_lane,
.serdes_irq_setup = mv88e6390_serdes_irq_setup,
.serdes_irq_free = mv88e6390_serdes_irq_free,
.gpio_ops = &mv88e6352_gpio_ops,
......@@ -3354,9 +3312,10 @@ static const struct mv88e6xxx_ops mv88e6190x_ops = {
.rmu_disable = mv88e6390_g1_rmu_disable,
.vtu_getnext = mv88e6390_g1_vtu_getnext,
.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
.serdes_power = mv88e6390x_serdes_power,
.serdes_irq_setup = mv88e6390x_serdes_irq_setup,
.serdes_irq_free = mv88e6390x_serdes_irq_free,
.serdes_power = mv88e6390_serdes_power,
.serdes_get_lane = mv88e6390x_serdes_get_lane,
.serdes_irq_setup = mv88e6390_serdes_irq_setup,
.serdes_irq_free = mv88e6390_serdes_irq_free,
.gpio_ops = &mv88e6352_gpio_ops,
.phylink_validate = mv88e6390x_phylink_validate,
};
......@@ -3401,6 +3360,7 @@ static const struct mv88e6xxx_ops mv88e6191_ops = {
.vtu_getnext = mv88e6390_g1_vtu_getnext,
.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
.serdes_power = mv88e6390_serdes_power,
.serdes_get_lane = mv88e6390_serdes_get_lane,
.serdes_irq_setup = mv88e6390_serdes_irq_setup,
.serdes_irq_free = mv88e6390_serdes_irq_free,
.avb_ops = &mv88e6390_avb_ops,
......@@ -3537,6 +3497,7 @@ static const struct mv88e6xxx_ops mv88e6290_ops = {
.vtu_getnext = mv88e6390_g1_vtu_getnext,
.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
.serdes_power = mv88e6390_serdes_power,
.serdes_get_lane = mv88e6390_serdes_get_lane,
.serdes_irq_setup = mv88e6390_serdes_irq_setup,
.serdes_irq_free = mv88e6390_serdes_irq_free,
.gpio_ops = &mv88e6352_gpio_ops,
......@@ -3657,6 +3618,8 @@ static const struct mv88e6xxx_ops mv88e6341_ops = {
.port_disable_pri_override = mv88e6xxx_port_disable_pri_override,
.port_link_state = mv88e6352_port_link_state,
.port_get_cmode = mv88e6352_port_get_cmode,
.port_set_cmode_writable = mv88e6341_port_set_cmode_writable,
.port_set_cmode = mv88e6341_port_set_cmode,
.port_setup_message_port = mv88e6xxx_setup_message_port,
.stats_snapshot = mv88e6390_g1_stats_snapshot,
.stats_set_histogram = mv88e6095_g1_stats_set_histogram,
......@@ -3671,7 +3634,10 @@ static const struct mv88e6xxx_ops mv88e6341_ops = {
.reset = mv88e6352_g1_reset,
.vtu_getnext = mv88e6352_g1_vtu_getnext,
.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
.serdes_power = mv88e6341_serdes_power,
.serdes_power = mv88e6390_serdes_power,
.serdes_get_lane = mv88e6341_serdes_get_lane,
.serdes_irq_setup = mv88e6390_serdes_irq_setup,
.serdes_irq_free = mv88e6390_serdes_irq_free,
.gpio_ops = &mv88e6352_gpio_ops,
.avb_ops = &mv88e6390_avb_ops,
.ptp_ops = &mv88e6352_ptp_ops,
......@@ -3854,6 +3820,7 @@ static const struct mv88e6xxx_ops mv88e6390_ops = {
.vtu_getnext = mv88e6390_g1_vtu_getnext,
.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
.serdes_power = mv88e6390_serdes_power,
.serdes_get_lane = mv88e6390_serdes_get_lane,
.serdes_irq_setup = mv88e6390_serdes_irq_setup,
.serdes_irq_free = mv88e6390_serdes_irq_free,
.gpio_ops = &mv88e6352_gpio_ops,
......@@ -3903,9 +3870,10 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = {
.rmu_disable = mv88e6390_g1_rmu_disable,
.vtu_getnext = mv88e6390_g1_vtu_getnext,
.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
.serdes_power = mv88e6390x_serdes_power,
.serdes_irq_setup = mv88e6390x_serdes_irq_setup,
.serdes_irq_free = mv88e6390x_serdes_irq_free,
.serdes_power = mv88e6390_serdes_power,
.serdes_get_lane = mv88e6390x_serdes_get_lane,
.serdes_irq_setup = mv88e6390_serdes_irq_setup,
.serdes_irq_free = mv88e6390_serdes_irq_free,
.gpio_ops = &mv88e6352_gpio_ops,
.avb_ops = &mv88e6390_avb_ops,
.ptp_ops = &mv88e6352_ptp_ops,
......
......@@ -400,6 +400,7 @@ struct mv88e6xxx_ops {
/* CMODE control what PHY mode the MAC will use, eg. SGMII, RGMII, etc.
* Some chips allow this to be configured on specific ports.
*/
int (*port_set_cmode_writable)(struct mv88e6xxx_chip *chip, int port);
int (*port_set_cmode)(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int (*port_get_cmode)(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
......@@ -443,6 +444,9 @@ struct mv88e6xxx_ops {
/* Power on/off a SERDES interface */
int (*serdes_power)(struct mv88e6xxx_chip *chip, int port, bool on);
/* SERDES lane mapping */
int (*serdes_get_lane)(struct mv88e6xxx_chip *chip, int port, u8 *lane);
/* SERDES interrupt handling */
int (*serdes_irq_setup)(struct mv88e6xxx_chip *chip, int port);
void (*serdes_irq_free)(struct mv88e6xxx_chip *chip, int port);
......
......@@ -392,17 +392,14 @@ phy_interface_t mv88e6390x_port_max_speed_mode(int port)
return PHY_INTERFACE_MODE_NA;
}
int mv88e6390x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode)
static int mv88e6xxx_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode)
{
int lane;
u8 lane;
u16 cmode;
u16 reg;
int err;
if (port != 9 && port != 10)
return -EOPNOTSUPP;
/* Default to a slow mode, so freeing up SERDES interfaces for
* other ports which might use them for SFPs.
*/
......@@ -411,7 +408,7 @@ int mv88e6390x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
switch (mode) {
case PHY_INTERFACE_MODE_1000BASEX:
cmode = MV88E6XXX_PORT_STS_CMODE_1000BASE_X;
cmode = MV88E6XXX_PORT_STS_CMODE_1000BASEX;
break;
case PHY_INTERFACE_MODE_SGMII:
cmode = MV88E6XXX_PORT_STS_CMODE_SGMII;
......@@ -434,18 +431,18 @@ int mv88e6390x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
if (cmode == chip->ports[port].cmode)
return 0;
lane = mv88e6390x_serdes_get_lane(chip, port);
if (lane < 0 && lane != -ENODEV)
return lane;
err = mv88e6xxx_serdes_get_lane(chip, port, &lane);
if (err && err != -ENODEV)
return err;
if (lane >= 0) {
if (err != -ENODEV) {
if (chip->ports[port].serdes_irq) {
err = mv88e6390_serdes_irq_disable(chip, port, lane);
if (err)
return err;
}
err = mv88e6390x_serdes_power(chip, port, false);
err = mv88e6390_serdes_power(chip, port, false);
if (err)
return err;
}
......@@ -466,11 +463,11 @@ int mv88e6390x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
chip->ports[port].cmode = cmode;
lane = mv88e6390x_serdes_get_lane(chip, port);
if (lane < 0)
return lane;
err = mv88e6xxx_serdes_get_lane(chip, port, &lane);
if (err)
return err;
err = mv88e6390x_serdes_power(chip, port, true);
err = mv88e6390_serdes_power(chip, port, true);
if (err)
return err;
......@@ -484,9 +481,65 @@ int mv88e6390x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
return 0;
}
int mv88e6390x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode)
{
if (port != 9 && port != 10)
return -EOPNOTSUPP;
return mv88e6xxx_port_set_cmode(chip, port, mode);
}
int mv88e6390_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode)
{
if (port != 9 && port != 10)
return -EOPNOTSUPP;
switch (mode) {
case PHY_INTERFACE_MODE_NA:
return 0;
case PHY_INTERFACE_MODE_XGMII:
case PHY_INTERFACE_MODE_XAUI:
case PHY_INTERFACE_MODE_RXAUI:
return -EINVAL;
default:
break;
}
return mv88e6xxx_port_set_cmode(chip, port, mode);
}
int mv88e6341_port_set_cmode_writable(struct mv88e6xxx_chip *chip, int port)
{
int err, addr;
u16 reg, bits;
if (port != 5)
return -EOPNOTSUPP;
addr = chip->info->port_base_addr + port;
err = mv88e6xxx_port_hidden_read(chip, 0x7, addr, 0, &reg);
if (err)
return err;
bits = MV88E6341_PORT_RESERVED_1A_FORCE_CMODE |
MV88E6341_PORT_RESERVED_1A_SGMII_AN;
if ((reg & bits) == bits)
return 0;
reg |= bits;
return mv88e6xxx_port_hidden_write(chip, 0x7, addr, 0, reg);
}
int mv88e6341_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode)
{
if (port != 5)
return -EOPNOTSUPP;
switch (mode) {
case PHY_INTERFACE_MODE_NA:
return 0;
......@@ -498,7 +551,7 @@ int mv88e6390_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
break;
}
return mv88e6390x_port_set_cmode(chip, port, mode);
return mv88e6xxx_port_set_cmode(chip, port, mode);
}
int mv88e6185_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode)
......@@ -618,7 +671,7 @@ int mv88e6352_port_link_state(struct mv88e6xxx_chip *chip, int port,
else
state->interface = PHY_INTERFACE_MODE_RGMII;
break;
case MV88E6XXX_PORT_STS_CMODE_1000BASE_X:
case MV88E6XXX_PORT_STS_CMODE_1000BASEX:
state->interface = PHY_INTERFACE_MODE_1000BASEX;
break;
case MV88E6XXX_PORT_STS_CMODE_SGMII:
......
......@@ -43,8 +43,8 @@
#define MV88E6XXX_PORT_STS_FLOW_CTL 0x0010
#define MV88E6XXX_PORT_STS_CMODE_MASK 0x000f
#define MV88E6XXX_PORT_STS_CMODE_RGMII 0x0007
#define MV88E6XXX_PORT_STS_CMODE_100BASE_X 0x0008
#define MV88E6XXX_PORT_STS_CMODE_1000BASE_X 0x0009
#define MV88E6XXX_PORT_STS_CMODE_100BASEX 0x0008
#define MV88E6XXX_PORT_STS_CMODE_1000BASEX 0x0009
#define MV88E6XXX_PORT_STS_CMODE_SGMII 0x000a
#define MV88E6XXX_PORT_STS_CMODE_2500BASEX 0x000b
#define MV88E6XXX_PORT_STS_CMODE_XAUI 0x000c
......@@ -261,14 +261,16 @@
#define MV88E6095_PORT_IEEE_PRIO_REMAP_4567 0x19
/* Offset 0x1a: Magic undocumented errata register */
#define PORT_RESERVED_1A 0x1a
#define PORT_RESERVED_1A_BUSY BIT(15)
#define PORT_RESERVED_1A_WRITE BIT(14)
#define PORT_RESERVED_1A_READ 0
#define PORT_RESERVED_1A_PORT_SHIFT 5
#define PORT_RESERVED_1A_BLOCK (0xf << 10)
#define PORT_RESERVED_1A_CTRL_PORT 4
#define PORT_RESERVED_1A_DATA_PORT 5
#define MV88E6XXX_PORT_RESERVED_1A 0x1a
#define MV88E6XXX_PORT_RESERVED_1A_BUSY 0x8000
#define MV88E6XXX_PORT_RESERVED_1A_WRITE 0x4000
#define MV88E6XXX_PORT_RESERVED_1A_READ 0x0000
#define MV88E6XXX_PORT_RESERVED_1A_PORT_SHIFT 5
#define MV88E6XXX_PORT_RESERVED_1A_BLOCK_SHIFT 10
#define MV88E6XXX_PORT_RESERVED_1A_CTRL_PORT 0x04
#define MV88E6XXX_PORT_RESERVED_1A_DATA_PORT 0x05
#define MV88E6341_PORT_RESERVED_1A_FORCE_CMODE 0x8000
#define MV88E6341_PORT_RESERVED_1A_SGMII_AN 0x2000
int mv88e6xxx_port_read(struct mv88e6xxx_chip *chip, int port, int reg,
u16 *val);
......@@ -334,6 +336,9 @@ int mv88e6097_port_pause_limit(struct mv88e6xxx_chip *chip, int port, u8 in,
u8 out);
int mv88e6390_port_pause_limit(struct mv88e6xxx_chip *chip, int port, u8 in,
u8 out);
int mv88e6341_port_set_cmode_writable(struct mv88e6xxx_chip *chip, int port);
int mv88e6341_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int mv88e6390_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int mv88e6390x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
......@@ -353,4 +358,10 @@ int mv88e6095_port_set_upstream_port(struct mv88e6xxx_chip *chip, int port,
int mv88e6xxx_port_disable_learn_limit(struct mv88e6xxx_chip *chip, int port);
int mv88e6xxx_port_disable_pri_override(struct mv88e6xxx_chip *chip, int port);
int mv88e6xxx_port_hidden_write(struct mv88e6xxx_chip *chip, int block,
int port, int reg, u16 val);
int mv88e6xxx_port_hidden_wait(struct mv88e6xxx_chip *chip);
int mv88e6xxx_port_hidden_read(struct mv88e6xxx_chip *chip, int block, int port,
int reg, u16 *val);
#endif /* _MV88E6XXX_PORT_H */
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx Switch Hidden Registers support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2019 Andrew Lunn <andrew@lunn.ch>
*/
#include <linux/bitfield.h>
#include "chip.h"
#include "port.h"
/* The mv88e6390 and mv88e6341 have some hidden registers used for debug and
* development. The errata also makes use of them.
*/
int mv88e6xxx_port_hidden_write(struct mv88e6xxx_chip *chip, int block,
int port, int reg, u16 val)
{
u16 ctrl;
int err;
err = mv88e6xxx_port_write(chip, MV88E6XXX_PORT_RESERVED_1A_DATA_PORT,
MV88E6XXX_PORT_RESERVED_1A, val);
if (err)
return err;
ctrl = MV88E6XXX_PORT_RESERVED_1A_BUSY |
MV88E6XXX_PORT_RESERVED_1A_WRITE |
block << MV88E6XXX_PORT_RESERVED_1A_BLOCK_SHIFT |
port << MV88E6XXX_PORT_RESERVED_1A_PORT_SHIFT |
reg;
return mv88e6xxx_port_write(chip, MV88E6XXX_PORT_RESERVED_1A_CTRL_PORT,
MV88E6XXX_PORT_RESERVED_1A, ctrl);
}
int mv88e6xxx_port_hidden_wait(struct mv88e6xxx_chip *chip)
{
int bit = __bf_shf(MV88E6XXX_PORT_RESERVED_1A_BUSY);
return mv88e6xxx_wait_bit(chip, MV88E6XXX_PORT_RESERVED_1A_CTRL_PORT,
MV88E6XXX_PORT_RESERVED_1A, bit, 0);
}
int mv88e6xxx_port_hidden_read(struct mv88e6xxx_chip *chip, int block, int port,
int reg, u16 *val)
{
u16 ctrl;
int err;
ctrl = MV88E6XXX_PORT_RESERVED_1A_BUSY |
MV88E6XXX_PORT_RESERVED_1A_READ |
block << MV88E6XXX_PORT_RESERVED_1A_BLOCK_SHIFT |
port << MV88E6XXX_PORT_RESERVED_1A_PORT_SHIFT |
reg;
err = mv88e6xxx_port_write(chip, MV88E6XXX_PORT_RESERVED_1A_CTRL_PORT,
MV88E6XXX_PORT_RESERVED_1A, ctrl);
if (err)
return err;
err = mv88e6xxx_port_hidden_wait(chip);
if (err)
return err;
return mv88e6xxx_port_read(chip, MV88E6XXX_PORT_RESERVED_1A_DATA_PORT,
MV88E6XXX_PORT_RESERVED_1A, val);
}
......@@ -73,8 +73,8 @@ static bool mv88e6352_port_has_serdes(struct mv88e6xxx_chip *chip, int port)
{
u8 cmode = chip->ports[port].cmode;
if ((cmode == MV88E6XXX_PORT_STS_CMODE_100BASE_X) ||
(cmode == MV88E6XXX_PORT_STS_CMODE_1000BASE_X) ||
if ((cmode == MV88E6XXX_PORT_STS_CMODE_100BASEX) ||
(cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX) ||
(cmode == MV88E6XXX_PORT_STS_CMODE_SGMII))
return true;
......@@ -286,36 +286,52 @@ void mv88e6352_serdes_irq_free(struct mv88e6xxx_chip *chip, int port)
chip->ports[port].serdes_irq = 0;
}
/* Return the SERDES lane address a port is using. Only Ports 9 and 10
* have SERDES lanes. Returns -ENODEV if a port does not have a lane.
*/
static int mv88e6390_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)
int mv88e6341_serdes_get_lane(struct mv88e6xxx_chip *chip, int port, u8 *lane)
{
u8 cmode = chip->ports[port].cmode;
if (port != 5)
return -ENODEV;
if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX) {
*lane = MV88E6341_PORT5_LANE;
return 0;
}
return -ENODEV;
}
int mv88e6390_serdes_get_lane(struct mv88e6xxx_chip *chip, int port, u8 *lane)
{
u8 cmode = chip->ports[port].cmode;
switch (port) {
case 9:
if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
return MV88E6390_PORT9_LANE0;
return -ENODEV;
cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX) {
*lane = MV88E6390_PORT9_LANE0;
return 0;
}
break;
case 10:
if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
return MV88E6390_PORT10_LANE0;
return -ENODEV;
cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX) {
*lane = MV88E6390_PORT10_LANE0;
return 0;
}
break;
default:
return -ENODEV;
break;
}
return -ENODEV;
}
/* Return the SERDES lane address a port is using. Ports 9 and 10 can
* use multiple lanes. If so, return the first lane the port uses.
* Returns -ENODEV if a port does not have a lane.
*/
int mv88e6390x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)
int mv88e6390x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port, u8 *lane)
{
u8 cmode_port9, cmode_port10, cmode_port;
......@@ -325,74 +341,98 @@ int mv88e6390x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)
switch (port) {
case 2:
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASE_X)
return MV88E6390_PORT9_LANE1;
return -ENODEV;
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX) {
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASEX) {
*lane = MV88E6390_PORT9_LANE1;
return 0;
}
}
break;
case 3:
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_RXAUI)
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASE_X)
return MV88E6390_PORT9_LANE2;
return -ENODEV;
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_RXAUI) {
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASEX) {
*lane = MV88E6390_PORT9_LANE2;
return 0;
}
}
break;
case 4:
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_RXAUI)
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASE_X)
return MV88E6390_PORT9_LANE3;
return -ENODEV;
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_RXAUI) {
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASEX) {
*lane = MV88E6390_PORT9_LANE3;
return 0;
}
}
break;
case 5:
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASE_X)
return MV88E6390_PORT10_LANE1;
return -ENODEV;
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX) {
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASEX) {
*lane = MV88E6390_PORT10_LANE1;
return 0;
}
}
break;
case 6:
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_RXAUI)
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASE_X)
return MV88E6390_PORT10_LANE2;
return -ENODEV;
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_RXAUI) {
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASEX) {
*lane = MV88E6390_PORT10_LANE2;
return 0;
}
}
break;
case 7:
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_RXAUI)
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASE_X)
return MV88E6390_PORT10_LANE3;
return -ENODEV;
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_RXAUI) {
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASEX) {
*lane = MV88E6390_PORT10_LANE3;
return 0;
}
}
break;
case 9:
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_XAUI ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_RXAUI)
return MV88E6390_PORT9_LANE0;
return -ENODEV;
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_RXAUI) {
*lane = MV88E6390_PORT9_LANE0;
return 0;
}
break;
case 10:
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_XAUI ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_RXAUI)
return MV88E6390_PORT10_LANE0;
return -ENODEV;
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_RXAUI) {
*lane = MV88E6390_PORT10_LANE0;
return 0;
}
break;
default:
return -ENODEV;
break;
}
return -ENODEV;
}
/* Set the power on/off for 10GBASE-R and 10GBASE-X4/X2 */
static int mv88e6390_serdes_power_10g(struct mv88e6xxx_chip *chip, int lane,
static int mv88e6390_serdes_power_10g(struct mv88e6xxx_chip *chip, u8 lane,
bool on)
{
u16 val, new_val;
......@@ -419,7 +459,7 @@ static int mv88e6390_serdes_power_10g(struct mv88e6xxx_chip *chip, int lane,
}
/* Set the power on/off for SGMII and 1000Base-X */
static int mv88e6390_serdes_power_sgmii(struct mv88e6xxx_chip *chip, int lane,
static int mv88e6390_serdes_power_sgmii(struct mv88e6xxx_chip *chip, u8 lane,
bool on)
{
u16 val, new_val;
......@@ -444,14 +484,22 @@ static int mv88e6390_serdes_power_sgmii(struct mv88e6xxx_chip *chip, int lane,
return err;
}
static int mv88e6390_serdes_power_lane(struct mv88e6xxx_chip *chip, int port,
int lane, bool on)
int mv88e6390_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on)
{
u8 cmode = chip->ports[port].cmode;
u8 lane;
int err;
err = mv88e6xxx_serdes_get_lane(chip, port, &lane);
if (err) {
if (err == -ENODEV)
err = 0;
return err;
}
switch (cmode) {
case MV88E6XXX_PORT_STS_CMODE_SGMII:
case MV88E6XXX_PORT_STS_CMODE_1000BASE_X:
case MV88E6XXX_PORT_STS_CMODE_1000BASEX:
case MV88E6XXX_PORT_STS_CMODE_2500BASEX:
return mv88e6390_serdes_power_sgmii(chip, lane, on);
case MV88E6XXX_PORT_STS_CMODE_XAUI:
......@@ -462,52 +510,14 @@ static int mv88e6390_serdes_power_lane(struct mv88e6xxx_chip *chip, int port,
return 0;
}
int mv88e6390_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on)
{
int lane;
lane = mv88e6390_serdes_get_lane(chip, port);
if (lane == -ENODEV)
return 0;
if (lane < 0)
return lane;
switch (port) {
case 9 ... 10:
return mv88e6390_serdes_power_lane(chip, port, lane, on);
}
return 0;
}
int mv88e6390x_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on)
{
int lane;
lane = mv88e6390x_serdes_get_lane(chip, port);
if (lane == -ENODEV)
return 0;
if (lane < 0)
return lane;
switch (port) {
case 2 ... 4:
case 5 ... 7:
case 9 ... 10:
return mv88e6390_serdes_power_lane(chip, port, lane, on);
}
return 0;
}
static void mv88e6390_serdes_irq_link_sgmii(struct mv88e6xxx_chip *chip,
int port, int lane)
int port, u8 lane)
{
u8 cmode = chip->ports[port].cmode;
struct dsa_switch *ds = chip->ds;
int duplex = DUPLEX_UNKNOWN;
int speed = SPEED_UNKNOWN;
phy_interface_t mode;
int link, err;
u16 status;
......@@ -527,7 +537,10 @@ static void mv88e6390_serdes_irq_link_sgmii(struct mv88e6xxx_chip *chip,
switch (status & MV88E6390_SGMII_PHY_STATUS_SPEED_MASK) {
case MV88E6390_SGMII_PHY_STATUS_SPEED_1000:
speed = SPEED_1000;
if (cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
speed = SPEED_2500;
else
speed = SPEED_1000;
break;
case MV88E6390_SGMII_PHY_STATUS_SPEED_100:
speed = SPEED_100;
......@@ -541,8 +554,22 @@ static void mv88e6390_serdes_irq_link_sgmii(struct mv88e6xxx_chip *chip,
}
}
switch (cmode) {
case MV88E6XXX_PORT_STS_CMODE_SGMII:
mode = PHY_INTERFACE_MODE_SGMII;
break;
case MV88E6XXX_PORT_STS_CMODE_1000BASEX:
mode = PHY_INTERFACE_MODE_1000BASEX;
break;
case MV88E6XXX_PORT_STS_CMODE_2500BASEX:
mode = PHY_INTERFACE_MODE_2500BASEX;
break;
default:
mode = PHY_INTERFACE_MODE_NA;
}
err = mv88e6xxx_port_setup_mac(chip, port, link, speed, duplex,
PAUSE_OFF, PHY_INTERFACE_MODE_NA);
PAUSE_OFF, mode);
if (err)
dev_err(chip->dev, "can't propagate PHY settings to MAC: %d\n",
err);
......@@ -551,7 +578,7 @@ static void mv88e6390_serdes_irq_link_sgmii(struct mv88e6xxx_chip *chip,
}
static int mv88e6390_serdes_irq_enable_sgmii(struct mv88e6xxx_chip *chip,
int lane)
u8 lane)
{
return mv88e6390_serdes_write(chip, lane, MDIO_MMD_PHYXS,
MV88E6390_SGMII_INT_ENABLE,
......@@ -560,21 +587,21 @@ static int mv88e6390_serdes_irq_enable_sgmii(struct mv88e6xxx_chip *chip,
}
static int mv88e6390_serdes_irq_disable_sgmii(struct mv88e6xxx_chip *chip,
int lane)
u8 lane)
{
return mv88e6390_serdes_write(chip, lane, MDIO_MMD_PHYXS,
MV88E6390_SGMII_INT_ENABLE, 0);
}
int mv88e6390_serdes_irq_enable(struct mv88e6xxx_chip *chip, int port,
int lane)
u8 lane)
{
u8 cmode = chip->ports[port].cmode;
int err = 0;
switch (cmode) {
case MV88E6XXX_PORT_STS_CMODE_SGMII:
case MV88E6XXX_PORT_STS_CMODE_1000BASE_X:
case MV88E6XXX_PORT_STS_CMODE_1000BASEX:
case MV88E6XXX_PORT_STS_CMODE_2500BASEX:
err = mv88e6390_serdes_irq_enable_sgmii(chip, lane);
}
......@@ -583,14 +610,14 @@ int mv88e6390_serdes_irq_enable(struct mv88e6xxx_chip *chip, int port,
}
int mv88e6390_serdes_irq_disable(struct mv88e6xxx_chip *chip, int port,
int lane)
u8 lane)
{
u8 cmode = chip->ports[port].cmode;
int err = 0;
switch (cmode) {
case MV88E6XXX_PORT_STS_CMODE_SGMII:
case MV88E6XXX_PORT_STS_CMODE_1000BASE_X:
case MV88E6XXX_PORT_STS_CMODE_1000BASEX:
case MV88E6XXX_PORT_STS_CMODE_2500BASEX:
err = mv88e6390_serdes_irq_disable_sgmii(chip, lane);
}
......@@ -599,7 +626,7 @@ int mv88e6390_serdes_irq_disable(struct mv88e6xxx_chip *chip, int port,
}
static int mv88e6390_serdes_irq_status_sgmii(struct mv88e6xxx_chip *chip,
int lane, u16 *status)
u8 lane, u16 *status)
{
int err;
......@@ -616,16 +643,16 @@ static irqreturn_t mv88e6390_serdes_thread_fn(int irq, void *dev_id)
irqreturn_t ret = IRQ_NONE;
u8 cmode = port->cmode;
u16 status;
int lane;
int err;
u8 lane;
lane = mv88e6390x_serdes_get_lane(chip, port->port);
mv88e6xxx_serdes_get_lane(chip, port->port, &lane);
mv88e6xxx_reg_lock(chip);
switch (cmode) {
case MV88E6XXX_PORT_STS_CMODE_SGMII:
case MV88E6XXX_PORT_STS_CMODE_1000BASE_X:
case MV88E6XXX_PORT_STS_CMODE_1000BASEX:
case MV88E6XXX_PORT_STS_CMODE_2500BASEX:
err = mv88e6390_serdes_irq_status_sgmii(chip, lane, &status);
if (err)
......@@ -642,18 +669,17 @@ static irqreturn_t mv88e6390_serdes_thread_fn(int irq, void *dev_id)
return ret;
}
int mv88e6390x_serdes_irq_setup(struct mv88e6xxx_chip *chip, int port)
int mv88e6390_serdes_irq_setup(struct mv88e6xxx_chip *chip, int port)
{
int lane;
int err;
u8 lane;
lane = mv88e6390x_serdes_get_lane(chip, port);
if (lane == -ENODEV)
return 0;
if (lane < 0)
return lane;
err = mv88e6xxx_serdes_get_lane(chip, port, &lane);
if (err) {
if (err == -ENODEV)
err = 0;
return err;
}
chip->ports[port].serdes_irq = irq_find_mapping(chip->g2_irq.domain,
port);
......@@ -682,23 +708,18 @@ int mv88e6390x_serdes_irq_setup(struct mv88e6xxx_chip *chip, int port)
return mv88e6390_serdes_irq_enable(chip, port, lane);
}
int mv88e6390_serdes_irq_setup(struct mv88e6xxx_chip *chip, int port)
{
if (port < 9)
return 0;
return mv88e6390x_serdes_irq_setup(chip, port);
}
void mv88e6390x_serdes_irq_free(struct mv88e6xxx_chip *chip, int port)
void mv88e6390_serdes_irq_free(struct mv88e6xxx_chip *chip, int port)
{
int lane = mv88e6390x_serdes_get_lane(chip, port);
if (lane == -ENODEV)
return;
int err;
u8 lane;
if (lane < 0)
err = mv88e6xxx_serdes_get_lane(chip, port, &lane);
if (err) {
if (err != -ENODEV)
dev_err(chip->dev, "Unable to free SERDES irq: %d\n",
err);
return;
}
mv88e6390_serdes_irq_disable(chip, port, lane);
......@@ -711,27 +732,3 @@ void mv88e6390x_serdes_irq_free(struct mv88e6xxx_chip *chip, int port)
chip->ports[port].serdes_irq = 0;
}
void mv88e6390_serdes_irq_free(struct mv88e6xxx_chip *chip, int port)
{
if (port < 9)
return;
mv88e6390x_serdes_irq_free(chip, port);
}
int mv88e6341_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on)
{
u8 cmode = chip->ports[port].cmode;
if (port != 5)
return 0;
if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
return mv88e6390_serdes_power_sgmii(chip, MV88E6341_ADDR_SERDES,
on);
return 0;
}
......@@ -28,7 +28,7 @@
#define MV88E6352_SERDES_INT_STATUS 0x13
#define MV88E6341_ADDR_SERDES 0x15
#define MV88E6341_PORT5_LANE 0x15
#define MV88E6390_PORT9_LANE0 0x09
#define MV88E6390_PORT9_LANE1 0x12
......@@ -74,24 +74,35 @@
#define MV88E6390_SGMII_PHY_STATUS_SPD_DPL_VALID BIT(11)
#define MV88E6390_SGMII_PHY_STATUS_LINK BIT(10)
int mv88e6390x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6341_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on);
/* Put the SERDES lane address a port is using into *lane. If a port has
* multiple lanes, should put the first lane the port is using. If a port does
* not have a lane, return -ENODEV.
*/
static inline int mv88e6xxx_serdes_get_lane(struct mv88e6xxx_chip *chip,
int port, u8 *lane)
{
if (!chip->info->ops->serdes_get_lane)
return -EOPNOTSUPP;
return chip->info->ops->serdes_get_lane(chip, port, lane);
}
int mv88e6341_serdes_get_lane(struct mv88e6xxx_chip *chip, int port, u8 *lane);
int mv88e6390_serdes_get_lane(struct mv88e6xxx_chip *chip, int port, u8 *lane);
int mv88e6390x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port, u8 *lane);
int mv88e6352_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on);
int mv88e6390_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on);
int mv88e6390x_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on);
int mv88e6390_serdes_irq_setup(struct mv88e6xxx_chip *chip, int port);
void mv88e6390_serdes_irq_free(struct mv88e6xxx_chip *chip, int port);
int mv88e6390x_serdes_irq_setup(struct mv88e6xxx_chip *chip, int port);
void mv88e6390x_serdes_irq_free(struct mv88e6xxx_chip *chip, int port);
int mv88e6352_serdes_get_sset_count(struct mv88e6xxx_chip *chip, int port);
int mv88e6352_serdes_get_strings(struct mv88e6xxx_chip *chip,
int port, uint8_t *data);
int mv88e6352_serdes_get_stats(struct mv88e6xxx_chip *chip, int port,
uint64_t *data);
int mv88e6390_serdes_irq_enable(struct mv88e6xxx_chip *chip, int port,
int lane);
u8 lane);
int mv88e6390_serdes_irq_disable(struct mv88e6xxx_chip *chip, int port,
int lane);
u8 lane);
int mv88e6352_serdes_irq_setup(struct mv88e6xxx_chip *chip, int port);
void mv88e6352_serdes_irq_free(struct mv88e6xxx_chip *chip, int port);
......
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