diff options
-rw-r--r-- | drivers/net/smsc911x.c | 345 |
1 files changed, 318 insertions, 27 deletions
diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c index 04907ae11f51..89c1f3d258e7 100644 --- a/drivers/net/smsc911x.c +++ b/drivers/net/smsc911x.c @@ -52,6 +52,8 @@ #include <linux/smsc911x.h> #include "smsc911x.h" +#include <linux/delay.h> + #define SMSC_CHIPNAME "smsc911x" #define SMSC_MDIONAME "smsc911x-mdio" #define SMSC_DRV_VERSION "2008-10-21" @@ -116,6 +118,11 @@ struct smsc911x_data { unsigned int clear_bits_mask; unsigned int hashhi; unsigned int hashlo; + + /* Registers for the internal PM */ + unsigned long mac_wucsr; + unsigned long pmt_ctrl; + unsigned long phy_intmsk; }; /* The 16-bit access functions are significantly slower, due to the locking @@ -1468,12 +1475,59 @@ static void smsc911x_set_multicast_list(struct net_device *dev) spin_unlock_irqrestore(&pdata->mac_lock, flags); } +/* When we come out from suspend the INT_STS register can't be read. + * Detect a PME by the PMT_CTRL register instead. */ +static void smsc911x_pme_event( struct smsc911x_data * pdata ) +{ + unsigned long timeout; + u32 temp; + + /* Assert the byte test register for waking up */ + smsc911x_reg_write(pdata, BYTE_TEST , 0x0); + + timeout = 10; + do { + timeout--; + temp = smsc911x_reg_read(pdata, PMT_CTRL); + udelay(1000); + } while (timeout && !(temp & PMT_CTRL_READY_)); + + if (!timeout) + printk(KERN_ERR "smsc911x: Wakeup timeout by the controller\n"); +} + +static void smsc911x_pme_int( struct smsc911x_data * pdata ) +{ + u32 temp; + + /* Disable the power management interrupts */ + temp = smsc911x_reg_read(pdata, PMT_CTRL); + temp &= ~(PMT_CTRL_WOL_EN_| PMT_CTRL_PME_EN_| PMT_CTRL_ED_EN_); + smsc911x_reg_write(pdata, PMT_CTRL , temp); + + /* Disable the PM interrupts */ + temp = smsc911x_reg_read(pdata, INT_EN); + temp &= ~(INT_EN_PME_INT_EN_); + smsc911x_reg_write(pdata, INT_EN , temp); + + /* Disable the wakeup-events on the MAC */ + temp = smsc911x_mac_read(pdata, WUCSR); + temp &= ~(WUCSR_MPEN_); + smsc911x_mac_write(pdata, WUCSR, temp); + + /* Clear only the interrupts that were generated */ + temp = (INT_STS_PME_INT_); + smsc911x_reg_write(pdata, INT_STS , temp); +} + static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id) { struct net_device *dev = dev_id; struct smsc911x_data *pdata = netdev_priv(dev); u32 intsts = smsc911x_reg_read(pdata, INT_STS); u32 inten = smsc911x_reg_read(pdata, INT_EN); + u32 pmt_ctrl = smsc911x_reg_read(pdata, PMT_CTRL); + struct phy_device *phy_dev = pdata->phy_dev; int serviced = IRQ_NONE; u32 temp; @@ -1527,6 +1581,23 @@ static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id) serviced = IRQ_HANDLED; } + if (unlikely(intsts & inten & INT_STS_PHY_INT_)) { + smsc911x_reg_write( pdata, INT_STS , INT_STS_PHY_INT_); + temp = smsc911x_mii_read(phy_dev->bus, phy_dev->addr, MII_INTSTS); + SMSC_TRACE("PHY interrupt, sts 0x%04X", (u16)temp); + smsc911x_phy_adjust_link(dev); + serviced = IRQ_HANDLED; + } + + if ( !( pmt_ctrl & PMT_CTRL_READY_ ) && (pmt_ctrl & PMT_CTRL_WUPS_) ) { + smsc911x_pme_event( pdata ); + serviced = IRQ_HANDLED; + } + if (unlikely(intsts & inten & INT_STS_PME_INT_)) { + smsc911x_pme_int( pdata ); + serviced = IRQ_HANDLED; + } + return serviced; } @@ -1776,6 +1847,56 @@ static int smsc911x_ethtool_set_eeprom(struct net_device *dev, return ret; } +static int smsc911x_ethtool_set_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct smsc911x_data *pdata; + + /* Check for unsupported options */ + if (wol->wolopts & (WAKE_MAGICSECURE | WAKE_UCAST | WAKE_MCAST + | WAKE_BCAST | WAKE_ARP)) + return -EINVAL; + + pdata = netdev_priv(dev); + + /* When disable the WOL options need to disable the PHY-interrupts too */ + if (!wol->wolopts) { + SMSC_TRACE(WOL,"Disabling all sources\n"); + pdata->pmt_ctrl &= ~(PMT_CTRL_WOL_EN_ | PMT_CTRL_ED_EN_); + pdata->phy_intmsk &= ~PHY_INTMSK_ENERGYON_; + pdata->mac_wucsr = 0; + goto exit_set_wol; + } + + /* + * For the magic packet we MUST configure the MAC too, but we can't do it + * at this point, cause the controller stops working. + */ + if (wol->wolopts & WAKE_MAGIC) { + SMSC_TRACE(WOL,"Enabling magic frame\n"); + pdata->mac_wucsr |= WUCSR_MPEN_; + pdata->pmt_ctrl |= PMT_CTRL_WOL_EN_; + } + + /* For the PHY-wakeup we must use the energy detection */ + if (wol->wolopts & WAKE_PHY) { + SMSC_TRACE(WOL,"Enabling PHY energy\n"); + pdata->phy_intmsk |= PHY_INTMSK_ENERGYON_; + pdata->pmt_ctrl |= PMT_CTRL_ED_EN_; + } + + exit_set_wol: + return 0; +} + +/* Function for getting the infos about the WOL */ +static void smsc911x_ethtool_get_wol(struct net_device *net_dev, + struct ethtool_wolinfo *wol) +{ + /* Only for magic and PHY power detection available up now */ + wol->supported = WAKE_MAGIC | WAKE_PHY; +} + static const struct ethtool_ops smsc911x_ethtool_ops = { .get_settings = smsc911x_ethtool_getsettings, .set_settings = smsc911x_ethtool_setsettings, @@ -1789,6 +1910,8 @@ static const struct ethtool_ops smsc911x_ethtool_ops = { .get_eeprom_len = smsc911x_ethtool_get_eeprom_len, .get_eeprom = smsc911x_ethtool_get_eeprom, .set_eeprom = smsc911x_ethtool_set_eeprom, + .get_wol = smsc911x_ethtool_get_wol, + .set_wol = smsc911x_ethtool_set_wol, }; static const struct net_device_ops smsc911x_netdev_ops = { @@ -2075,6 +2198,9 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev) SMSC_TRACE(PROBE, "Network interface: \"%s\"", dev->name); } + /* Mark as capable of being a wake up source */ + device_set_wakeup_capable(&dev->dev,1); + spin_lock_init(&pdata->mac_lock); retval = smsc911x_mii_init(pdev, dev); @@ -2133,45 +2259,210 @@ out_0: /* This implementation assumes the devices remains powered on its VDDVARIO * pins during suspend. */ -static int smsc911x_suspend(struct platform_device *pdev, pm_message_t state) -{ - struct net_device *dev = platform_get_drvdata(pdev); - struct smsc911x_data *pdata = netdev_priv(dev); +/* + * For the mode D1 we MUST left the interrupts enabled + */ +static int smsc911x_drv_state_wakeup(struct smsc911x_data *pdata, int mode) { + int retval; + unsigned long regval; struct phy_device *phy_dev = pdata->phy_dev; - smsc911x_mii_write(phy_dev->bus, phy_dev->addr, MII_BMCR, BMCR_PDOWN); + retval = 0; - /* enable wake on LAN, energy detection and the external PME - * signal. */ - smsc911x_reg_write(pdata, PMT_CTRL, - PMT_CTRL_PM_MODE_D1_ | PMT_CTRL_WOL_EN_ | - PMT_CTRL_ED_EN_ | PMT_CTRL_PME_EN_); + if (mode != 1 && mode != 2) + return -EINVAL; - return 0; + /* Clear already received WUs */ + regval = smsc911x_mac_read(pdata, WUCSR); + regval &= ~(WUCSR_MPR_| WUCSR_WUFR_); + + /* Magic packet enable 'WUCSR_MPEN_' */ + regval |= pdata->mac_wucsr; + SMSC_TRACE(WOL,"WUCSR 0x%08lx\n", regval); + smsc911x_mac_write(pdata, WUCSR, regval); + + /* For the D2 we must enable the PHY interrupt for the energy detection */ + regval = smsc911x_reg_read(pdata, INT_EN); + regval |= (INT_EN_PME_INT_EN_| INT_EN_PHY_INT_EN_); + SMSC_TRACE(WOL,"INT_EN 0x%08lx\n", regval); + smsc911x_reg_write(pdata, INT_EN , regval); + + if (mode /* @FIXME: Enabled only for D2 */) { + u16 phy_mode; + + phy_mode = smsc911x_mii_read(phy_dev->bus, phy_dev->addr, MII_INTMSK); + phy_mode |= PHY_INTMSK_ENERGYON_; + smsc911x_mii_write(phy_dev->bus, phy_dev->addr, MII_INTMSK, phy_mode); + } + + /* Clear the PM mode and clear the current wakeup status */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + regval &= ~PMT_CTRL_PM_MODE_; + regval |= PMT_CTRL_WUPS_; + SMSC_TRACE(WOL,"PMT_CTRL 0x%08lx\n", regval); + smsc911x_reg_write(pdata, PMT_CTRL , regval); + + /* Enable the PME at prior and the wake on LAN */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + regval |= pdata->pmt_ctrl; /* Enable the ENERGY detect or WOL interrupt */ + regval |= PMT_CTRL_PME_EN_; + + if (mode == 1) + regval |= PMT_CTRL_PM_MODE_D1_; + else + regval |= PMT_CTRL_PM_MODE_D2_; + + SMSC_TRACE(WOL,"PMT_CTRL 0x%08lx\n", regval); + smsc911x_reg_write(pdata, PMT_CTRL , regval); + + return retval; } -static int smsc911x_resume(struct platform_device *pdev) -{ - struct net_device *dev = platform_get_drvdata(pdev); - struct smsc911x_data *pdata = netdev_priv(dev); +/* For the state D2 we must disable the host-interrupts */ +static int smsc911x_drv_state_d2(struct smsc911x_data *pdata) { + unsigned long regval; struct phy_device *phy_dev = pdata->phy_dev; - unsigned int to = 100; - /* Note 3.11 from the datasheet: - * "When the LAN9220 is in a power saving state, a write of any - * data to the BYTE_TEST register will wake-up the device." + /* Disable the interrupts of the controller */ + regval = smsc911x_reg_read(pdata, INT_CFG); + regval &= ~INT_CFG_IRQ_EN_; + smsc911x_reg_write(pdata, INT_CFG , regval); + + /* Set the phy to the power down mode */ + regval = smsc911x_mii_read(phy_dev->bus, phy_dev->addr, MII_BMCR); + regval |= BMCR_PDOWN; + smsc911x_mii_write(phy_dev->bus, phy_dev->addr, MII_BMCR, regval); + + /* + * Enter into the power mode D2 (the controller doesn't + * support the mode D3) */ - smsc911x_reg_write(pdata, BYTE_TEST, 0); + regval = smsc911x_reg_read(pdata, PMT_CTRL); + regval &= ~PMT_CTRL_PM_MODE_; + regval |= PMT_CTRL_PM_MODE_D2_; + smsc911x_reg_write(pdata, PMT_CTRL , regval); - /* poll the READY bit in PMT_CTRL. Any other access to the device is - * forbidden while this bit isn't set. Try for 100ms and return -EIO - * if it failed. */ - while (!(smsc911x_reg_read(pdata, PMT_CTRL) & PMT_CTRL_READY_) && --to) - udelay(1000); + return 0; +} + +static int smsc911x_suspend(struct platform_device *pdev, pm_message_t state) { + struct net_device *ndev; + struct smsc911x_data *pdata; + int retval; + + ndev = platform_get_drvdata(pdev); + pdata = netdev_priv(ndev); + if (!ndev) + return -ENODEV; + + /* @FIXME: Implement the other supported power modes of the smsc911x */ + if (state.event != PM_EVENT_SUSPEND) + return -ENOTSUPP; + + if (netif_running(ndev)) { - smsc911x_mii_write(phy_dev->bus, phy_dev->addr, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); + /* The below code is coming from the WinCE guys */ + netif_device_detach(ndev); + + /* + * If configured as wakeup-source enter the mode D1 for packet + * detection using the standard IRQ-line + */ + if (device_may_wakeup(&ndev->dev)) { + + /* + * Sanity check for verifying that a wakeup-source was + * configured from the user space. If the energy-detect + * wakeup was enabled, then use the D2 for entering into the + * power mode + */ + if (!(pdata->pmt_ctrl & (PMT_CTRL_WOL_EN_| PMT_CTRL_ED_EN_))) { + printk(KERN_ERR"smsc911x: No WOL source defined.\n"); + retval = -EINVAL; + goto err_attach; + } + + /* + * By the WOL (magic packet, etc.) we can ONLY use the D1, but + * for the energy detect over the PHY we can change into D2 + */ + if (pdata->pmt_ctrl & PMT_CTRL_WOL_EN_) { + SMSC_TRACE(WOL,"Preparing D1 with wakeup\n"); + smsc911x_drv_state_wakeup(pdata, 1); + } else { + /* @TEST: Use first only D1 for the wakups */ + SMSC_TRACE(WOL,"Preparing D2 with wakeup\n"); + smsc911x_drv_state_wakeup(pdata, 2); + } + + enable_irq_wake(ndev->irq); + + } else { + /* + * Enter into the power mode D2 (the controller doesn't + * support the mode D3) + */ + smsc911x_drv_state_d2(pdata); + } + } + + return 0; + + err_attach: netif_device_attach(ndev); + return retval; +} + +static int smsc911x_resume(struct platform_device *pdev) { + int retval; + struct net_device *ndev; + unsigned long pmt_ctrl; + + retval = 0; + ndev = platform_get_drvdata(pdev); + pmt_ctrl = 0; + if (ndev) { + struct smsc911x_data *pdata = netdev_priv(ndev); + struct phy_device *phy_dev = pdata->phy_dev; + + if (netif_running(ndev)) { + unsigned long regval; + + smsc911x_pme_event( pdata ); + smsc911x_pme_int( pdata ); + + /* Set the controller into the state D0 */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + regval &= ~PMT_CTRL_PM_MODE_; + regval |= PMT_CTRL_PM_MODE_D0_; + smsc911x_reg_write(pdata, PMT_CTRL , regval); + + /* Paranoic sanity checks */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + if (regval & PMT_CTRL_PM_MODE_) + printk(KERN_ERR "smsc911x: PM mode isn't disabled (0x%04lx)\n", regval); + + if (!(regval & PMT_CTRL_READY_)) { + retval = -EBUSY; + printk(KERN_ERR "smsc911x: Device is still NOT ready.\n"); + goto exit_resume; + } + + regval = smsc911x_mii_read(phy_dev->bus, phy_dev->addr, MII_BMCR); + regval &= ~BMCR_PDOWN; + smsc911x_mii_write(phy_dev->bus, phy_dev->addr, MII_BMCR, regval); + + /* Reenable the interrupts now in case they were disabled */ + regval = smsc911x_reg_read(pdata, INT_CFG); + regval |= INT_CFG_IRQ_EN_; + smsc911x_reg_write(pdata, INT_CFG , regval); + + /* Reset the wakeup control and status register */ + smsc911x_mac_write(pdata, WUCSR, 0x00); + netif_device_attach(ndev); + } + } - return (to == 0) ? -EIO : 0; + exit_resume: return retval; } #else |