linux/drivers/net/phy/smsc.c
<<
>>
Prefs
   1/*
   2 * drivers/net/phy/smsc.c
   3 *
   4 * Driver for SMSC PHYs
   5 *
   6 * Author: Herbert Valerio Riedel
   7 *
   8 * Copyright (c) 2006 Herbert Valerio Riedel <hvr@gnu.org>
   9 *
  10 * This program is free software; you can redistribute  it and/or modify it
  11 * under  the terms of  the GNU General  Public License as published by the
  12 * Free Software Foundation;  either version 2 of the  License, or (at your
  13 * option) any later version.
  14 *
  15 * Support added for SMSC LAN8187 and LAN8700 by steve.glendinning@shawell.net
  16 *
  17 */
  18
  19#include <linux/kernel.h>
  20#include <linux/module.h>
  21#include <linux/mii.h>
  22#include <linux/ethtool.h>
  23#include <linux/phy.h>
  24#include <linux/netdevice.h>
  25#include <linux/smscphy.h>
  26
  27static int smsc_phy_config_intr(struct phy_device *phydev)
  28{
  29        int rc = phy_write (phydev, MII_LAN83C185_IM,
  30                        ((PHY_INTERRUPT_ENABLED == phydev->interrupts)
  31                        ? MII_LAN83C185_ISF_INT_PHYLIB_EVENTS
  32                        : 0));
  33
  34        return rc < 0 ? rc : 0;
  35}
  36
  37static int smsc_phy_ack_interrupt(struct phy_device *phydev)
  38{
  39        int rc = phy_read (phydev, MII_LAN83C185_ISF);
  40
  41        return rc < 0 ? rc : 0;
  42}
  43
  44static int smsc_phy_config_init(struct phy_device *phydev)
  45{
  46        int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
  47
  48        if (rc < 0)
  49                return rc;
  50
  51        /* Enable energy detect mode for this SMSC Transceivers */
  52        rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
  53                       rc | MII_LAN83C185_EDPWRDOWN);
  54        if (rc < 0)
  55                return rc;
  56
  57        return smsc_phy_ack_interrupt(phydev);
  58}
  59
  60static int smsc_phy_reset(struct phy_device *phydev)
  61{
  62        int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES);
  63        if (rc < 0)
  64                return rc;
  65
  66        /* If the SMSC PHY is in power down mode, then set it
  67         * in all capable mode before using it.
  68         */
  69        if ((rc & MII_LAN83C185_MODE_MASK) == MII_LAN83C185_MODE_POWERDOWN) {
  70                int timeout = 50000;
  71
  72                /* set "all capable" mode and reset the phy */
  73                rc |= MII_LAN83C185_MODE_ALL;
  74                phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc);
  75                phy_write(phydev, MII_BMCR, BMCR_RESET);
  76
  77                /* wait end of reset (max 500 ms) */
  78                do {
  79                        udelay(10);
  80                        if (timeout-- == 0)
  81                                return -1;
  82                        rc = phy_read(phydev, MII_BMCR);
  83                } while (rc & BMCR_RESET);
  84        }
  85        return 0;
  86}
  87
  88static int lan911x_config_init(struct phy_device *phydev)
  89{
  90        return smsc_phy_ack_interrupt(phydev);
  91}
  92
  93/*
  94 * The LAN87xx suffers from rare absence of the ENERGYON-bit when Ethernet cable
  95 * plugs in while LAN87xx is in Energy Detect Power-Down mode. This leads to
  96 * unstable detection of plugging in Ethernet cable.
  97 * This workaround disables Energy Detect Power-Down mode and waiting for
  98 * response on link pulses to detect presence of plugged Ethernet cable.
  99 * The Energy Detect Power-Down mode is enabled again in the end of procedure to
 100 * save approximately 220 mW of power if cable is unplugged.
 101 */
 102static int lan87xx_read_status(struct phy_device *phydev)
 103{
 104        int err = genphy_read_status(phydev);
 105        int i;
 106
 107        if (!phydev->link) {
 108                /* Disable EDPD to wake up PHY */
 109                int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
 110                if (rc < 0)
 111                        return rc;
 112
 113                rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
 114                               rc & ~MII_LAN83C185_EDPWRDOWN);
 115                if (rc < 0)
 116                        return rc;
 117
 118                /* Wait max 640 ms to detect energy */
 119                for (i = 0; i < 64; i++) {
 120                        /* Sleep to allow link test pulses to be sent */
 121                        msleep(10);
 122                        rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
 123                        if (rc < 0)
 124                                return rc;
 125                        if (rc & MII_LAN83C185_ENERGYON)
 126                                break;
 127                }
 128
 129                /* Re-enable EDPD */
 130                rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
 131                if (rc < 0)
 132                        return rc;
 133
 134                rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
 135                               rc | MII_LAN83C185_EDPWRDOWN);
 136                if (rc < 0)
 137                        return rc;
 138        }
 139
 140        return err;
 141}
 142
 143static struct phy_driver smsc_phy_driver[] = {
 144{
 145        .phy_id         = 0x0007c0a0, /* OUI=0x00800f, Model#=0x0a */
 146        .phy_id_mask    = 0xfffffff0,
 147        .name           = "SMSC LAN83C185",
 148
 149        .features       = (PHY_BASIC_FEATURES | SUPPORTED_Pause
 150                                | SUPPORTED_Asym_Pause),
 151        .flags          = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
 152
 153        /* basic functions */
 154        .config_aneg    = genphy_config_aneg,
 155        .read_status    = genphy_read_status,
 156        .config_init    = smsc_phy_config_init,
 157        .soft_reset     = smsc_phy_reset,
 158
 159        /* IRQ related */
 160        .ack_interrupt  = smsc_phy_ack_interrupt,
 161        .config_intr    = smsc_phy_config_intr,
 162
 163        .suspend        = genphy_suspend,
 164        .resume         = genphy_resume,
 165
 166        .driver         = { .owner = THIS_MODULE, }
 167}, {
 168        .phy_id         = 0x0007c0b0, /* OUI=0x00800f, Model#=0x0b */
 169        .phy_id_mask    = 0xfffffff0,
 170        .name           = "SMSC LAN8187",
 171
 172        .features       = (PHY_BASIC_FEATURES | SUPPORTED_Pause
 173                                | SUPPORTED_Asym_Pause),
 174        .flags          = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
 175
 176        /* basic functions */
 177        .config_aneg    = genphy_config_aneg,
 178        .read_status    = genphy_read_status,
 179        .config_init    = smsc_phy_config_init,
 180        .soft_reset     = smsc_phy_reset,
 181
 182        /* IRQ related */
 183        .ack_interrupt  = smsc_phy_ack_interrupt,
 184        .config_intr    = smsc_phy_config_intr,
 185
 186        .suspend        = genphy_suspend,
 187        .resume         = genphy_resume,
 188
 189        .driver         = { .owner = THIS_MODULE, }
 190}, {
 191        .phy_id         = 0x0007c0c0, /* OUI=0x00800f, Model#=0x0c */
 192        .phy_id_mask    = 0xfffffff0,
 193        .name           = "SMSC LAN8700",
 194
 195        .features       = (PHY_BASIC_FEATURES | SUPPORTED_Pause
 196                                | SUPPORTED_Asym_Pause),
 197        .flags          = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
 198
 199        /* basic functions */
 200        .config_aneg    = genphy_config_aneg,
 201        .read_status    = lan87xx_read_status,
 202        .config_init    = smsc_phy_config_init,
 203        .soft_reset     = smsc_phy_reset,
 204
 205        /* IRQ related */
 206        .ack_interrupt  = smsc_phy_ack_interrupt,
 207        .config_intr    = smsc_phy_config_intr,
 208
 209        .suspend        = genphy_suspend,
 210        .resume         = genphy_resume,
 211
 212        .driver         = { .owner = THIS_MODULE, }
 213}, {
 214        .phy_id         = 0x0007c0d0, /* OUI=0x00800f, Model#=0x0d */
 215        .phy_id_mask    = 0xfffffff0,
 216        .name           = "SMSC LAN911x Internal PHY",
 217
 218        .features       = (PHY_BASIC_FEATURES | SUPPORTED_Pause
 219                                | SUPPORTED_Asym_Pause),
 220        .flags          = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
 221
 222        /* basic functions */
 223        .config_aneg    = genphy_config_aneg,
 224        .read_status    = genphy_read_status,
 225        .config_init    = lan911x_config_init,
 226
 227        /* IRQ related */
 228        .ack_interrupt  = smsc_phy_ack_interrupt,
 229        .config_intr    = smsc_phy_config_intr,
 230
 231        .suspend        = genphy_suspend,
 232        .resume         = genphy_resume,
 233
 234        .driver         = { .owner = THIS_MODULE, }
 235}, {
 236        .phy_id         = 0x0007c0f0, /* OUI=0x00800f, Model#=0x0f */
 237        .phy_id_mask    = 0xfffffff0,
 238        .name           = "SMSC LAN8710/LAN8720",
 239
 240        .features       = (PHY_BASIC_FEATURES | SUPPORTED_Pause
 241                                | SUPPORTED_Asym_Pause),
 242        .flags          = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG,
 243
 244        /* basic functions */
 245        .config_aneg    = genphy_config_aneg,
 246        .read_status    = lan87xx_read_status,
 247        .config_init    = smsc_phy_config_init,
 248        .soft_reset     = smsc_phy_reset,
 249
 250        /* IRQ related */
 251        .ack_interrupt  = smsc_phy_ack_interrupt,
 252        .config_intr    = smsc_phy_config_intr,
 253
 254        .suspend        = genphy_suspend,
 255        .resume         = genphy_resume,
 256
 257        .driver         = { .owner = THIS_MODULE, }
 258} };
 259
 260module_phy_driver(smsc_phy_driver);
 261
 262MODULE_DESCRIPTION("SMSC PHY driver");
 263MODULE_AUTHOR("Herbert Valerio Riedel");
 264MODULE_LICENSE("GPL");
 265
 266static struct mdio_device_id __maybe_unused smsc_tbl[] = {
 267        { 0x0007c0a0, 0xfffffff0 },
 268        { 0x0007c0b0, 0xfffffff0 },
 269        { 0x0007c0c0, 0xfffffff0 },
 270        { 0x0007c0d0, 0xfffffff0 },
 271        { 0x0007c0f0, 0xfffffff0 },
 272        { }
 273};
 274
 275MODULE_DEVICE_TABLE(mdio, smsc_tbl);
 276