Commit 45c5d3bc authored by Lennert Buytenhek's avatar Lennert Buytenhek Committed by Lennert Buytenhek

mv643xx_eth: use the SMI done interrupt to wait for SMI access completion

If the platform code has passed us the IRQ number of the mv643xx_eth
top-level error interrupt, use the error interrupt to wait for SMI
access completion instead of polling the SMI busy bit, since SMI bus
accesses can take up to tens of milliseconds.
Signed-off-by: default avatarLennert Buytenhek <buytenh@marvell.com>
parent 2b3ba0e3
......@@ -72,6 +72,13 @@ static char mv643xx_eth_driver_version[] = "1.3";
*/
#define PHY_ADDR 0x0000
#define SMI_REG 0x0004
#define SMI_BUSY 0x10000000
#define SMI_READ_VALID 0x08000000
#define SMI_OPCODE_READ 0x04000000
#define SMI_OPCODE_WRITE 0x00000000
#define ERR_INT_CAUSE 0x0080
#define ERR_INT_SMI_DONE 0x00000010
#define ERR_INT_MASK 0x0084
#define WINDOW_BASE(w) (0x0200 + ((w) << 3))
#define WINDOW_SIZE(w) (0x0204 + ((w) << 3))
#define WINDOW_REMAP_HIGH(w) (0x0280 + ((w) << 2))
......@@ -253,6 +260,15 @@ struct mv643xx_eth_shared_private {
*/
struct mutex phy_lock;
/*
* If we have access to the error interrupt pin (which is
* somewhat misnamed as it not only reflects internal errors
* but also reflects SMI completion), use that to wait for
* SMI access completion instead of polling the SMI busy bit.
*/
int err_interrupt;
wait_queue_head_t smi_busy_wait;
/*
* Per-port MBUS window access register value.
*/
......@@ -979,68 +995,103 @@ static void txq_set_wrr(struct tx_queue *txq, int weight)
/* mii management interface *************************************************/
#define SMI_BUSY 0x10000000
#define SMI_READ_VALID 0x08000000
#define SMI_OPCODE_READ 0x04000000
#define SMI_OPCODE_WRITE 0x00000000
static irqreturn_t mv643xx_eth_err_irq(int irq, void *dev_id)
{
struct mv643xx_eth_shared_private *msp = dev_id;
if (readl(msp->base + ERR_INT_CAUSE) & ERR_INT_SMI_DONE) {
writel(~ERR_INT_SMI_DONE, msp->base + ERR_INT_CAUSE);
wake_up(&msp->smi_busy_wait);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static void smi_reg_read(struct mv643xx_eth_private *mp, unsigned int addr,
unsigned int reg, unsigned int *value)
static int smi_is_done(struct mv643xx_eth_shared_private *msp)
{
void __iomem *smi_reg = mp->shared_smi->base + SMI_REG;
return !(readl(msp->base + SMI_REG) & SMI_BUSY);
}
static int smi_wait_ready(struct mv643xx_eth_shared_private *msp)
{
if (msp->err_interrupt == NO_IRQ) {
int i;
/* the SMI register is a shared resource */
mutex_lock(&mp->shared_smi->phy_lock);
for (i = 0; !smi_is_done(msp); i++) {
if (i == 10)
return -ETIMEDOUT;
msleep(10);
}
/* wait for the SMI register to become available */
for (i = 0; readl(smi_reg) & SMI_BUSY; i++) {
if (i == 1000) {
printk("%s: PHY busy timeout\n", mp->dev->name);
goto out;
return 0;
}
udelay(10);
if (!wait_event_timeout(msp->smi_busy_wait, smi_is_done(msp),
msecs_to_jiffies(100)))
return -ETIMEDOUT;
return 0;
}
static int smi_reg_read(struct mv643xx_eth_private *mp,
unsigned int addr, unsigned int reg)
{
struct mv643xx_eth_shared_private *msp = mp->shared_smi;
void __iomem *smi_reg = msp->base + SMI_REG;
int ret;
mutex_lock(&msp->phy_lock);
if (smi_wait_ready(msp)) {
printk("%s: SMI bus busy timeout\n", mp->dev->name);
ret = -ETIMEDOUT;
goto out;
}
writel(SMI_OPCODE_READ | (reg << 21) | (addr << 16), smi_reg);
/* now wait for the data to be valid */
for (i = 0; !(readl(smi_reg) & SMI_READ_VALID); i++) {
if (i == 1000) {
printk("%s: PHY read timeout\n", mp->dev->name);
if (smi_wait_ready(msp)) {
printk("%s: SMI bus busy timeout\n", mp->dev->name);
ret = -ETIMEDOUT;
goto out;
}
udelay(10);
ret = readl(smi_reg);
if (!(ret & SMI_READ_VALID)) {
printk("%s: SMI bus read not valid\n", mp->dev->name);
ret = -ENODEV;
goto out;
}
*value = readl(smi_reg) & 0xffff;
ret &= 0xffff;
out:
mutex_unlock(&mp->shared_smi->phy_lock);
mutex_unlock(&msp->phy_lock);
return ret;
}
static void smi_reg_write(struct mv643xx_eth_private *mp,
unsigned int addr,
static int smi_reg_write(struct mv643xx_eth_private *mp, unsigned int addr,
unsigned int reg, unsigned int value)
{
void __iomem *smi_reg = mp->shared_smi->base + SMI_REG;
int i;
struct mv643xx_eth_shared_private *msp = mp->shared_smi;
void __iomem *smi_reg = msp->base + SMI_REG;
/* the SMI register is a shared resource */
mutex_lock(&mp->shared_smi->phy_lock);
mutex_lock(&msp->phy_lock);
/* wait for the SMI register to become available */
for (i = 0; readl(smi_reg) & SMI_BUSY; i++) {
if (i == 1000) {
printk("%s: PHY busy timeout\n", mp->dev->name);
goto out;
}
udelay(10);
if (smi_wait_ready(msp)) {
printk("%s: SMI bus busy timeout\n", mp->dev->name);
mutex_unlock(&msp->phy_lock);
return -ETIMEDOUT;
}
writel(SMI_OPCODE_WRITE | (reg << 21) |
(addr << 16) | (value & 0xffff), smi_reg);
out:
mutex_unlock(&mp->shared_smi->phy_lock);
mutex_unlock(&msp->phy_lock);
return 0;
}
......@@ -1877,16 +1928,19 @@ static irqreturn_t mv643xx_eth_irq(int irq, void *dev_id)
static void phy_reset(struct mv643xx_eth_private *mp)
{
unsigned int data;
int data;
data = smi_reg_read(mp, mp->phy_addr, MII_BMCR);
if (data < 0)
return;
smi_reg_read(mp, mp->phy_addr, MII_BMCR, &data);
data |= BMCR_RESET;
smi_reg_write(mp, mp->phy_addr, MII_BMCR, data);
if (smi_reg_write(mp, mp->phy_addr, MII_BMCR, data) < 0)
return;
do {
udelay(1);
smi_reg_read(mp, mp->phy_addr, MII_BMCR, &data);
} while (data & BMCR_RESET);
data = smi_reg_read(mp, mp->phy_addr, MII_BMCR);
} while (data >= 0 && data & BMCR_RESET);
}
static void port_start(struct mv643xx_eth_private *mp)
......@@ -2214,11 +2268,7 @@ static void mv643xx_eth_netpoll(struct net_device *dev)
static int mv643xx_eth_mdio_read(struct net_device *dev, int addr, int reg)
{
struct mv643xx_eth_private *mp = netdev_priv(dev);
int val;
smi_reg_read(mp, addr, reg, &val);
return val;
return smi_reg_read(mp, addr, reg);
}
static void mv643xx_eth_mdio_write(struct net_device *dev, int addr, int reg, int val)
......@@ -2317,6 +2367,24 @@ static int mv643xx_eth_shared_probe(struct platform_device *pdev)
mutex_init(&msp->phy_lock);
msp->err_interrupt = NO_IRQ;
init_waitqueue_head(&msp->smi_busy_wait);
/*
* Check whether the error interrupt is hooked up.
*/
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res != NULL) {
int err;
err = request_irq(res->start, mv643xx_eth_err_irq,
IRQF_SHARED, "mv643xx_eth", msp);
if (!err) {
writel(ERR_INT_SMI_DONE, msp->base + ERR_INT_MASK);
msp->err_interrupt = res->start;
}
}
/*
* (Re-)program MBUS remapping windows if we are asked to.
*/
......@@ -2343,6 +2411,8 @@ static int mv643xx_eth_shared_remove(struct platform_device *pdev)
{
struct mv643xx_eth_shared_private *msp = platform_get_drvdata(pdev);
if (msp->err_interrupt != NO_IRQ)
free_irq(msp->err_interrupt, msp);
iounmap(msp->base);
kfree(msp);
......@@ -2431,13 +2501,20 @@ static void set_params(struct mv643xx_eth_private *mp,
static int phy_detect(struct mv643xx_eth_private *mp)
{
unsigned int data;
unsigned int data2;
int data;
int data2;
data = smi_reg_read(mp, mp->phy_addr, MII_BMCR);
if (data < 0)
return -ENODEV;
if (smi_reg_write(mp, mp->phy_addr, MII_BMCR, data ^ BMCR_ANENABLE) < 0)
return -ENODEV;
smi_reg_read(mp, mp->phy_addr, MII_BMCR, &data);
smi_reg_write(mp, mp->phy_addr, MII_BMCR, data ^ BMCR_ANENABLE);
data2 = smi_reg_read(mp, mp->phy_addr, MII_BMCR);
if (data2 < 0)
return -ENODEV;
smi_reg_read(mp, mp->phy_addr, MII_BMCR, &data2);
if (((data ^ data2) & BMCR_ANENABLE) == 0)
return -ENODEV;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment