Commit a5a6858b authored by Russell King's avatar Russell King Committed by David S. Miller
Browse files

net: dsa: mv88e6xxx: extend phylink to Serdes PHYs



Extend the mv88e6xxx phylink implementation down to Serdes PHYs, which
handle the PCS layer of such links.

- Implement phylink PCS link state reading, so that we can provide
  ethtool with the linkmodes and link speed in the expected manner.
  Note: this will only be called for in-band negotiation, which is
  only supported by the serdes interfaces.
- Implement phylink PCS configuration, so that the in-band AN and
  advertisement can be configured.
- Implement phylink PCS negotiation restart, so that the in-band AN
  can be restarted.
- Implement phylink PCS link up, so that when operating out-of-band,
  the Serdes can be configured for the appropriate fixed speed mode.

Signed-off-by: default avatarRussell King <rmk+kernel@armlinux.org.uk>
Reviewed-by: default avatarAndrew Lunn <andrew@lunn.ch>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 64d47d50
Loading
Loading
Loading
Loading
+153 −21
Original line number Diff line number Diff line
@@ -419,8 +419,8 @@ static int mv88e6xxx_port_config_interface(struct mv88e6xxx_chip *chip,
	return 0;
}

int mv88e6xxx_port_setup_mac(struct mv88e6xxx_chip *chip, int port, int link,
			     int speed, int duplex, int pause,
static int mv88e6xxx_port_setup_mac(struct mv88e6xxx_chip *chip, int port,
				    int link, int speed, int duplex, int pause,
				    phy_interface_t mode)
{
	struct phylink_link_state state;
@@ -488,6 +488,81 @@ static int mv88e6xxx_phy_is_internal(struct dsa_switch *ds, int port)
	return port < chip->info->num_internal_phys;
}

static int mv88e6xxx_serdes_pcs_get_state(struct dsa_switch *ds, int port,
					  struct phylink_link_state *state)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	u8 lane;
	int err;

	mv88e6xxx_reg_lock(chip);
	lane = mv88e6xxx_serdes_get_lane(chip, port);
	if (lane && chip->info->ops->serdes_pcs_get_state)
		err = chip->info->ops->serdes_pcs_get_state(chip, port, lane,
							    state);
	else
		err = -EOPNOTSUPP;
	mv88e6xxx_reg_unlock(chip);

	return err;
}

static int mv88e6xxx_serdes_pcs_config(struct mv88e6xxx_chip *chip, int port,
				       unsigned int mode,
				       phy_interface_t interface,
				       const unsigned long *advertise)
{
	const struct mv88e6xxx_ops *ops = chip->info->ops;
	u8 lane;

	if (ops->serdes_pcs_config) {
		lane = mv88e6xxx_serdes_get_lane(chip, port);
		if (lane)
			return ops->serdes_pcs_config(chip, port, lane, mode,
						      interface, advertise);
	}

	return 0;
}

static void mv88e6xxx_serdes_pcs_an_restart(struct dsa_switch *ds, int port)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	const struct mv88e6xxx_ops *ops;
	int err = 0;
	u8 lane;

	ops = chip->info->ops;

	if (ops->serdes_pcs_an_restart) {
		mv88e6xxx_reg_lock(chip);
		lane = mv88e6xxx_serdes_get_lane(chip, port);
		if (lane)
			err = ops->serdes_pcs_an_restart(chip, port, lane);
		mv88e6xxx_reg_unlock(chip);

		if (err)
			dev_err(ds->dev, "p%d: failed to restart AN\n", port);
	}
}

static int mv88e6xxx_serdes_pcs_link_up(struct mv88e6xxx_chip *chip, int port,
					unsigned int mode,
					int speed, int duplex)
{
	const struct mv88e6xxx_ops *ops = chip->info->ops;
	u8 lane;

	if (!phylink_autoneg_inband(mode) && ops->serdes_pcs_link_up) {
		lane = mv88e6xxx_serdes_get_lane(chip, port);
		if (lane)
			return ops->serdes_pcs_link_up(chip, port, lane,
						       speed, duplex);
	}

	return 0;
}

