net: dsa: mv88e6xxx: Add SERDES phydev_link_change for 6352
authorAndrew Lunn <andrew@lunn.ch>
Sun, 2 Sep 2018 16:13:15 +0000 (18:13 +0200)
committerDavid S. Miller <davem@davemloft.net>
Sun, 2 Sep 2018 23:16:23 +0000 (16:16 -0700)
The 6352 family has one SERDES interface, which can be used by either
port 4 or port 5. Add interrupt support for the SERDES interface, and
report when the link status changes.

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/serdes.c
drivers/net/dsa/mv88e6xxx/serdes.h

index b57f5403982a3daab802146c7408a9ec2c304f54..e07838430d16cfcd8e82c9ab847e396e95ffeb14 100644 (file)
@@ -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,
index e82983975754122be7610c1d096855b486c9520e..bb69650ff7727b2c78b9ea640cfecab69b782aae 100644 (file)
@@ -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.
  */
index b1496de9c6fefff42a3c41fdd8c572272cc538f1..7870c5a9ef1268345a2f5f32a8abf842447224e4 100644 (file)
 
 #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