Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit efd1ba6a authored by Andrew Lunn's avatar Andrew Lunn Committed by David S. Miller
Browse files

net: dsa: mv88e6xxx: Add SERDES phydev_mac_change up for 6390



phylink wants to know when the MAC layers notices a change in the
link. For the 6390 family, this is a change in the SERDES state.

Add interrupt support for the SERDES interface used to implement
SGMII/1000Base-X/2500Base-X. This is currently limited to ports 9 and
10. Support for the 10G SERDES and other ports will be added later,
building on this basic framework.

Signed-off-by: default avatarAndrew Lunn <andrew@lunn.ch>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 7b898469
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -2337,7 +2337,12 @@ static int mv88e6xxx_port_enable(struct dsa_switch *ds, int port,
	int err;

	mutex_lock(&chip->reg_lock);

	err = mv88e6xxx_serdes_power(chip, port, true);

	if (!err && chip->info->ops->serdes_irq_setup)
		err = chip->info->ops->serdes_irq_setup(chip, port);

	mutex_unlock(&chip->reg_lock);

	return err;
@@ -2349,8 +2354,13 @@ static void mv88e6xxx_port_disable(struct dsa_switch *ds, int port,
	struct mv88e6xxx_chip *chip = ds->priv;

	mutex_lock(&chip->reg_lock);

	if (chip->info->ops->serdes_irq_free)
		chip->info->ops->serdes_irq_free(chip, port);

	if (mv88e6xxx_serdes_power(chip, port, false))
		dev_err(chip->dev, "failed to power off SERDES\n");

	mutex_unlock(&chip->reg_lock);
}

@@ -3225,6 +3235,8 @@ 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_irq_setup = mv88e6390_serdes_irq_setup,
	.serdes_irq_free = mv88e6390_serdes_irq_free,
	.gpio_ops = &mv88e6352_gpio_ops,
	.phylink_validate = mv88e6390_phylink_validate,
};
@@ -3265,6 +3277,8 @@ static const struct mv88e6xxx_ops mv88e6190x_ops = {
	.vtu_getnext = mv88e6390_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
	.serdes_power = mv88e6390x_serdes_power,
	.serdes_irq_setup = mv88e6390_serdes_irq_setup,
	.serdes_irq_free = mv88e6390_serdes_irq_free,
	.gpio_ops = &mv88e6352_gpio_ops,
	.phylink_validate = mv88e6390x_phylink_validate,
};
@@ -3305,6 +3319,8 @@ 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_irq_setup = mv88e6390_serdes_irq_setup,
	.serdes_irq_free = mv88e6390_serdes_irq_free,
	.avb_ops = &mv88e6390_avb_ops,
	.ptp_ops = &mv88e6352_ptp_ops,
	.phylink_validate = mv88e6390_phylink_validate,
@@ -3393,6 +3409,8 @@ 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_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,
@@ -3694,6 +3712,8 @@ 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_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,
@@ -3739,6 +3759,8 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = {
	.vtu_getnext = mv88e6390_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6390_g1_vtu_loadpurge,
	.serdes_power = mv88e6390x_serdes_power,
	.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,
+5 −0
Original line number Diff line number Diff line
@@ -200,6 +200,7 @@ struct mv88e6xxx_port {
	u64 vtu_member_violation;
	u64 vtu_miss_violation;
	u8 cmode;
	int serdes_irq;
};

struct mv88e6xxx_chip {
@@ -434,6 +435,10 @@ struct mv88e6xxx_ops {
	/* Power on/off a SERDES interface */
	int (*serdes_power)(struct mv88e6xxx_chip *chip, int port, bool on);

	/* SERDES interrupt handling */
	int (*serdes_irq_setup)(struct mv88e6xxx_chip *chip, int port);
	void (*serdes_irq_free)(struct mv88e6xxx_chip *chip, int port);

	/* Statistics from the SERDES interface */
	int (*serdes_get_sset_count)(struct mv88e6xxx_chip *chip, int port);
	int (*serdes_get_strings)(struct mv88e6xxx_chip *chip,  int port,
+179 −0
Original line number Diff line number Diff line
@@ -11,6 +11,8 @@
 * (at your option) any later version.
 */

#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/mii.h>

#include "chip.h"
@@ -399,6 +401,183 @@ int mv88e6390x_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on)
	return 0;
}

static void mv88e6390_serdes_irq_link_sgmii(struct mv88e6xxx_chip *chip,
					    int port, int lane)
{
	struct dsa_switch *ds = chip->ds;
	u16 status;
	bool up;

	mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
			      MV88E6390_SGMII_STATUS, &status);

	/* Status must be read twice in order to give the current link
	 * status. Otherwise the change in link status since the last
	 * read of the register is returned.
	 */
	mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
			      MV88E6390_SGMII_STATUS, &status);
	up = status & MV88E6390_SGMII_STATUS_LINK;

	dsa_port_phylink_mac_change(ds, port, up);
}

static int mv88e6390_serdes_irq_enable_sgmii(struct mv88e6xxx_chip *chip,
					     int lane)
{
	return mv88e6390_serdes_write(chip, lane, MDIO_MMD_PHYXS,
				      MV88E6390_SGMII_INT_ENABLE,
				      MV88E6390_SGMII_INT_LINK_DOWN |
				      MV88E6390_SGMII_INT_LINK_UP);
}