static void mv88e6065_phylink_validate(struct mv88e6xxx_chip *chip, int port,
				       unsigned long *mask,
				       struct phylink_link_state *state)
@@ -592,22 +667,6 @@ static void mv88e6xxx_validate(struct dsa_switch *ds, int port,
	phylink_helper_basex_speed(state);
}

static int mv88e6xxx_link_state(struct dsa_switch *ds, int port,
				struct phylink_link_state *state)
{
	struct mv88e6xxx_chip *chip = ds->priv;
	int err;

	mv88e6xxx_reg_lock(chip);
	if (chip->info->ops->port_link_state)
		err = chip->info->ops->port_link_state(chip, port, state);
	else
		err = -EOPNOTSUPP;
	mv88e6xxx_reg_unlock(chip);

	return err;
}

static void mv88e6xxx_mac_config(struct dsa_switch *ds, int port,
				 unsigned int mode,
				 const struct phylink_link_state *state)
@@ -629,6 +688,18 @@ static void mv88e6xxx_mac_config(struct dsa_switch *ds, int port,
	 * gets in the way.
	 */
	err = mv88e6xxx_port_config_interface(chip, port, state->interface);
	if (err && err != -EOPNOTSUPP)
		goto err_unlock;

	err = mv88e6xxx_serdes_pcs_config(chip, port, mode, state->interface,
					  state->advertising);
	/* FIXME: we should restart negotiation if something changed - which
	 * is something we get if we convert to using phylinks PCS operations.
	 */
	if (err > 0)
		err = 0;

err_unlock:
	mv88e6xxx_reg_unlock(chip);

	if (err && err != -EOPNOTSUPP)
@@ -683,9 +754,14 @@ static void mv88e6xxx_mac_link_up(struct dsa_switch *ds, int port,
		/* FIXME: for an automedia port, should we force the link
		 * down here - what if the link comes up due to "other" media
		 * while we're bringing the port up, how is the exclusivity
		 * handled in the Marvell hardware? E.g. port 4 on 88E6532
		 * handled in the Marvell hardware? E.g. port 2 on 88E6390
		 * shared between internal PHY and Serdes.
		 */
		err = mv88e6xxx_serdes_pcs_link_up(chip, port, mode, speed,
						   duplex);
		if (err)
			goto error;

		if (ops->port_set_speed) {
			err = ops->port_set_speed(chip, port, speed);
			if (err && err != -EOPNOTSUPP)
@@ -3557,6 +3633,11 @@ static const struct mv88e6xxx_ops mv88e6141_ops = {
	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
	.serdes_power = mv88e6390_serdes_power,
	.serdes_get_lane = mv88e6341_serdes_get_lane,
	/* Check status register pause & lpa register */
	.serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6390_serdes_pcs_config,
	.serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart,
	.serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up,
	.serdes_irq_mapping = mv88e6390_serdes_irq_mapping,
	.serdes_irq_enable = mv88e6390_serdes_irq_enable,
	.serdes_irq_status = mv88e6390_serdes_irq_status,
@@ -3729,6 +3810,10 @@ static const struct mv88e6xxx_ops mv88e6172_ops = {
	.vtu_getnext = mv88e6352_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
	.serdes_get_lane = mv88e6352_serdes_get_lane,
	.serdes_pcs_get_state = mv88e6352_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6352_serdes_pcs_config,
	.serdes_pcs_an_restart = mv88e6352_serdes_pcs_an_restart,
	.serdes_pcs_link_up = mv88e6352_serdes_pcs_link_up,
	.serdes_power = mv88e6352_serdes_power,
	.serdes_get_regs_len = mv88e6352_serdes_get_regs_len,
	.serdes_get_regs = mv88e6352_serdes_get_regs,
@@ -3822,6 +3907,10 @@ static const struct mv88e6xxx_ops mv88e6176_ops = {
	.vtu_getnext = mv88e6352_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
	.serdes_get_lane = mv88e6352_serdes_get_lane,
	.serdes_pcs_get_state = mv88e6352_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6352_serdes_pcs_config,
	.serdes_pcs_an_restart = mv88e6352_serdes_pcs_an_restart,
	.serdes_pcs_link_up = mv88e6352_serdes_pcs_link_up,
	.serdes_power = mv88e6352_serdes_power,
	.serdes_irq_mapping = mv88e6352_serdes_irq_mapping,
	.serdes_irq_enable = mv88e6352_serdes_irq_enable,
@@ -3912,6 +4001,11 @@ static const struct mv88e6xxx_ops mv88e6190_ops = {
	.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
	.serdes_power = mv88e6390_serdes_power,
	.serdes_get_lane = mv88e6390_serdes_get_lane,
	/* Check status register pause & lpa register */
	.serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6390_serdes_pcs_config,
	.serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart,
	.serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up,
	.serdes_irq_mapping = mv88e6390_serdes_irq_mapping,
	.serdes_irq_enable = mv88e6390_serdes_irq_enable,
	.serdes_irq_status = mv88e6390_serdes_irq_status,
@@ -3968,6 +4062,11 @@ static const struct mv88e6xxx_ops mv88e6190x_ops = {
	.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
	.serdes_power = mv88e6390_serdes_power,
	.serdes_get_lane = mv88e6390x_serdes_get_lane,
	/* Check status register pause & lpa register */
	.serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6390_serdes_pcs_config,
	.serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart,
	.serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up,
	.serdes_irq_mapping = mv88e6390_serdes_irq_mapping,
	.serdes_irq_enable = mv88e6390_serdes_irq_enable,
	.serdes_irq_status = mv88e6390_serdes_irq_status,
@@ -4023,6 +4122,11 @@ static const struct mv88e6xxx_ops mv88e6191_ops = {
	.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
	.serdes_power = mv88e6390_serdes_power,
	.serdes_get_lane = mv88e6390_serdes_get_lane,
	/* Check status register pause & lpa register */
	.serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6390_serdes_pcs_config,
	.serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart,
	.serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up,
	.serdes_irq_mapping = mv88e6390_serdes_irq_mapping,
	.serdes_irq_enable = mv88e6390_serdes_irq_enable,
	.serdes_irq_status = mv88e6390_serdes_irq_status,
@@ -4080,6 +4184,10 @@ static const struct mv88e6xxx_ops mv88e6240_ops = {
	.vtu_getnext = mv88e6352_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
	.serdes_get_lane = mv88e6352_serdes_get_lane,
	.serdes_pcs_get_state = mv88e6352_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6352_serdes_pcs_config,
	.serdes_pcs_an_restart = mv88e6352_serdes_pcs_an_restart,
	.serdes_pcs_link_up = mv88e6352_serdes_pcs_link_up,
	.serdes_power = mv88e6352_serdes_power,
	.serdes_irq_mapping = mv88e6352_serdes_irq_mapping,
	.serdes_irq_enable = mv88e6352_serdes_irq_enable,
@@ -4176,6 +4284,11 @@ static const struct mv88e6xxx_ops mv88e6290_ops = {
	.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
	.serdes_power = mv88e6390_serdes_power,
	.serdes_get_lane = mv88e6390_serdes_get_lane,
	/* Check status register pause & lpa register */
	.serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6390_serdes_pcs_config,
	.serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart,
	.serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up,
	.serdes_irq_mapping = mv88e6390_serdes_irq_mapping,
	.serdes_irq_enable = mv88e6390_serdes_irq_enable,
	.serdes_irq_status = mv88e6390_serdes_irq_status,
@@ -4319,6 +4432,11 @@ static const struct mv88e6xxx_ops mv88e6341_ops = {
	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
	.serdes_power = mv88e6390_serdes_power,
	.serdes_get_lane = mv88e6341_serdes_get_lane,
	/* Check status register pause & lpa register */
	.serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6390_serdes_pcs_config,
	.serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart,
	.serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up,
	.serdes_irq_mapping = mv88e6390_serdes_irq_mapping,
	.serdes_irq_enable = mv88e6390_serdes_irq_enable,
	.serdes_irq_status = mv88e6390_serdes_irq_status,
@@ -4458,6 +4576,10 @@ static const struct mv88e6xxx_ops mv88e6352_ops = {
	.vtu_getnext = mv88e6352_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
	.serdes_get_lane = mv88e6352_serdes_get_lane,
	.serdes_pcs_get_state = mv88e6352_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6352_serdes_pcs_config,
	.serdes_pcs_an_restart = mv88e6352_serdes_pcs_an_restart,
	.serdes_pcs_link_up = mv88e6352_serdes_pcs_link_up,
	.serdes_power = mv88e6352_serdes_power,
	.serdes_irq_mapping = mv88e6352_serdes_irq_mapping,
	.serdes_irq_enable = mv88e6352_serdes_irq_enable,
@@ -4519,6 +4641,11 @@ static const struct mv88e6xxx_ops mv88e6390_ops = {
	.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
	.serdes_power = mv88e6390_serdes_power,
	.serdes_get_lane = mv88e6390_serdes_get_lane,
	/* Check status register pause & lpa register */
	.serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6390_serdes_pcs_config,
	.serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart,
	.serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up,
	.serdes_irq_mapping = mv88e6390_serdes_irq_mapping,
	.serdes_irq_enable = mv88e6390_serdes_irq_enable,
	.serdes_irq_status = mv88e6390_serdes_irq_status,
@@ -4579,6 +4706,10 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = {
	.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
	.serdes_power = mv88e6390_serdes_power,
	.serdes_get_lane = mv88e6390x_serdes_get_lane,
	.serdes_pcs_get_state = mv88e6390_serdes_pcs_get_state,
	.serdes_pcs_config = mv88e6390_serdes_pcs_config,
	.serdes_pcs_an_restart = mv88e6390_serdes_pcs_an_restart,
	.serdes_pcs_link_up = mv88e6390_serdes_pcs_link_up,
	.serdes_irq_mapping = mv88e6390_serdes_irq_mapping,
	.serdes_irq_enable = mv88e6390_serdes_irq_enable,
	.serdes_irq_status = mv88e6390_serdes_irq_status,
@@ -5457,8 +5588,9 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
	.setup			= mv88e6xxx_setup,
	.teardown		= mv88e6xxx_teardown,
	.phylink_validate	= mv88e6xxx_validate,
	.phylink_mac_link_state	= mv88e6xxx_link_state,
	.phylink_mac_link_state	= mv88e6xxx_serdes_pcs_get_state,
	.phylink_mac_config	= mv88e6xxx_mac_config,
	.phylink_mac_an_restart	= mv88e6xxx_serdes_pcs_an_restart,
	.phylink_mac_link_down	= mv88e6xxx_mac_link_down,
	.phylink_mac_link_up	= mv88e6xxx_mac_link_up,
	.get_strings		= mv88e6xxx_get_strings,
+11 −3
Original line number Diff line number Diff line
@@ -502,6 +502,17 @@ struct mv88e6xxx_ops {
	/* SERDES lane mapping */
	u8 (*serdes_get_lane)(struct mv88e6xxx_chip *chip, int port);

	int (*serdes_pcs_get_state)(struct mv88e6xxx_chip *chip, int port,
				    u8 lane, struct phylink_link_state *state);
	int (*serdes_pcs_config)(struct mv88e6xxx_chip *chip, int port,
				 u8 lane, unsigned int mode,
				 phy_interface_t interface,
				 const unsigned long *advertise);
	int (*serdes_pcs_an_restart)(struct mv88e6xxx_chip *chip, int port,
				     u8 lane);
	int (*serdes_pcs_link_up)(struct mv88e6xxx_chip *chip, int port,
				  u8 lane, int speed, int duplex);

	/* SERDES interrupt handling */
	unsigned int (*serdes_irq_mapping)(struct mv88e6xxx_chip *chip,
					   int port);
@@ -669,9 +680,6 @@ int mv88e6xxx_wait_mask(struct mv88e6xxx_chip *chip, int addr, int reg,
			u16 mask, u16 val);
int mv88e6xxx_wait_bit(struct mv88e6xxx_chip *chip, int addr, int reg,
		       int bit, int val);
int mv88e6xxx_port_setup_mac(struct mv88e6xxx_chip *chip, int port, int link,
			     int speed, int duplex, int pause,
			     phy_interface_t mode);
struct mii_bus *mv88e6xxx_default_mdio_bus(struct mv88e6xxx_chip *chip);

static inline void mv88e6xxx_reg_lock(struct mv88e6xxx_chip *chip)
+292 −50
Original line number Diff line number Diff line
@@ -49,6 +49,52 @@ static int mv88e6390_serdes_write(struct mv88e6xxx_chip *chip,
	return mv88e6xxx_phy_write(chip, lane, reg_c45, val);
}

static int mv88e6xxx_serdes_pcs_get_state(struct mv88e6xxx_chip *chip,
					  u16 status, u16 lpa,
					  struct phylink_link_state *state)
{
	if (status & MV88E6390_SGMII_PHY_STATUS_SPD_DPL_VALID) {
		state->link = !!(status & MV88E6390_SGMII_PHY_STATUS_LINK);
		state->duplex = status &
				MV88E6390_SGMII_PHY_STATUS_DUPLEX_FULL ?
			                         DUPLEX_FULL : DUPLEX_HALF;

		if (status & MV88E6390_SGMII_PHY_STATUS_TX_PAUSE)
			state->pause |= MLO_PAUSE_TX;
		if (status & MV88E6390_SGMII_PHY_STATUS_RX_PAUSE)
			state->pause |= MLO_PAUSE_RX;

		switch (status & MV88E6390_SGMII_PHY_STATUS_SPEED_MASK) {
		case MV88E6390_SGMII_PHY_STATUS_SPEED_1000:
			if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
				state->speed = SPEED_2500;
			else
				state->speed = SPEED_1000;
			break;
		case MV88E6390_SGMII_PHY_STATUS_SPEED_100:
			state->speed = SPEED_100;
			break;
		case MV88E6390_SGMII_PHY_STATUS_SPEED_10:
			state->speed = SPEED_10;
			break;
		default:
			dev_err(chip->dev, "invalid PHY speed\n");
			return -EINVAL;
		}
	} else {
		state->link = false;
	}

	if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
		mii_lpa_mod_linkmode_x(state->lp_advertising, lpa,
				       ETHTOOL_LINK_MODE_2500baseX_Full_BIT);
	else if (state->interface == PHY_INTERFACE_MODE_1000BASEX)
		mii_lpa_mod_linkmode_x(state->lp_advertising, lpa,
				       ETHTOOL_LINK_MODE_1000baseX_Full_BIT);

	return 0;
}

int mv88e6352_serdes_power(struct mv88e6xxx_chip *chip, int port, u8 lane,
			   bool up)
{
@@ -70,6 +116,120 @@ int mv88e6352_serdes_power(struct mv88e6xxx_chip *chip, int port, u8 lane,
	return err;
}

int mv88e6352_serdes_pcs_config(struct mv88e6xxx_chip *chip, int port,
				u8 lane, unsigned int mode,
				phy_interface_t interface,
				const unsigned long *advertise)
{
	u16 adv, bmcr, val;
	bool changed;
	int err;

	switch (interface) {
	case PHY_INTERFACE_MODE_SGMII:
		adv = 0x0001;
		break;

	case PHY_INTERFACE_MODE_1000BASEX:
		adv = linkmode_adv_to_mii_adv_x(advertise,
					ETHTOOL_LINK_MODE_1000baseX_Full_BIT);
		break;

	default:
		return 0;
	}

	err = mv88e6352_serdes_read(chip, MII_ADVERTISE, &val);
	if (err)
		return err;

	changed = val != adv;
	if (changed) {
		err = mv88e6352_serdes_write(chip, MII_ADVERTISE, adv);
		if (err)
			return err;
	}

	err = mv88e6352_serdes_read(chip, MII_BMCR, &val);
	if (err)
		return err;

	if (phylink_autoneg_inband(mode))
		bmcr = val | BMCR_ANENABLE;
	else
		bmcr = val & ~BMCR_ANENABLE;

	if (bmcr == val)
		return changed;

	return mv88e6352_serdes_write(chip, MII_BMCR, bmcr);
}

int mv88e6352_serdes_pcs_get_state(struct mv88e6xxx_chip *chip, int port,
				   u8 lane, struct phylink_link_state *state)
{
	u16 lpa, status;
	int err;

	err = mv88e6352_serdes_read(chip, 0x11, &status);
	if (err) {
		dev_err(chip->dev, "can't read Serdes PHY status: %d\n", err);
		return err;
	}

	err = mv88e6352_serdes_read(chip, MII_LPA, &lpa);
	if (err) {
		dev_err(chip->dev, "can't read Serdes PHY LPA: %d\n", err);
		return err;
	}

	return mv88e6xxx_serdes_pcs_get_state(chip, status, lpa, state);
}

int mv88e6352_serdes_pcs_an_restart(struct mv88e6xxx_chip *chip, int port,
				    u8 lane)
{
	u16 bmcr;
	int err;

	err = mv88e6352_serdes_read(chip, MII_BMCR, &bmcr);
	if (err)
		return err;

	return mv88e6352_serdes_write(chip, MII_BMCR, bmcr | BMCR_ANRESTART);
}

int mv88e6352_serdes_pcs_link_up(struct mv88e6xxx_chip *chip, int port,
				 u8 lane, int speed, int duplex)
{
	u16 val, bmcr;
	int err;

	err = mv88e6352_serdes_read(chip, MII_BMCR, &val);
	if (err)
		return err;

	bmcr = val & ~(BMCR_SPEED100 | BMCR_FULLDPLX | BMCR_SPEED1000);
	switch (speed) {
	case SPEED_1000:
		bmcr |= BMCR_SPEED1000;
		break;
	case SPEED_100:
		bmcr |= BMCR_SPEED100;
		break;
	case SPEED_10:
		break;
	}

	if (duplex == DUPLEX_FULL)
		bmcr |= BMCR_FULLDPLX;

	if (bmcr == val)
		return 0;

	return mv88e6352_serdes_write(chip, MII_BMCR, bmcr);
}

u8 mv88e6352_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)
{
	u8 cmode = chip->ports[port].cmode;
@@ -538,71 +698,153 @@ int mv88e6390_serdes_power(struct mv88e6xxx_chip *chip, int port, u8 lane,
	return err;
}

static void mv88e6390_serdes_irq_link_sgmii(struct mv88e6xxx_chip *chip,
					    int port, u8 lane)
int mv88e6390_serdes_pcs_config(struct mv88e6xxx_chip *chip, int port,
				u8 lane, unsigned int mode,
				phy_interface_t interface,
				const unsigned long *advertise)
{
	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;
	u16 val, bmcr, adv;
	bool changed;
	int err;

	switch (interface) {
	case PHY_INTERFACE_MODE_SGMII:
		adv = 0x0001;
		break;

	case PHY_INTERFACE_MODE_1000BASEX:
		adv = linkmode_adv_to_mii_adv_x(advertise,
					ETHTOOL_LINK_MODE_1000baseX_Full_BIT);
		break;

	case PHY_INTERFACE_MODE_2500BASEX:
		adv = linkmode_adv_to_mii_adv_x(advertise,
					ETHTOOL_LINK_MODE_2500baseX_Full_BIT);
		break;

	default:
		return 0;
	}

	err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
				    MV88E6390_SGMII_ADVERTISE, &val);
	if (err)
		return err;

	changed = val != adv;
	if (changed) {
		err = mv88e6390_serdes_write(chip, lane, MDIO_MMD_PHYXS,
					     MV88E6390_SGMII_ADVERTISE, adv);
		if (err)
			return err;
	}

	err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
				    MV88E6390_SGMII_BMCR, &val);
	if (err)
		return err;

	if (phylink_autoneg_inband(mode))
		bmcr = val | BMCR_ANENABLE;
	else
		bmcr = val & ~BMCR_ANENABLE;

	/* setting ANENABLE triggers a restart of negotiation */
	if (bmcr == val)
		return changed;

	return mv88e6390_serdes_write(chip, lane, MDIO_MMD_PHYXS,
				      MV88E6390_SGMII_BMCR, bmcr);
}

int mv88e6390_serdes_pcs_get_state(struct mv88e6xxx_chip *chip, int port,
				   u8 lane, struct phylink_link_state *state)
{
	u16 lpa, status;
	int err;

	err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
				    MV88E6390_SGMII_PHY_STATUS, &status);
	if (err) {
		dev_err(chip->dev, "can't read SGMII PHY status: %d\n", err);
		return;
		dev_err(chip->dev, "can't read Serdes PHY status: %d\n", err);
		return err;
	}

	link = status & MV88E6390_SGMII_PHY_STATUS_LINK ?
	       LINK_FORCED_UP : LINK_FORCED_DOWN;
	err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
				    MV88E6390_SGMII_LPA, &lpa);
	if (err) {
		dev_err(chip->dev, "can't read Serdes PHY LPA: %d\n", err);
		return err;
	}

	if (status & MV88E6390_SGMII_PHY_STATUS_SPD_DPL_VALID) {
		duplex = status & MV88E6390_SGMII_PHY_STATUS_DUPLEX_FULL ?
			 DUPLEX_FULL : DUPLEX_HALF;
	return mv88e6xxx_serdes_pcs_get_state(chip, status, lpa, state);
}

		switch (status & MV88E6390_SGMII_PHY_STATUS_SPEED_MASK) {
		case MV88E6390_SGMII_PHY_STATUS_SPEED_1000:
			if (cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
				speed = SPEED_2500;
			else
				speed = SPEED_1000;
int mv88e6390_serdes_pcs_an_restart(struct mv88e6xxx_chip *chip, int port,
				    u8 lane)
{
	u16 bmcr;
	int err;

	err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
				    MV88E6390_SGMII_BMCR, &bmcr);
	if (err)
		return err;

	return mv88e6390_serdes_write(chip, lane, MDIO_MMD_PHYXS,
				      MV88E6390_SGMII_BMCR,
				      bmcr | BMCR_ANRESTART);
}

int mv88e6390_serdes_pcs_link_up(struct mv88e6xxx_chip *chip, int port,
				 u8 lane, int speed, int duplex)
{
	u16 val, bmcr;
	int err;

	err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
				    MV88E6390_SGMII_BMCR, &val);
	if (err)
		return err;

	bmcr = val & ~(BMCR_SPEED100 | BMCR_FULLDPLX | BMCR_SPEED1000);
	switch (speed) {
	case SPEED_2500:
	case SPEED_1000:
		bmcr |= BMCR_SPEED1000;
		break;
		case MV88E6390_SGMII_PHY_STATUS_SPEED_100:
			speed = SPEED_100;
	case SPEED_100:
		bmcr |= BMCR_SPEED100;
		break;
		case MV88E6390_SGMII_PHY_STATUS_SPEED_10:
			speed = SPEED_10;
	case SPEED_10:
		break;
		default:
			dev_err(chip->dev, "invalid PHY speed\n");
			return;
	}

	if (duplex == DUPLEX_FULL)
		bmcr |= BMCR_FULLDPLX;

	if (bmcr == val)
		return 0;

	return mv88e6390_serdes_write(chip, lane, MDIO_MMD_PHYXS,
				      MV88E6390_SGMII_BMCR, bmcr);
}

	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;
static void mv88e6390_serdes_irq_link_sgmii(struct mv88e6xxx_chip *chip,
					    int port, u8 lane)
{
	u16 status;
	int err;

	err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
				    MV88E6390_SGMII_PHY_STATUS, &status);
	if (err) {
		dev_err(chip->dev, "can't read SGMII PHY status: %d\n", err);
		return;
	}

	err = mv88e6xxx_port_setup_mac(chip, port, link, speed, duplex,
				       PAUSE_OFF, mode);
	if (err)
		dev_err(chip->dev, "can't propagate PHY settings to MAC: %d\n",
			err);
	else
		dsa_port_phylink_mac_change(ds, port, link == LINK_FORCED_UP);
	dsa_port_phylink_mac_change(chip->ds, port,
				!!(status & MV88E6390_SGMII_PHY_STATUS_LINK));
}

static int mv88e6390_serdes_irq_enable_sgmii(struct mv88e6xxx_chip *chip,
+24 −0
Original line number Diff line number Diff line
@@ -48,6 +48,8 @@

/* 1000BASE-X and SGMII */
#define MV88E6390_SGMII_BMCR		(0x2000 + MII_BMCR)
#define MV88E6390_SGMII_ADVERTISE	(0x2000 + MII_ADVERTISE)
#define MV88E6390_SGMII_LPA		(0x2000 + MII_LPA)
#define MV88E6390_SGMII_INT_ENABLE	0xa001
#define MV88E6390_SGMII_INT_SPEED_CHANGE	BIT(14)
#define MV88E6390_SGMII_INT_DUPLEX_CHANGE	BIT(13)
@@ -66,6 +68,8 @@
#define MV88E6390_SGMII_PHY_STATUS_DUPLEX_FULL	BIT(13)
#define MV88E6390_SGMII_PHY_STATUS_SPD_DPL_VALID BIT(11)
#define MV88E6390_SGMII_PHY_STATUS_LINK		BIT(10)
#define MV88E6390_SGMII_PHY_STATUS_TX_PAUSE	BIT(3)
#define MV88E6390_SGMII_PHY_STATUS_RX_PAUSE	BIT(2)

/* Packet generator pad packet checker */
#define MV88E6390_PG_CONTROL		0xf010
@@ -75,6 +79,26 @@ u8 mv88e6341_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
u8 mv88e6352_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
u8 mv88e6390_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
u8 mv88e6390x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6352_serdes_pcs_config(struct mv88e6xxx_chip *chip, int port,
				u8 lane, unsigned int mode,
				phy_interface_t interface,
				const unsigned long *advertise);
int mv88e6390_serdes_pcs_config(struct mv88e6xxx_chip *chip, int port,
				u8 lane, unsigned int mode,
				phy_interface_t interface,
				const unsigned long *advertise);
int mv88e6352_serdes_pcs_get_state(struct mv88e6xxx_chip *chip, int port,
				   u8 lane, struct phylink_link_state *state);
int mv88e6390_serdes_pcs_get_state(struct mv88e6xxx_chip *chip, int port,
				   u8 lane, struct phylink_link_state *state);
int mv88e6352_serdes_pcs_an_restart(struct mv88e6xxx_chip *chip, int port,
				    u8 lane);
int mv88e6390_serdes_pcs_an_restart(struct mv88e6xxx_chip *chip, int port,
				    u8 lane);
int mv88e6352_serdes_pcs_link_up(struct mv88e6xxx_chip *chip, int port,
				 u8 lane, int speed, int duplex);
int mv88e6390_serdes_pcs_link_up(struct mv88e6xxx_chip *chip, int port,
				 u8 lane, int speed, int duplex);
unsigned int mv88e6352_serdes_irq_mapping(struct mv88e6xxx_chip *chip,
					  int port);
unsigned int mv88e6390_serdes_irq_mapping(struct mv88e6xxx_chip *chip,