mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2025-01-18 18:36:18 +07:00
2d2e1dd299
The ports CMODE indicates the type of link between the MAC and the PHY. It is used often in the SERDES code. Rather than read it each time, cache its value. Signed-off-by: Andrew Lunn <andrew@lunn.ch> Signed-off-by: David S. Miller <davem@davemloft.net>
417 lines
11 KiB
C
417 lines
11 KiB
C
/*
|
|
* Marvell 88E6xxx SERDES manipulation, via SMI bus
|
|
*
|
|
* Copyright (c) 2008 Marvell Semiconductor
|
|
*
|
|
* Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
|
|
*
|
|
* 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/mii.h>
|
|
|
|
#include "chip.h"
|
|
#include "global2.h"
|
|
#include "phy.h"
|
|
#include "port.h"
|
|
#include "serdes.h"
|
|
|
|
static int mv88e6352_serdes_read(struct mv88e6xxx_chip *chip, int reg,
|
|
u16 *val)
|
|
{
|
|
return mv88e6xxx_phy_page_read(chip, MV88E6352_ADDR_SERDES,
|
|
MV88E6352_SERDES_PAGE_FIBER,
|
|
reg, val);
|
|
}
|
|
|
|
static int mv88e6352_serdes_write(struct mv88e6xxx_chip *chip, int reg,
|
|
u16 val)
|
|
{
|
|
return mv88e6xxx_phy_page_write(chip, MV88E6352_ADDR_SERDES,
|
|
MV88E6352_SERDES_PAGE_FIBER,
|
|
reg, val);
|
|
}
|
|
|
|
static int mv88e6390_serdes_read(struct mv88e6xxx_chip *chip,
|
|
int lane, int device, int reg, u16 *val)
|
|
{
|
|
int reg_c45 = MII_ADDR_C45 | device << 16 | reg;
|
|
|
|
return mv88e6xxx_phy_read(chip, lane, reg_c45, val);
|
|
}
|
|
|
|
static int mv88e6390_serdes_write(struct mv88e6xxx_chip *chip,
|
|
int lane, int device, int reg, u16 val)
|
|
{
|
|
int reg_c45 = MII_ADDR_C45 | device << 16 | reg;
|
|
|
|
return mv88e6xxx_phy_write(chip, lane, reg_c45, val);
|
|
}
|
|
|
|
static int mv88e6352_serdes_power_set(struct mv88e6xxx_chip *chip, bool on)
|
|
{
|
|
u16 val, new_val;
|
|
int err;
|
|
|
|
err = mv88e6352_serdes_read(chip, MII_BMCR, &val);
|
|
if (err)
|
|
return err;
|
|
|
|
if (on)
|
|
new_val = val & ~BMCR_PDOWN;
|
|
else
|
|
new_val = val | BMCR_PDOWN;
|
|
|
|
if (val != new_val)
|
|
err = mv88e6352_serdes_write(chip, MII_BMCR, new_val);
|
|
|
|
return err;
|
|
}
|
|
|
|
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) ||
|
|
(cmode == MV88E6XXX_PORT_STS_CMODE_SGMII))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int mv88e6352_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on)
|
|
{
|
|
int err;
|
|
|
|
if (mv88e6352_port_has_serdes(chip, port)) {
|
|
err = mv88e6352_serdes_power_set(chip, on);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct mv88e6352_serdes_hw_stat {
|
|
char string[ETH_GSTRING_LEN];
|
|
int sizeof_stat;
|
|
int reg;
|
|
};
|
|
|
|
static struct mv88e6352_serdes_hw_stat mv88e6352_serdes_hw_stats[] = {
|
|
{ "serdes_fibre_rx_error", 16, 21 },
|
|
{ "serdes_PRBS_error", 32, 24 },
|
|
};
|
|
|
|
int mv88e6352_serdes_get_sset_count(struct mv88e6xxx_chip *chip, int port)
|
|
{
|
|
if (mv88e6352_port_has_serdes(chip, port))
|
|
return ARRAY_SIZE(mv88e6352_serdes_hw_stats);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mv88e6352_serdes_get_strings(struct mv88e6xxx_chip *chip,
|
|
int port, uint8_t *data)
|
|
{
|
|
struct mv88e6352_serdes_hw_stat *stat;
|
|
int i;
|
|
|
|
if (!mv88e6352_port_has_serdes(chip, port))
|
|
return 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mv88e6352_serdes_hw_stats); i++) {
|
|
stat = &mv88e6352_serdes_hw_stats[i];
|
|
memcpy(data + i * ETH_GSTRING_LEN, stat->string,
|
|
ETH_GSTRING_LEN);
|
|
}
|
|
return ARRAY_SIZE(mv88e6352_serdes_hw_stats);
|
|
}
|
|
|
|
static uint64_t mv88e6352_serdes_get_stat(struct mv88e6xxx_chip *chip,
|
|
struct mv88e6352_serdes_hw_stat *stat)
|
|
{
|
|
u64 val = 0;
|
|
u16 reg;
|
|
int err;
|
|
|
|
err = mv88e6352_serdes_read(chip, stat->reg, ®);
|
|
if (err) {
|
|
dev_err(chip->dev, "failed to read statistic\n");
|
|
return 0;
|
|
}
|
|
|
|
val = reg;
|
|
|
|
if (stat->sizeof_stat == 32) {
|
|
err = mv88e6352_serdes_read(chip, stat->reg + 1, ®);
|
|
if (err) {
|
|
dev_err(chip->dev, "failed to read statistic\n");
|
|
return 0;
|
|
}
|
|
val = val << 16 | reg;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
int mv88e6352_serdes_get_stats(struct mv88e6xxx_chip *chip, int port,
|
|
uint64_t *data)
|
|
{
|
|
struct mv88e6xxx_port *mv88e6xxx_port = &chip->ports[port];
|
|
struct mv88e6352_serdes_hw_stat *stat;
|
|
u64 value;
|
|
int i;
|
|
|
|
if (!mv88e6352_port_has_serdes(chip, port))
|
|
return 0;
|
|
|
|
BUILD_BUG_ON(ARRAY_SIZE(mv88e6352_serdes_hw_stats) >
|
|
ARRAY_SIZE(mv88e6xxx_port->serdes_stats));
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mv88e6352_serdes_hw_stats); i++) {
|
|
stat = &mv88e6352_serdes_hw_stats[i];
|
|
value = mv88e6352_serdes_get_stat(chip, stat);
|
|
mv88e6xxx_port->serdes_stats[i] += value;
|
|
data[i] = mv88e6xxx_port->serdes_stats[i];
|
|
}
|
|
|
|
return ARRAY_SIZE(mv88e6352_serdes_hw_stats);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
u8 cmode = chip->ports[port].cmode;
|
|
|
|
switch (port) {
|
|
case 9:
|
|
if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
|
|
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII ||
|
|
cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
|
|
return MV88E6390_PORT9_LANE0;
|
|
return -ENODEV;
|
|
case 10:
|
|
if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
|
|
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII ||
|
|
cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
|
|
return MV88E6390_PORT10_LANE0;
|
|
return -ENODEV;
|
|
default:
|
|
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.
|
|
*/
|
|
static int mv88e6390x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)
|
|
{
|
|
u8 cmode_port9, cmode_port10, cmode_port;
|
|
|
|
cmode_port9 = chip->ports[9].cmode;
|
|
cmode_port10 = chip->ports[10].cmode;
|
|
cmode_port = chip->ports[port].cmode;
|
|
|
|
switch (port) {
|
|
case 2:
|
|
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
|
|
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;
|
|
case 3:
|
|
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
|
|
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;
|
|
case 4:
|
|
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
|
|
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;
|
|
case 5:
|
|
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
|
|
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;
|
|
case 6:
|
|
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
|
|
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;
|
|
case 7:
|
|
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
|
|
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;
|
|
case 9:
|
|
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
|
|
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;
|
|
case 10:
|
|
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASE_X ||
|
|
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;
|
|
default:
|
|
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,
|
|
bool on)
|
|
{
|
|
u16 val, new_val;
|
|
int err;
|
|
|
|
err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
|
|
MV88E6390_PCS_CONTROL_1, &val);
|
|
|
|
if (err)
|
|
return err;
|
|
|
|
if (on)
|
|
new_val = val & ~(MV88E6390_PCS_CONTROL_1_RESET |
|
|
MV88E6390_PCS_CONTROL_1_LOOPBACK |
|
|
MV88E6390_PCS_CONTROL_1_PDOWN);
|
|
else
|
|
new_val = val | MV88E6390_PCS_CONTROL_1_PDOWN;
|
|
|
|
if (val != new_val)
|
|
err = mv88e6390_serdes_write(chip, lane, MDIO_MMD_PHYXS,
|
|
MV88E6390_PCS_CONTROL_1, new_val);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Set the power on/off for SGMII and 1000Base-X */
|
|
static int mv88e6390_serdes_power_sgmii(struct mv88e6xxx_chip *chip, int lane,
|
|
bool on)
|
|
{
|
|
u16 val, new_val;
|
|
int err;
|
|
|
|
err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
|
|
MV88E6390_SGMII_CONTROL, &val);
|
|
if (err)
|
|
return err;
|
|
|
|
if (on)
|
|
new_val = val & ~(MV88E6390_SGMII_CONTROL_RESET |
|
|
MV88E6390_SGMII_CONTROL_LOOPBACK |
|
|
MV88E6390_SGMII_CONTROL_PDOWN);
|
|
else
|
|
new_val = val | MV88E6390_SGMII_CONTROL_PDOWN;
|
|
|
|
if (val != new_val)
|
|
err = mv88e6390_serdes_write(chip, lane, MDIO_MMD_PHYXS,
|
|
MV88E6390_SGMII_CONTROL, new_val);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int mv88e6390_serdes_power_lane(struct mv88e6xxx_chip *chip, int port,
|
|
int lane, bool on)
|
|
{
|
|
u8 cmode = chip->ports[port].cmode;
|
|
|
|
switch (cmode) {
|
|
case MV88E6XXX_PORT_STS_CMODE_SGMII:
|
|
case MV88E6XXX_PORT_STS_CMODE_1000BASE_X:
|
|
case MV88E6XXX_PORT_STS_CMODE_2500BASEX:
|
|
return mv88e6390_serdes_power_sgmii(chip, lane, on);
|
|
case MV88E6XXX_PORT_STS_CMODE_XAUI:
|
|
case MV88E6XXX_PORT_STS_CMODE_RXAUI:
|
|
return mv88e6390_serdes_power_10g(chip, lane, on);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|