static int mv88e6390_serdes_irq_disable_sgmii(struct mv88e6xxx_chip *chip,
					      int 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 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_2500BASEX:
		err = mv88e6390_serdes_irq_enable_sgmii(chip, lane);
	}

	return err;
}

int mv88e6390_serdes_irq_disable(struct mv88e6xxx_chip *chip, int port,
				 int 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_2500BASEX:
		err = mv88e6390_serdes_irq_disable_sgmii(chip, lane);
	}

	return err;
}

static int mv88e6390_serdes_irq_status_sgmii(struct mv88e6xxx_chip *chip,
					     int lane, u16 *status)
{
	int err;

	err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
				    MV88E6390_SGMII_INT_STATUS, status);

	return err;
}

static irqreturn_t mv88e6390_serdes_thread_fn(int irq, void *dev_id)
{
	struct mv88e6xxx_port *port = dev_id;
	struct mv88e6xxx_chip *chip = port->chip;
	irqreturn_t ret = IRQ_NONE;
	u8 cmode = port->cmode;
	u16 status;
	int lane;
	int err;

	lane = mv88e6390x_serdes_get_lane(chip, port->port);

	mutex_lock(&chip->reg_lock);

	switch (cmode) {
	case MV88E6XXX_PORT_STS_CMODE_SGMII:
	case MV88E6XXX_PORT_STS_CMODE_1000BASE_X:
	case MV88E6XXX_PORT_STS_CMODE_2500BASEX:
		err = mv88e6390_serdes_irq_status_sgmii(chip, lane, &status);
		if (err)
			goto out;
		if (status && (MV88E6390_SGMII_INT_LINK_DOWN ||
			       MV88E6390_SGMII_INT_LINK_UP)) {
			ret = IRQ_HANDLED;
			mv88e6390_serdes_irq_link_sgmii(chip, port->port, lane);
		}
	}
out:
	mutex_unlock(&chip->reg_lock);

	return ret;
}

int mv88e6390_serdes_irq_setup(struct mv88e6xxx_chip *chip, int port)
{
	int lane;
	int err;

	/* Only support ports 9 and 10 at the moment */
	if (port < 9)
		return 0;

	lane = mv88e6390x_serdes_get_lane(chip, port);

	if (lane == -ENODEV)
		return 0;

	if (lane < 0)
		return lane;

	chip->ports[port].serdes_irq = irq_find_mapping(chip->g2_irq.domain,
							port);
	if (chip->ports[port].serdes_irq < 0) {
		dev_err(chip->dev, "Unable to map SERDES irq: %d\n",
			chip->ports[port].serdes_irq);
		return chip->ports[port].serdes_irq;
	}

	/* Requesting the IRQ will trigger irq callbacks. So we cannot
	 * hold the reg_lock.
	 */
	mutex_unlock(&chip->reg_lock);
	err = request_threaded_irq(chip->ports[port].serdes_irq, NULL,
				   mv88e6390_serdes_thread_fn,
				   IRQF_ONESHOT, "mv88e6xxx-serdes",
				   &chip->ports[port]);
	mutex_lock(&chip->reg_lock);

	if (err) {
		dev_err(chip->dev, "Unable to request SERDES interrupt: %d\n",
			err);
		return err;
	}

	return mv88e6390_serdes_irq_enable(chip, port, lane);
}

void mv88e6390_serdes_irq_free(struct mv88e6xxx_chip *chip, int port)
{
	int lane = mv88e6390x_serdes_get_lane(chip, port);

	if (port < 9)
		return;

	if (lane < 0)
		return;

	mv88e6390_serdes_irq_disable(chip, port, lane);

	/* Freeing the IRQ will trigger irq callbacks. So we cannot
	 * hold the reg_lock.
	 */
	mutex_unlock(&chip->reg_lock);
	free_irq(chip->ports[port].serdes_irq, &chip->ports[port]);
	mutex_lock(&chip->reg_lock);
}

int mv88e6341_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on)
{
	u8 cmode = chip->ports[port].cmode;
+16 −0
Original line number Diff line number Diff line
@@ -42,11 +42,27 @@
#define MV88E6390_SGMII_CONTROL_RESET		BIT(15)
#define MV88E6390_SGMII_CONTROL_LOOPBACK	BIT(14)
#define MV88E6390_SGMII_CONTROL_PDOWN		BIT(11)
#define MV88E6390_SGMII_STATUS		0x2001
#define MV88E6390_SGMII_STATUS_AN_DONE		BIT(5)
#define MV88E6390_SGMII_STATUS_REMOTE_FAULT	BIT(4)
#define MV88E6390_SGMII_STATUS_LINK		BIT(2)
#define MV88E6390_SGMII_INT_ENABLE	0xa001
#define MV88E6390_SGMII_INT_SPEED_CHANGE	BIT(14)
#define MV88E6390_SGMII_INT_DUPLEX_CHANGE	BIT(13)
#define MV88E6390_SGMII_INT_PAGE_RX		BIT(12)
#define MV88E6390_SGMII_INT_AN_COMPLETE		BIT(11)
#define MV88E6390_SGMII_INT_LINK_DOWN		BIT(10)
#define MV88E6390_SGMII_INT_LINK_UP		BIT(9)
#define MV88E6390_SGMII_INT_SYMBOL_ERROR	BIT(8)
#define MV88E6390_SGMII_INT_FALSE_CARRIER	BIT(7)
#define MV88E6390_SGMII_INT_STATUS	0xa002

int mv88e6341_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on);
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 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);