Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cc89c323 authored by Woojung Huh's avatar Woojung Huh Committed by David S. Miller
Browse files

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: default avatarWoojung Huh <woojung.huh@microchip.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 2331ccc5
Loading
Loading
Loading
Loading
+189 −26
Original line number Original line Diff line number Diff line
@@ -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;
@@ -1203,6 +1240,9 @@ 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) {