lan78xx: Use irq_domain for phy interrupt from USB Int. EP
authorWoojung Huh <woojung.huh@microchip.com>
Tue, 1 Nov 2016 20:02:00 +0000 (20:02 +0000)
committerDavid S. Miller <davem@davemloft.net>
Wed, 2 Nov 2016 19:23:16 +0000 (15:23 -0400)
To utilize phylib with interrupt fully than handling some of phy stuff in the MAC driver,
create irq_domain for USB interrupt EP of phy interrupt and
pass the irq number to phy_connect_direct() instead of PHY_IGNORE_INTERRUPT.

Idea comes from drivers/gpio/gpio-dl2.c

Signed-off-by: Woojung Huh <woojung.huh@microchip.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/usb/lan78xx.c

index c4e748e92db40faed62fe7501ad2453daca95668..bcd9010c1f270a03706234240b4b1a1baebf50a2 100644 (file)
 #include <linux/ipv6.h>
 #include <linux/mdio.h>
 #include <net/ip6_checksum.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
 #include <linux/microchipphy.h>
 #include "lan78xx.h"
 
 #define DRIVER_AUTHOR  "WOOJUNG HUH <woojung.huh@microchip.com>"
 #define DRIVER_DESC    "LAN78XX USB 3.0 Gigabit Ethernet Devices"
 #define DRIVER_NAME    "lan78xx"
-#define DRIVER_VERSION "1.0.4"
+#define DRIVER_VERSION "1.0.5"
 
 #define TX_TIMEOUT_JIFFIES             (5 * HZ)
 #define THROTTLE_JIFFIES               (HZ / 8)
 /* statistic update interval (mSec) */
 #define STAT_UPDATE_TIMER              (1 * 1000)
 
