|
@@ -7,6 +7,7 @@
|
|
|
* Author: Andy Fleming
|
|
|
*
|
|
|
* Copyright (c) 2004 Freescale Semiconductor, Inc.
|
|
|
+ * Copyright (c) 2006 Maciej W. Rozycki
|
|
|
*
|
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
|
* under the terms of the GNU General Public License as published by the
|
|
@@ -32,6 +33,8 @@
|
|
|
#include <linux/mii.h>
|
|
|
#include <linux/ethtool.h>
|
|
|
#include <linux/phy.h>
|
|
|
+#include <linux/timer.h>
|
|
|
+#include <linux/workqueue.h>
|
|
|
|
|
|
#include <asm/io.h>
|
|
|
#include <asm/irq.h>
|
|
@@ -484,6 +487,9 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)
|
|
|
{
|
|
|
struct phy_device *phydev = phy_dat;
|
|
|
|
|
|
+ if (PHY_HALTED == phydev->state)
|
|
|
+ return IRQ_NONE; /* It can't be ours. */
|
|
|
+
|
|
|
/* The MDIO bus is not allowed to be written in interrupt
|
|
|
* context, so we need to disable the irq here. A work
|
|
|
* queue will write the PHY to disable and clear the
|
|
@@ -577,6 +583,13 @@ int phy_stop_interrupts(struct phy_device *phydev)
|
|
|
if (err)
|
|
|
phy_error(phydev);
|
|
|
|
|
|
+ /*
|
|
|
+ * Finish any pending work; we might have been scheduled
|
|
|
+ * to be called from keventd ourselves, though.
|
|
|
+ */
|
|
|
+ if (!current_is_keventd())
|
|
|
+ flush_scheduled_work();
|
|
|
+
|
|
|
free_irq(phydev->irq, phydev);
|
|
|
|
|
|
return err;
|
|
@@ -603,7 +616,8 @@ static void phy_change(void *data)
|
|
|
enable_irq(phydev->irq);
|
|
|
|
|
|
/* Reenable interrupts */
|
|
|
- err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED);
|
|
|
+ if (PHY_HALTED != phydev->state)
|
|
|
+ err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED);
|
|
|
|
|
|
if (err)
|
|
|
goto irq_enable_err;
|
|
@@ -624,18 +638,24 @@ void phy_stop(struct phy_device *phydev)
|
|
|
if (PHY_HALTED == phydev->state)
|
|
|
goto out_unlock;
|
|
|
|
|
|
- if (phydev->irq != PHY_POLL) {
|
|
|
- /* Clear any pending interrupts */
|
|
|
- phy_clear_interrupt(phydev);
|
|
|
+ phydev->state = PHY_HALTED;
|
|
|
|
|
|
+ if (phydev->irq != PHY_POLL) {
|
|
|
/* Disable PHY Interrupts */
|
|
|
phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED);
|
|
|
- }
|
|
|
|
|
|
- phydev->state = PHY_HALTED;
|
|
|
+ /* Clear any pending interrupts */
|
|
|
+ phy_clear_interrupt(phydev);
|
|
|
+ }
|
|
|
|
|
|
out_unlock:
|
|
|
spin_unlock(&phydev->lock);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Cannot call flush_scheduled_work() here as desired because
|
|
|
+ * of rtnl_lock(), but PHY_HALTED shall guarantee phy_change()
|
|
|
+ * will not reenable interrupts.
|
|
|
+ */
|
|
|
}
|
|
|
|
|
|
|