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/of.h>
  24#include <linux/phy.h>
  25#include <linux/netdevice.h>
  26#include <linux/smscphy.h>
  27
  28struct smsc_hw_stat {
  29        const char *string;
  30        u8 reg;
  31        u8 bits;
  32};
  33
  34static struct smsc_hw_stat smsc_hw_stats[] = {
  35        { "phy_symbol_errors", 26, 16},
  36};
  37
  38struct smsc_phy_priv {
  39        bool energy_enable;
  40};
  41
  42static int smsc_phy_config_intr(struct phy_device *phydev)
  43{
  44        int rc = phy_write (phydev, MII_LAN83C185_IM,
  45                        ((PHY_INTERRUPT_ENABLED == phydev->interrupts)
  46                        ? MII_LAN83C185_ISF_INT_PHYLIB_EVENTS
  47                        : 0));
  48
  49        return rc < 0 ? rc : 0;
  50}
  51
  52static int smsc_phy_ack_interrupt(struct phy_device *phydev)
  53{
  54        int rc = phy_read (phydev, MII_LAN83C185_ISF);
  55
  56        return rc < 0 ? rc : 0;
  57}
  58
  59static int smsc_phy_config_init(struct phy_device *phydev)
  60{
  61        struct smsc_phy_priv *priv = phydev->priv;
  62
  63        int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
  64
  65        if (rc < 0)
  66                return rc;
  67
  68        if (priv->energy_enable) {
  69                /* Enable energy detect mode for this SMSC Transceivers */
  70                rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
  71                               rc | MII_LAN83C185_EDPWRDOWN);
  72                if (rc < 0)
  73                        return rc;
  74        }
  75
  76        return smsc_phy_ack_interrupt(phydev);
  77}
  78
  79static int smsc_phy_reset(struct phy_device *phydev)
  80{
  81        int rc = phy_read(phydev, MII_LAN83C185_SPECIAL_MODES);
  82        if (rc < 0)
  83                return rc;
  84
  85        /* If the SMSC PHY is in power down mode, then set it
  86         * in all capable mode before using it.
  87         */
  88        if ((rc & MII_LAN83C185_MODE_MASK) == MII_LAN83C185_MODE_POWERDOWN) {
  89                /* set "all capable" mode */
  90                rc |= MII_LAN83C185_MODE_ALL;
  91                phy_write(phydev, MII_LAN83C185_SPECIAL_MODES, rc);
  92        }
  93
  94        /* reset the phy */
  95        return genphy_soft_reset(phydev);
  96}
  97
  98static int lan911x_config_init(struct phy_device *phydev)
  99{
 100        return smsc_phy_ack_interrupt(phydev);
 101}
 102
 103/*
 104 * The LAN87xx suffers from rare absence of the ENERGYON-bit when Ethernet cable
 105 * plugs in while LAN87xx is in Energy Detect Power-Down mode. This leads to
 106 * unstable detection of plugging in Ethernet cable.
 107 * This workaround disables Energy Detect Power-Down mode and waiting for
 108 * response on link pulses to detect presence of plugged Ethernet cable.
 109 * The Energy Detect Power-Down mode is enabled again in the end of procedure to
 110 * save approximately 220 mW of power if cable is unplugged.
 111 */
 112static int lan87xx_read_status(struct phy_device *phydev)
 113{
 114        struct smsc_phy_priv *priv = phydev->priv;
 115
 116        int err = genphy_read_status(phydev);
 117
 118        if (!phydev->link && priv->energy_enable) {
 119                int i;
 120
 121                /* Disable EDPD to wake up PHY */
 122                int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
 123                if (rc < 0)
 124                        return rc;
 125
 126                rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
 127                               rc & ~MII_LAN83C185_EDPWRDOWN);
 128                if (rc < 0)
 129                        return rc;
 130
 131                /* Wait max 640 ms to detect energy */
 132                for (i = 0; i < 64; i++) {
 133                        /* Sleep to allow link test pulses to be sent */
 134                        msleep(10);
 135                        rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
 136                        if (rc < 0)
 137                                return rc;
 138                        if (rc & MII_LAN83C185_ENERGYON)
 139                                break;
 140                }
 141
 142                /* Re-enable EDPD */
 143                rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
 144                if (rc < 0)
 145                        return rc;
 146
 147                rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
 148                               rc | MII_LAN83C185_EDPWRDOWN);
 149                if (rc < 0)
 150                        return rc;
 151        }
 152
 153        return err;
 154}
 155
 156static int smsc_get_sset_count(struct phy_device *phydev)
 157{
 158        return ARRAY_SIZE(smsc_hw_stats);
 159}
 160
 161static void smsc_get_strings(struct phy_device *phydev, u8 *data)
 162{
 163        int i;
 164
 165        for (i = 0; i < ARRAY_SIZE(smsc_hw_stats); i++) {
 166                strncpy(data + i * ETH_GSTRING_LEN,
 167                       smsc_hw_stats[i].string, ETH_GSTRING_LEN);
 168        }
 169}
 170
 171static u64 smsc_get_stat(struct phy_device *phydev, int i)
 172{
 173        struct smsc_hw_stat stat = smsc_hw_stats[i];
 174        int val;
 175        u64 ret;
 176
 177        val = phy_read(phydev, stat.reg);
 178        if (val < 0)
 179                ret = U64_MAX;
 180        else
 181                ret = val;
 182
 183        return ret;
 184}
 185
 186static void smsc_get_stats(struct phy_device *phydev,
 187                           struct ethtool_stats *stats, u64 *data)
 188{
 189        int i;
 190
 191        for (i = 0; i < ARRAY_SIZE(smsc_hw_stats); i++)
 192                data[i] = smsc_get_stat(phydev, i);
 193}
 194
 195static int smsc_phy_probe(struct phy_device *phydev)
 196{
 197        struct device *dev = &phydev->mdio.dev;
 198        struct device_node *of_node = dev->of_node;
 199        struct smsc_phy_priv *priv;
 200
 201        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 202        if (!priv)
 203                return -ENOMEM;
 204
 205        priv->energy_enable = true;
 206
 207        if (of_property_read_bool(of_node, "smsc,disable-energy-detect"))
 208                priv->energy_enable = false;
 209
 210        phydev->priv = priv;
 211
 212        return 0;
 213}
 214
 215static struct phy_driver smsc_phy_driver[] = {
 216{
 217        .phy_id         = 0x0007c0a0, /* OUI=0x00800f, Model#=0x0a */
 218        .phy_id_mask    = 0xfffffff0,
 219        .name           = "SMSC LAN83C185",
 220
 221        .features       = PHY_BASIC_FEATURES,
 222        .flags          = PHY_HAS_INTERRUPT,
 223
 224        .probe          = smsc_phy_probe,
 225
 226        /* basic functions */
 227        .config_init    = smsc_phy_config_init,
 228        .soft_reset     = smsc_phy_reset,
 229
 230        /* IRQ related */
 231        .ack_interrupt  = smsc_phy_ack_interrupt,
 232        .config_intr    = smsc_phy_config_intr,
 233
 234        .suspend        = genphy_suspend,
 235        .resume         = genphy_resume,
 236}, {
 237        .phy_id         = 0x0007c0b0, /* OUI=0x00800f, Model#=0x0b */
 238        .phy_id_mask    = 0xfffffff0,
 239        .name           = "SMSC LAN8187",
 240
 241        .features       = PHY_BASIC_FEATURES,
 242        .flags          = PHY_HAS_INTERRUPT,
 243
 244        .probe          = smsc_phy_probe,
 245
 246        /* basic functions */
 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        /* Statistics */
 255        .get_sset_count = smsc_get_sset_count,
 256        .get_strings    = smsc_get_strings,
 257        .get_stats      = smsc_get_stats,
 258
 259        .suspend        = genphy_suspend,
 260        .resume         = genphy_resume,
 261}, {
 262        .phy_id         = 0x0007c0c0, /* OUI=0x00800f, Model#=0x0c */
 263        .phy_id_mask    = 0xfffffff0,
 264        .name           = "SMSC LAN8700",
 265
 266        .features       = PHY_BASIC_FEATURES,
 267        .flags          = PHY_HAS_INTERRUPT,
 268
 269        .probe          = smsc_phy_probe,
 270
 271        /* basic functions */
 272        .read_status    = lan87xx_read_status,
 273        .config_init    = smsc_phy_config_init,
 274        .soft_reset     = smsc_phy_reset,
 275
 276        /* IRQ related */
 277        .ack_interrupt  = smsc_phy_ack_interrupt,
 278        .config_intr    = smsc_phy_config_intr,
 279
 280        /* Statistics */
 281        .get_sset_count = smsc_get_sset_count,
 282        .get_strings    = smsc_get_strings,
 283        .get_stats      = smsc_get_stats,
 284
 285        .suspend        = genphy_suspend,
 286        .resume         = genphy_resume,
 287}, {
 288        .phy_id         = 0x0007c0d0, /* OUI=0x00800f, Model#=0x0d */
 289        .phy_id_mask    = 0xfffffff0,
 290        .name           = "SMSC LAN911x Internal PHY",
 291
 292        .features       = PHY_BASIC_FEATURES,
 293        .flags          = PHY_HAS_INTERRUPT,
 294
 295        .probe          = smsc_phy_probe,
 296
 297        /* basic functions */
 298        .config_init    = lan911x_config_init,
 299
 300        /* IRQ related */
 301        .ack_interrupt  = smsc_phy_ack_interrupt,
 302        .config_intr    = smsc_phy_config_intr,
 303
 304        .suspend        = genphy_suspend,
 305        .resume         = genphy_resume,
 306}, {
 307        .phy_id         = 0x0007c0f0, /* OUI=0x00800f, Model#=0x0f */
 308        .phy_id_mask    = 0xfffffff0,
 309        .name           = "SMSC LAN8710/LAN8720",
 310
 311        .features       = PHY_BASIC_FEATURES,
 312        .flags          = PHY_HAS_INTERRUPT | PHY_RST_AFTER_CLK_EN,
 313
 314        .probe          = smsc_phy_probe,
 315
 316        /* basic functions */
 317        .read_status    = lan87xx_read_status,
 318        .config_init    = smsc_phy_config_init,
 319        .soft_reset     = smsc_phy_reset,
 320
 321        /* IRQ related */
 322        .ack_interrupt  = smsc_phy_ack_interrupt,
 323        .config_intr    = smsc_phy_config_intr,
 324
 325        /* Statistics */
 326        .get_sset_count = smsc_get_sset_count,
 327        .get_strings    = smsc_get_strings,
 328        .get_stats      = smsc_get_stats,
 329
 330        .suspend        = genphy_suspend,
 331        .resume         = genphy_resume,
 332}, {
 333        .phy_id         = 0x0007c110,
 334        .phy_id_mask    = 0xfffffff0,
 335        .name           = "SMSC LAN8740",
 336
 337        .features       = PHY_BASIC_FEATURES,
 338        .flags          = PHY_HAS_INTERRUPT,
 339
 340        .probe          = smsc_phy_probe,
 341
 342        /* basic functions */
 343        .read_status    = lan87xx_read_status,
 344        .config_init    = smsc_phy_config_init,
 345        .soft_reset     = smsc_phy_reset,
 346
 347        /* IRQ related */
 348        .ack_interrupt  = smsc_phy_ack_interrupt,
 349        .config_intr    = smsc_phy_config_intr,
 350
 351        /* Statistics */
 352        .get_sset_count = smsc_get_sset_count,
 353        .get_strings    = smsc_get_strings,
 354        .get_stats      = smsc_get_stats,
 355
 356        .suspend        = genphy_suspend,
 357        .resume         = genphy_resume,
 358} };
 359
 360module_phy_driver(smsc_phy_driver);
 361
 362MODULE_DESCRIPTION("SMSC PHY driver");
 363MODULE_AUTHOR("Herbert Valerio Riedel");
 364MODULE_LICENSE("GPL");
 365
 366static struct mdio_device_id __maybe_unused smsc_tbl[] = {
 367        { 0x0007c0a0, 0xfffffff0 },
 368        { 0x0007c0b0, 0xfffffff0 },
 369        { 0x0007c0c0, 0xfffffff0 },
 370        { 0x0007c0d0, 0xfffffff0 },
 371        { 0x0007c0f0, 0xfffffff0 },
 372        { 0x0007c110, 0xfffffff0 },
 373        { }
 374};
 375
 376MODULE_DEVICE_TABLE(mdio, smsc_tbl);
 377