+/* defines interrupts from interrupt EP */
+#define MAX_INT_EP                     (32)
+#define INT_EP_INTEP                   (31)
+#define INT_EP_OTP_WR_DONE             (28)
+#define INT_EP_EEE_TX_LPI_START                (26)
+#define INT_EP_EEE_TX_LPI_STOP         (25)
+#define INT_EP_EEE_RX_LPI              (24)
+#define INT_EP_MAC_RESET_TIMEOUT       (23)
+#define INT_EP_RDFO                    (22)
+#define INT_EP_TXE                     (21)
+#define INT_EP_USB_STATUS              (20)
+#define INT_EP_TX_DIS                  (19)
+#define INT_EP_RX_DIS                  (18)
+#define INT_EP_PHY                     (17)
+#define INT_EP_DP                      (16)
+#define INT_EP_MAC_ERR                 (15)
+#define INT_EP_TDFU                    (14)
+#define INT_EP_TDFO                    (13)
+#define INT_EP_UTX                     (12)
+#define INT_EP_GPIO_11                 (11)
+#define INT_EP_GPIO_10                 (10)
+#define INT_EP_GPIO_9                  (9)
+#define INT_EP_GPIO_8                  (8)
+#define INT_EP_GPIO_7                  (7)
+#define INT_EP_GPIO_6                  (6)
+#define INT_EP_GPIO_5                  (5)
+#define INT_EP_GPIO_4                  (4)
+#define INT_EP_GPIO_3                  (3)
+#define INT_EP_GPIO_2                  (2)
+#define INT_EP_GPIO_1                  (1)
+#define INT_EP_GPIO_0                  (0)
+
 static const char lan78xx_gstrings[][ETH_GSTRING_LEN] = {
        "RX FCS Errors",
        "RX Alignment Errors",
@@ -296,6 +332,15 @@ struct statstage {
        struct lan78xx_statstage64      curr_stat;
 };
 
+struct irq_domain_data {
+       struct irq_domain       *irqdomain;
+       unsigned int            phyirq;
+       struct irq_chip         *irqchip;
+       irq_flow_handler_t      irq_handler;
+       u32                     irqenable;
+       struct mutex            irq_lock;               /* for irq bus access */
+};
+
 struct lan78xx_net {
        struct net_device       *net;
        struct usb_device       *udev;
@@ -351,6 +396,8 @@ struct lan78xx_net {
 
        int                     delta;
        struct statstage        stats;
+
+       struct irq_domain_data  domain_data;
 };
 
 /* use ethtool to change the level for any given device */
@@ -1096,11 +1143,6 @@ static int lan78xx_link_reset(struct lan78xx_net *dev)
        int ladv, radv, ret;
        u32 buf;
 
-       /* clear PHY interrupt status */
-       ret = phy_read(phydev, LAN88XX_INT_STS);
-       if (unlikely(ret < 0))
-               return -EIO;
-
        /* clear LAN78xx interrupt status */
        ret = lan78xx_write_reg(dev, INT_STS, INT_STS_PHY_INT_);
        if (unlikely(ret < 0))
@@ -1120,16 +1162,12 @@ static int lan78xx_link_reset(struct lan78xx_net *dev)
                if (unlikely(ret < 0))
                        return -EIO;
 
-               phy_mac_interrupt(phydev, 0);
-
                del_timer(&dev->stat_monitor);
        } else if (phydev->link && !dev->link_on) {
                dev->link_on = true;
 
                phy_ethtool_ksettings_get(phydev, &ecmd);
 
-               ret = phy_read(phydev, LAN88XX_INT_STS);
-
                if (dev->udev->speed == USB_SPEED_SUPER) {
                        if (ecmd.base.speed == 1000) {
                                /* disable U2 */
@@ -1163,7 +1201,6 @@ static int lan78xx_link_reset(struct lan78xx_net *dev)
 
                ret = lan78xx_update_flowcontrol(dev, ecmd.base.duplex, ladv,
                                                 radv);
-               phy_mac_interrupt(phydev, 1);
 
                if (!timer_pending(&dev->stat_monitor)) {
                        dev->delta = 1;
@@ -1202,7 +1239,10 @@ static void lan78xx_status(struct lan78xx_net *dev, struct urb *urb)
 
        if (intdata & INT_ENP_PHY_INT) {
                netif_dbg(dev, link, dev->net, "PHY INTR: 0x%08x\n", intdata);
-                         lan78xx_defer_kevent(dev, EVENT_LINK_RESET);
+               lan78xx_defer_kevent(dev, EVENT_LINK_RESET);
+
+               if (dev->domain_data.phyirq > 0)
+                       generic_handle_irq(dev->domain_data.phyirq);
        } else
                netdev_warn(dev->net,
                            "unexpected interrupt: 0x%08x\n", intdata);
@@ -1844,6 +1884,127 @@ static void lan78xx_link_status_change(struct net_device *net)
        }
 }
 
+static int irq_map(struct irq_domain *d, unsigned int irq,
+                  irq_hw_number_t hwirq)
+{
+       struct irq_domain_data *data = d->host_data;
+
+       irq_set_chip_data(irq, data);
+       irq_set_chip_and_handler(irq, data->irqchip, data->irq_handler);
+       irq_set_noprobe(irq);
+
+       return 0;
+}
+
+static void irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+       irq_set_chip_and_handler(irq, NULL, NULL);
+       irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops chip_domain_ops = {
+       .map    = irq_map,
+       .unmap  = irq_unmap,
+};
+
+static void lan78xx_irq_mask(struct irq_data *irqd)
+{
+       struct irq_domain_data *data = irq_data_get_irq_chip_data(irqd);
+
+       data->irqenable &= ~BIT(irqd_to_hwirq(irqd));
+}
+
+static void lan78xx_irq_unmask(struct irq_data *irqd)
+{
+       struct irq_domain_data *data = irq_data_get_irq_chip_data(irqd);
+
+       data->irqenable |= BIT(irqd_to_hwirq(irqd));
+}
+
+static void lan78xx_irq_bus_lock(struct irq_data *irqd)
+{
+       struct irq_domain_data *data = irq_data_get_irq_chip_data(irqd);
+
+       mutex_lock(&data->irq_lock);
+}
+
+static void lan78xx_irq_bus_sync_unlock(struct irq_data *irqd)
+{
+       struct irq_domain_data *data = irq_data_get_irq_chip_data(irqd);
+       struct lan78xx_net *dev =
+                       container_of(data, struct lan78xx_net, domain_data);
+       u32 buf;
+       int ret;
+
+       /* call register access here because irq_bus_lock & irq_bus_sync_unlock
+        * are only two callbacks executed in non-atomic contex.
+        */
+       ret = lan78xx_read_reg(dev, INT_EP_CTL, &buf);
+       if (buf != data->irqenable)
+               ret = lan78xx_write_reg(dev, INT_EP_CTL, data->irqenable);
+
+       mutex_unlock(&data->irq_lock);
+}
+
+static struct irq_chip lan78xx_irqchip = {
+       .name                   = "lan78xx-irqs",
+       .irq_mask               = lan78xx_irq_mask,
+       .irq_unmask             = lan78xx_irq_unmask,
+       .irq_bus_lock           = lan78xx_irq_bus_lock,
+       .irq_bus_sync_unlock    = lan78xx_irq_bus_sync_unlock,
+};
+
+static int lan78xx_setup_irq_domain(struct lan78xx_net *dev)
+{
+       struct device_node *of_node;
+       struct irq_domain *irqdomain;
+       unsigned int irqmap = 0;
+       u32 buf;
+       int ret = 0;
+
+       of_node = dev->udev->dev.parent->of_node;
+
+       mutex_init(&dev->domain_data.irq_lock);
+
+       lan78xx_read_reg(dev, INT_EP_CTL, &buf);
+       dev->domain_data.irqenable = buf;
+
+       dev->domain_data.irqchip = &lan78xx_irqchip;
+       dev->domain_data.irq_handler = handle_simple_irq;
+
+       irqdomain = irq_domain_add_simple(of_node, MAX_INT_EP, 0,
+                                         &chip_domain_ops, &dev->domain_data);
+       if (irqdomain) {
+               /* create mapping for PHY interrupt */
+               irqmap = irq_create_mapping(irqdomain, INT_EP_PHY);
+               if (!irqmap) {
+                       irq_domain_remove(irqdomain);
+
+                       irqdomain = NULL;
+                       ret = -EINVAL;
+               }
+       } else {
+               ret = -EINVAL;
+       }
+
+       dev->domain_data.irqdomain = irqdomain;
+       dev->domain_data.phyirq = irqmap;
+
+       return ret;
+}
+
+static void lan78xx_remove_irq_domain(struct lan78xx_net *dev)
+{
+       if (dev->domain_data.phyirq > 0) {
+               irq_dispose_mapping(dev->domain_data.phyirq);
+
+               if (dev->domain_data.irqdomain)
+                       irq_domain_remove(dev->domain_data.irqdomain);
+       }
+       dev->domain_data.phyirq = 0;
+       dev->domain_data.irqdomain = NULL;
+}
+
 static int lan78xx_phy_init(struct lan78xx_net *dev)
 {
        int ret;
@@ -1856,15 +2017,12 @@ static int lan78xx_phy_init(struct lan78xx_net *dev)
                return -EIO;
        }
 
-       /* Enable PHY interrupts.
-        * We handle our own interrupt
-        */
-       ret = phy_read(phydev, LAN88XX_INT_STS);
-       ret = phy_write(phydev, LAN88XX_INT_MASK,
-                       LAN88XX_INT_MASK_MDINTPIN_EN_ |
-                       LAN88XX_INT_MASK_LINK_CHANGE_);
-
-       phydev->irq = PHY_IGNORE_INTERRUPT;
+       /* if phyirq is not set, use polling mode in phylib */
+       if (dev->domain_data.phyirq > 0)
+               phydev->irq = dev->domain_data.phyirq;
+       else
+               phydev->irq = 0;
+       netdev_dbg(dev->net, "phydev->irq = %d\n", phydev->irq);
 
        ret = phy_connect_direct(dev->net, phydev,
                                 lan78xx_link_status_change,
@@ -2255,11 +2413,6 @@ static int lan78xx_reset(struct lan78xx_net *dev)
        buf |= MAC_CR_AUTO_DUPLEX_ | MAC_CR_AUTO_SPEED_;
        ret = lan78xx_write_reg(dev, MAC_CR, buf);
 
-       /* enable PHY interrupts */
-       ret = lan78xx_read_reg(dev, INT_EP_CTL, &buf);
-       buf |= INT_ENP_PHY_INT;
-       ret = lan78xx_write_reg(dev, INT_EP_CTL, buf);
-
        ret = lan78xx_read_reg(dev, MAC_TX, &buf);
        buf |= MAC_TX_TXEN_;
        ret = lan78xx_write_reg(dev, MAC_TX, buf);
@@ -2668,6 +2821,14 @@ static int lan78xx_bind(struct lan78xx_net *dev, struct usb_interface *intf)
 
        dev->net->hw_features = dev->net->features;
 
+       ret = lan78xx_setup_irq_domain(dev);
+       if (ret < 0) {
+               netdev_warn(dev->net,
+                           "lan78xx_setup_irq_domain() failed : %d", ret);
+               kfree(pdata);
+               return ret;
+       }
+
        /* Init all registers */
        ret = lan78xx_reset(dev);
 
@@ -2684,6 +2845,8 @@ static void lan78xx_unbind(struct lan78xx_net *dev, struct usb_interface *intf)
 {
        struct lan78xx_priv *pdata = (struct lan78xx_priv *)(dev->data[0]);
 
+       lan78xx_remove_irq_domain(dev);
+
        lan78xx_remove_mdio(dev);
 
        if (pdata) {