summaryrefslogtreecommitdiffstats
path: root/drivers/net/phy/phy.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/phy/phy.c')
-rw-r--r--drivers/net/phy/phy.c54
1 files changed, 34 insertions, 20 deletions
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index cb230f44d6f..9bc11773705 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -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>
@@ -204,7 +205,7 @@ static const struct phy_setting settings[] = {
},
};
-#define MAX_NUM_SETTINGS (sizeof(settings)/sizeof(struct phy_setting))
+#define MAX_NUM_SETTINGS ARRAY_SIZE(settings)
/**
* phy_find_setting - find a PHY settings array entry that matches speed & duplex
@@ -424,7 +425,7 @@ int phy_start_aneg(struct phy_device *phydev)
{
int err;
- spin_lock(&phydev->lock);
+ spin_lock_bh(&phydev->lock);
if (AUTONEG_DISABLE == phydev->autoneg)
phy_sanitize_settings(phydev);
@@ -445,7 +446,7 @@ int phy_start_aneg(struct phy_device *phydev)
}
out_unlock:
- spin_unlock(&phydev->lock);
+ spin_unlock_bh(&phydev->lock);
return err;
}
EXPORT_SYMBOL(phy_start_aneg);
@@ -490,10 +491,10 @@ void phy_stop_machine(struct phy_device *phydev)
{
del_timer_sync(&phydev->phy_timer);
- spin_lock(&phydev->lock);
+ spin_lock_bh(&phydev->lock);
if (phydev->state > PHY_UP)
phydev->state = PHY_UP;
- spin_unlock(&phydev->lock);
+ spin_unlock_bh(&phydev->lock);
phydev->adjust_state = NULL;
}
@@ -537,9 +538,9 @@ static void phy_force_reduction(struct phy_device *phydev)
*/
void phy_error(struct phy_device *phydev)
{
- spin_lock(&phydev->lock);
+ spin_lock_bh(&phydev->lock);
phydev->state = PHY_HALTED;
- spin_unlock(&phydev->lock);
+ spin_unlock_bh(&phydev->lock);
}
/**
@@ -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;
}
@@ -690,11 +702,12 @@ static void phy_change(struct work_struct *work)
if (err)
goto phy_err;
- spin_lock(&phydev->lock);
+ spin_lock_bh(&phydev->lock);
if ((PHY_RUNNING == phydev->state) || (PHY_NOLINK == phydev->state))
phydev->state = PHY_CHANGELINK;
- spin_unlock(&phydev->lock);
+ 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);
}
@@ -718,13 +732,11 @@ phy_err:
*/
void phy_stop(struct phy_device *phydev)
{
- spin_lock(&phydev->lock);
+ spin_lock_bh(&phydev->lock);
if (PHY_HALTED == phydev->state)
goto out_unlock;
- phydev->state = PHY_HALTED;
-
if (phydev->irq != PHY_POLL) {
/* Disable PHY Interrupts */
phy_config_interrupt(phydev, PHY_INTERRUPT_DISABLED);
@@ -733,8 +745,10 @@ void phy_stop(struct phy_device *phydev)
phy_clear_interrupt(phydev);
}
+ phydev->state = PHY_HALTED;
+
out_unlock:
- spin_unlock(&phydev->lock);
+ spin_unlock_bh(&phydev->lock);
/*
* Cannot call flush_scheduled_work() here as desired because
@@ -782,7 +796,7 @@ static void phy_timer(unsigned long data)
int needs_aneg = 0;
int err = 0;
- spin_lock(&phydev->lock);
+ spin_lock_bh(&phydev->lock);
if (phydev->adjust_state)
phydev->adjust_state(phydev->attached_dev);
@@ -948,7 +962,7 @@ static void phy_timer(unsigned long data)
break;
}
- spin_unlock(&phydev->lock);
+ spin_unlock_bh(&phydev->lock);
if (needs_aneg)
err = phy_start_aneg(phydev);