lan78xx: Use irq_domain for phy interrupt from USB Int. EP
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>
This commit is contained in:
parent
2331ccc5b3
commit
cc89c323a3
@ -30,13 +30,17 @@
|
|||||||
#include <linux/ipv6.h>
|
#include <linux/ipv6.h>
|
||||||
#include <linux/mdio.h>
|
#include <linux/mdio.h>
|
||||||
#include <net/ip6_checksum.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 <linux/microchipphy.h>
|
||||||
#include "lan78xx.h"
|
#include "lan78xx.h"
|
||||||
|
|
||||||
#define DRIVER_AUTHOR "WOOJUNG HUH <woojung.huh@microchip.com>"
|
#define DRIVER_AUTHOR "WOOJUNG HUH <woojung.huh@microchip.com>"
|
||||||
#define DRIVER_DESC "LAN78XX USB 3.0 Gigabit Ethernet Devices"
|
#define DRIVER_DESC "LAN78XX USB 3.0 Gigabit Ethernet Devices"
|
||||||
#define DRIVER_NAME "lan78xx"
|
#define DRIVER_NAME "lan78xx"
|
||||||
#define DRIVER_VERSION "1.0.4"
|
#define DRIVER_VERSION "1.0.5"
|
||||||
|
|
||||||
#define TX_TIMEOUT_JIFFIES (5 * HZ)
|
#define TX_TIMEOUT_JIFFIES (5 * HZ)
|
||||||
#define THROTTLE_JIFFIES (HZ / 8)
|
#define THROTTLE_JIFFIES (HZ / 8)
|
||||||
@ -89,6 +93,38 @@
|
|||||||
/* statistic update interval (mSec) */
|
/* statistic update interval (mSec) */
|
||||||
#define STAT_UPDATE_TIMER (1 * 1000)
|
#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] = {
|
static const char lan78xx_gstrings[][ETH_GSTRING_LEN] = {
|
||||||
"RX FCS Errors",
|
"RX FCS Errors",
|
||||||
"RX Alignment Errors",
|
"RX Alignment Errors",
|
||||||
@ -296,6 +332,15 @@ struct statstage {
|
|||||||
struct lan78xx_statstage64 curr_stat;
|
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 lan78xx_net {
|
||||||
struct net_device *net;
|
struct net_device *net;
|
||||||
struct usb_device *udev;
|
struct usb_device *udev;
|
||||||
@ -351,6 +396,8 @@ struct lan78xx_net {
|
|||||||
|
|
||||||
int delta;
|
int delta;
|
||||||
struct statstage stats;
|
struct statstage stats;
|
||||||
|
|
||||||
|
struct irq_domain_data domain_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* use ethtool to change the level for any given device */
|
/* 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;
|
int ladv, radv, ret;
|
||||||
u32 buf;
|
u32 buf;
|
||||||
|
|
||||||
/* clear PHY interrupt status */
|
|
||||||
ret = phy_read(phydev, LAN88XX_INT_STS);
|
|
||||||
if (unlikely(ret < 0))
|
|
||||||
return -EIO;
|
|
||||||
|
|
||||||
/* clear LAN78xx interrupt status */
|
/* clear LAN78xx interrupt status */
|
||||||
ret = lan78xx_write_reg(dev, INT_STS, INT_STS_PHY_INT_);
|
ret = lan78xx_write_reg(dev, INT_STS, INT_STS_PHY_INT_);
|
||||||
if (unlikely(ret < 0))
|
if (unlikely(ret < 0))
|
||||||
@ -1120,16 +1162,12 @@ static int lan78xx_link_reset(struct lan78xx_net *dev)
|
|||||||
if (unlikely(ret < 0))
|
if (unlikely(ret < 0))
|
||||||
return -EIO;
|
return -EIO;
|
||||||
|
|
||||||
phy_mac_interrupt(phydev, 0);
|
|
||||||
|
|
||||||
del_timer(&dev->stat_monitor);
|
del_timer(&dev->stat_monitor);
|
||||||
} else if (phydev->link && !dev->link_on) {
|
} else if (phydev->link && !dev->link_on) {
|
||||||
dev->link_on = true;
|
dev->link_on = true;
|
||||||
|
|
||||||
phy_ethtool_ksettings_get(phydev, &ecmd);
|
phy_ethtool_ksettings_get(phydev, &ecmd);
|
||||||
|
|
||||||
ret = phy_read(phydev, LAN88XX_INT_STS);
|
|
||||||
|
|
||||||
if (dev->udev->speed == USB_SPEED_SUPER) {
|
if (dev->udev->speed == USB_SPEED_SUPER) {
|
||||||
if (ecmd.base.speed == 1000) {
|
if (ecmd.base.speed == 1000) {
|
||||||
/* disable U2 */
|
/* disable U2 */
|
||||||
@ -1163,7 +1201,6 @@ static int lan78xx_link_reset(struct lan78xx_net *dev)
|
|||||||
|
|
||||||
ret = lan78xx_update_flowcontrol(dev, ecmd.base.duplex, ladv,
|
ret = lan78xx_update_flowcontrol(dev, ecmd.base.duplex, ladv,
|
||||||
radv);
|
radv);
|
||||||
phy_mac_interrupt(phydev, 1);
|
|
||||||
|
|
||||||
if (!timer_pending(&dev->stat_monitor)) {
|
if (!timer_pending(&dev->stat_monitor)) {
|
||||||
dev->delta = 1;
|
dev->delta = 1;
|
||||||
@ -1202,7 +1239,10 @@ static void lan78xx_status(struct lan78xx_net *dev, struct urb *urb)
|
|||||||
|
|
||||||
if (intdata & INT_ENP_PHY_INT) {
|
if (intdata & INT_ENP_PHY_INT) {
|
||||||
netif_dbg(dev, link, dev->net, "PHY INTR: 0x%08x\n", intdata);
|
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
|
} else
|
||||||
netdev_warn(dev->net,
|
netdev_warn(dev->net,
|
||||||
"unexpected interrupt: 0x%08x\n", intdata);
|
"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)
|
static int lan78xx_phy_init(struct lan78xx_net *dev)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
@ -1856,15 +2017,12 @@ static int lan78xx_phy_init(struct lan78xx_net *dev)
|
|||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enable PHY interrupts.
|
/* if phyirq is not set, use polling mode in phylib */
|
||||||
* We handle our own interrupt
|
if (dev->domain_data.phyirq > 0)
|
||||||
*/
|
phydev->irq = dev->domain_data.phyirq;
|
||||||
ret = phy_read(phydev, LAN88XX_INT_STS);
|
else
|
||||||
ret = phy_write(phydev, LAN88XX_INT_MASK,
|
phydev->irq = 0;
|
||||||
LAN88XX_INT_MASK_MDINTPIN_EN_ |
|
netdev_dbg(dev->net, "phydev->irq = %d\n", phydev->irq);
|
||||||
LAN88XX_INT_MASK_LINK_CHANGE_);
|
|
||||||
|
|
||||||
phydev->irq = PHY_IGNORE_INTERRUPT;
|
|
||||||
|
|
||||||
ret = phy_connect_direct(dev->net, phydev,
|
ret = phy_connect_direct(dev->net, phydev,
|
||||||
lan78xx_link_status_change,
|
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_;
|
buf |= MAC_CR_AUTO_DUPLEX_ | MAC_CR_AUTO_SPEED_;
|
||||||
ret = lan78xx_write_reg(dev, MAC_CR, buf);
|
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);
|
ret = lan78xx_read_reg(dev, MAC_TX, &buf);
|
||||||
buf |= MAC_TX_TXEN_;
|
buf |= MAC_TX_TXEN_;
|
||||||
ret = lan78xx_write_reg(dev, MAC_TX, buf);
|
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;
|
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 */
|
/* Init all registers */
|
||||||
ret = lan78xx_reset(dev);
|
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]);
|
struct lan78xx_priv *pdata = (struct lan78xx_priv *)(dev->data[0]);
|
||||||
|
|
||||||
|
lan78xx_remove_irq_domain(dev);
|
||||||
|
|
||||||
lan78xx_remove_mdio(dev);
|
lan78xx_remove_mdio(dev);
|
||||||
|
|
||||||
if (pdata) {
|
if (pdata) {
|
||||||
|
Loading…
Reference in New Issue
Block a user