|
@@ -7,7 +7,7 @@
|
|
|
* Author: Andy Fleming
|
|
|
*
|
|
|
* Copyright (c) 2004 Freescale Semiconductor, Inc.
|
|
|
- * Copyright (c) 2006 Maciej W. Rozycki
|
|
|
+ * Copyright (c) 2006, 2007 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
|
|
@@ -35,6 +35,7 @@
|
|
|
#include <linux/timer.h>
|
|
|
#include <linux/workqueue.h>
|
|
|
|
|
|
+#include <asm/atomic.h>
|
|
|
#include <asm/io.h>
|
|
|
#include <asm/irq.h>
|
|
|
#include <asm/uaccess.h>
|
|
@@ -562,6 +563,7 @@ static irqreturn_t phy_interrupt(int irq, void *phy_dat)
|
|
|
* queue will write the PHY to disable and clear the
|
|
|
* interrupt, and then reenable the irq line. */
|
|
|
disable_irq_nosync(irq);
|
|
|
+ atomic_inc(&phydev->irq_disable);
|
|
|
|
|
|
schedule_work(&phydev->phy_queue);
|
|
|
|
|
@@ -632,6 +634,7 @@ int phy_start_interrupts(struct phy_device *phydev)
|
|
|
|
|
|
INIT_WORK(&phydev->phy_queue, phy_change);
|
|
|
|
|
|
+ atomic_set(&phydev->irq_disable, 0);
|
|
|
if (request_irq(phydev->irq, phy_interrupt,
|
|
|
IRQF_SHARED,
|
|
|
"phy_interrupt",
|
|
@@ -662,13 +665,22 @@ int phy_stop_interrupts(struct phy_device *phydev)
|
|
|
if (err)
|
|
|
phy_error(phydev);
|
|
|
|
|
|
+ free_irq(phydev->irq, phydev);
|
|
|
+
|
|
|
/*
|
|
|
- * Finish any pending work; we might have been scheduled to be called
|
|
|
- * from keventd ourselves, but cancel_work_sync() handles that.
|
|
|
+ * Cannot call flush_scheduled_work() here as desired because
|
|
|
+ * of rtnl_lock(), but we do not really care about what would
|
|
|
+ * be done, except from enable_irq(), so cancel any work
|
|
|
+ * possibly pending and take care of the matter below.
|
|
|
*/
|
|
|
cancel_work_sync(&phydev->phy_queue);
|
|
|
-
|
|
|
- free_irq(phydev->irq, phydev);
|
|
|
+ /*
|
|
|
+ * If work indeed has been cancelled, disable_irq() will have
|
|
|
+ * been left unbalanced from phy_interrupt() and enable_irq()
|
|
|
+ * has to be called so that other devices on the line work.
|
|
|
+ */
|
|
|
+ while (atomic_dec_return(&phydev->irq_disable) >= 0)
|
|
|
+ enable_irq(phydev->irq);
|
|
|
|
|
|
return err;
|
|
|
}
|
|
@@ -695,6 +707,7 @@ static void phy_change(struct work_struct *work)
|
|
|
phydev->state = PHY_CHANGELINK;
|
|
|
spin_unlock_bh(&phydev->lock);
|
|
|
|
|
|
+ atomic_dec(&phydev->irq_disable);
|
|
|
enable_irq(phydev->irq);
|
|
|
|
|
|
/* Reenable interrupts */
|
|
@@ -708,6 +721,7 @@ static void phy_change(struct work_struct *work)
|
|
|
|
|
|
irq_enable_err:
|
|
|
disable_irq(phydev->irq);
|
|
|
+ atomic_inc(&phydev->irq_disable);
|
|
|
phy_err:
|
|
|
phy_error(phydev);
|
|
|
}
|