diff options
author | Andrew Lunn <andrew@lunn.ch> | 2018-08-09 16:38:48 +0300 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2018-08-09 21:08:20 +0300 |
commit | efd1ba6af93ff63d40f92515a83405133145c028 (patch) | |
tree | 46e5072b99ad19c2987c57815224674aecc50d1d | |
parent | 7b898469b91ea08e42dbed52acf8dfcb4f5914d0 (diff) | |
download | linux-efd1ba6af93ff63d40f92515a83405133145c028.tar.xz |
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: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/dsa/mv88e6xxx/chip.c | 22 | ||||
-rw-r--r-- | drivers/net/dsa/mv88e6xxx/chip.h | 5 | ||||
-rw-r--r-- | drivers/net/dsa/mv88e6xxx/serdes.c | 179 | ||||
-rw-r--r-- | drivers/net/dsa/mv88e6xxx/serdes.h | 16 |
4 files changed, 222 insertions, 0 deletions
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index 5845cbf7f096..17752316ab10 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -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, diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h index 577398fe36df..f9ecb7872d32 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.h +++ b/drivers/net/dsa/mv88e6xxx/chip.h @@ -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, diff --git a/drivers/net/dsa/mv88e6xxx/serdes.c b/drivers/net/dsa/mv88e6xxx/serdes.c index 064d0bb8fe02..519346b81b87 100644 --- a/drivers/net/dsa/mv88e6xxx/serdes.c +++ b/drivers/net/dsa/mv88e6xxx/serdes.c @@ -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; diff --git a/drivers/net/dsa/mv88e6xxx/serdes.h b/drivers/net/dsa/mv88e6xxx/serdes.h index a64ca1974988..09da08cb5261 100644 --- a/drivers/net/dsa/mv88e6xxx/serdes.h +++ b/drivers/net/dsa/mv88e6xxx/serdes.h @@ -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); |