net: dsa: mv88e6xxx: Add SERDES phydev_mac_change up for 6390
authorAndrew Lunn <andrew@lunn.ch>
Thu, 9 Aug 2018 13:38:48 +0000 (15:38 +0200)
committerDavid S. Miller <davem@davemloft.net>
Thu, 9 Aug 2018 18:08:20 +0000 (11:08 -0700)
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>
drivers/net/dsa/mv88e6xxx/chip.c
drivers/net/dsa/mv88e6xxx/chip.h
drivers/net/dsa/mv88e6xxx/serdes.c
drivers/net/dsa/mv88e6xxx/serdes.h

index 5845cbf7f0965d361f50bf419d07107b67f9e311..17752316ab1027ad3f8bb54e6083f8b69de96ca0 100644 (file)
@@ -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,
index 577398fe36df8decd3aad87314aab8479059e737..f9ecb7872d32cd3a6f0807e176a8fc04f63692a7 100644 (file)
@@ -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,
index 064d0bb8fe02806dbdc008b0ffce0123ec71a612..519346b81b87176b3bd8c24497f56c40a80a559a 100644 (file)
@@ -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;
index a64ca1974988453c5e37fa660005ffd22ae1777f..09da08cb5261211a5782b7fefbfb74cc08725c01 100644 (file)
 #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);