Skip to content

Commit

Permalink
lan78xx: Use irq_domain for phy interrupt from USB Int. EP
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Woojung Huh authored and David S. Miller committed Nov 2, 2016
1 parent 2331ccc commit cc89c32
Showing 1 changed file with 189 additions and 26 deletions.
215 changes: 189 additions & 26 deletions drivers/net/usb/lan78xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@
#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)
Expand Down Expand Up @@ -89,6 +93,38 @@
/* 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",
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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))
Expand All @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand All @@ -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) {
Expand Down

0 comments on commit cc89c32

Please sign in to comment.