Commit 1fcc3fd7 authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'Full-phylink-support-for-mv88e6352'



Andrew Lunn says:

====================
Full phylink support for mv88e6352

These two patches implement full phylink support for the mv88e6352
family, when using an SFP connected to its SERDES interface. This adds
interrupt support to the SERDES, so that we get interrupts on link
up/down, and then make calls phydev_link_change().

The first patch is a minor bug fix, which does not seem to affect any
current features, so i'm not submitting it for stable. It is however
required for configuring SERDES interrupts.
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 743e4815 4382172f
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -3160,6 +3160,8 @@ static const struct mv88e6xxx_ops mv88e6176_ops = {
	.vtu_getnext = mv88e6352_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
	.serdes_power = mv88e6352_serdes_power,
	.serdes_irq_setup = mv88e6352_serdes_irq_setup,
	.serdes_irq_free = mv88e6352_serdes_irq_free,
	.gpio_ops = &mv88e6352_gpio_ops,
	.phylink_validate = mv88e6352_phylink_validate,
};
@@ -3366,6 +3368,8 @@ static const struct mv88e6xxx_ops mv88e6240_ops = {
	.vtu_getnext = mv88e6352_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
	.serdes_power = mv88e6352_serdes_power,
	.serdes_irq_setup = mv88e6352_serdes_irq_setup,
	.serdes_irq_free = mv88e6352_serdes_irq_free,
	.gpio_ops = &mv88e6352_gpio_ops,
	.avb_ops = &mv88e6352_avb_ops,
	.ptp_ops = &mv88e6352_ptp_ops,
@@ -3664,6 +3668,8 @@ static const struct mv88e6xxx_ops mv88e6352_ops = {
	.vtu_getnext = mv88e6352_g1_vtu_getnext,
	.vtu_loadpurge = mv88e6352_g1_vtu_loadpurge,
	.serdes_power = mv88e6352_serdes_power,
	.serdes_irq_setup = mv88e6352_serdes_irq_setup,
	.serdes_irq_free = mv88e6352_serdes_irq_free,
	.gpio_ops = &mv88e6352_gpio_ops,
	.avb_ops = &mv88e6352_avb_ops,
	.ptp_ops = &mv88e6352_ptp_ops,
+3 −0
Original line number Diff line number Diff line
@@ -110,6 +110,9 @@ int mv88e6xxx_phy_page_write(struct mv88e6xxx_chip *chip, int phy,
	err = mv88e6xxx_phy_page_get(chip, phy, page);
	if (!err) {
		err = mv88e6xxx_phy_write(chip, phy, MV88E6XXX_PHY_PAGE, page);
		if (!err)
			err = mv88e6xxx_phy_write(chip, phy, reg, val);

		mv88e6xxx_phy_page_put(chip, phy);
	}

+105 −0
Original line number Diff line number Diff line
@@ -185,6 +185,111 @@ int mv88e6352_serdes_get_stats(struct mv88e6xxx_chip *chip, int port,
	return ARRAY_SIZE(mv88e6352_serdes_hw_stats);
}

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

	mv88e6352_serdes_read(chip, MII_BMSR, &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.
	 */
	mv88e6352_serdes_read(chip, MII_BMSR, &status);

	up = status & BMSR_LSTATUS;

	dsa_port_phylink_mac_change(ds, port, up);
}

static irqreturn_t mv88e6352_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;
	u16 status;
	int err;

	mutex_lock(&chip->reg_lock);

	err = mv88e6352_serdes_read(chip, MV88E6352_SERDES_INT_STATUS, &status);
	if (err)
		goto out;

	if (status & MV88E6352_SERDES_INT_LINK_CHANGE) {
		ret = IRQ_HANDLED;
		mv88e6352_serdes_irq_link(chip, port->port);
	}
out:
	mutex_unlock(&chip->reg_lock);

	return ret;
}

static int mv88e6352_serdes_irq_enable(struct mv88e6xxx_chip *chip)
{
	return mv88e6352_serdes_write(chip, MV88E6352_SERDES_INT_ENABLE,
				      MV88E6352_SERDES_INT_LINK_CHANGE);
}

static int mv88e6352_serdes_irq_disable(struct mv88e6xxx_chip *chip)
{
	return mv88e6352_serdes_write(chip, MV88E6352_SERDES_INT_ENABLE, 0);
}

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

	if (!mv88e6352_port_has_serdes(chip, port))
		return 0;

	chip->ports[port].serdes_irq = irq_find_mapping(chip->g2_irq.domain,
							MV88E6352_SERDES_IRQ);
	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,
				   mv88e6352_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 mv88e6352_serdes_irq_enable(chip);
}

void mv88e6352_serdes_irq_free(struct mv88e6xxx_chip *chip, int port)
{
	if (!mv88e6352_port_has_serdes(chip, port))
		return;

	mv88e6352_serdes_irq_disable(chip);

	/* 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);

	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.
 */
+16 −0
Original line number Diff line number Diff line
@@ -18,6 +18,19 @@

#define MV88E6352_ADDR_SERDES		0x0f
#define MV88E6352_SERDES_PAGE_FIBER	0x01
#define MV88E6352_SERDES_IRQ		0x0b
#define MV88E6352_SERDES_INT_ENABLE	0x12
#define MV88E6352_SERDES_INT_SPEED_CHANGE	BIT(14)
#define MV88E6352_SERDES_INT_DUPLEX_CHANGE	BIT(13)
#define MV88E6352_SERDES_INT_PAGE_RX		BIT(12)
#define MV88E6352_SERDES_INT_AN_COMPLETE	BIT(11)
#define MV88E6352_SERDES_INT_LINK_CHANGE	BIT(10)
#define MV88E6352_SERDES_INT_SYMBOL_ERROR	BIT(9)
#define MV88E6352_SERDES_INT_FALSE_CARRIER	BIT(8)
#define MV88E6352_SERDES_INT_FIFO_OVER_UNDER	BIT(7)
#define MV88E6352_SERDES_INT_FIBRE_ENERGY	BIT(4)
#define MV88E6352_SERDES_INT_STATUS	0x13


#define MV88E6341_ADDR_SERDES		0x15

@@ -73,5 +86,8 @@ int mv88e6390_serdes_irq_enable(struct mv88e6xxx_chip *chip, int port,
				int lane);
int mv88e6390_serdes_irq_disable(struct mv88e6xxx_chip *chip, int port,
				 int lane);
int mv88e6352_serdes_irq_setup(struct mv88e6xxx_chip *chip, int port);
void mv88e6352_serdes_irq_free(struct mv88e6xxx_chip *chip, int port);


#endif