linux/drivers/net/phy/microchip.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2015 Microchip Technology
   3 *
   4 * This program is free software; you can redistribute it and/or
   5 * modify it under the terms of the GNU General Public License
   6 * as published by the Free Software Foundation; either version 2
   7 * of the License, or (at your option) any later version.
   8 *
   9 * This program is distributed in the hope that it will be useful,
  10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12 * GNU General Public License for more details.
  13 *
  14 * You should have received a copy of the GNU General Public License
  15 * along with this program; if not, see <http://www.gnu.org/licenses/>.
  16 */
  17#include <linux/kernel.h>
  18#include <linux/module.h>
  19#include <linux/mii.h>
  20#include <linux/ethtool.h>
  21#include <linux/phy.h>
  22#include <linux/microchipphy.h>
  23
  24#define DRIVER_AUTHOR   "WOOJUNG HUH <woojung.huh@microchip.com>"
  25#define DRIVER_DESC     "Microchip LAN88XX PHY driver"
  26
  27struct lan88xx_priv {
  28        int     chip_id;
  29        int     chip_rev;
  30        __u32   wolopts;
  31};
  32
  33static int lan88xx_phy_config_intr(struct phy_device *phydev)
  34{
  35        int rc;
  36
  37        if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
  38                /* unmask all source and clear them before enable */
  39                rc = phy_write(phydev, LAN88XX_INT_MASK, 0x7FFF);
  40                rc = phy_read(phydev, LAN88XX_INT_STS);
  41                rc = phy_write(phydev, LAN88XX_INT_MASK,
  42                               LAN88XX_INT_MASK_MDINTPIN_EN_ |
  43                               LAN88XX_INT_MASK_LINK_CHANGE_);
  44        } else {
  45                rc = phy_write(phydev, LAN88XX_INT_MASK, 0);
  46        }
  47
  48        return rc < 0 ? rc : 0;
  49}
  50
  51static int lan88xx_phy_ack_interrupt(struct phy_device *phydev)
  52{
  53        int rc = phy_read(phydev, LAN88XX_INT_STS);
  54
  55        return rc < 0 ? rc : 0;
  56}
  57
  58static int lan88xx_suspend(struct phy_device *phydev)
  59{
  60        struct lan88xx_priv *priv = phydev->priv;
  61
  62        /* do not power down PHY when WOL is enabled */
  63        if (!priv->wolopts)
  64                genphy_suspend(phydev);
  65
  66        return 0;
  67}
  68
  69static int lan88xx_probe(struct phy_device *phydev)
  70{
  71        struct device *dev = &phydev->mdio.dev;
  72        struct lan88xx_priv *priv;
  73
  74        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
  75        if (!priv)
  76                return -ENOMEM;
  77
  78        priv->wolopts = 0;
  79
  80        /* these values can be used to identify internal PHY */
  81        priv->chip_id = phy_read_mmd(phydev, 3, LAN88XX_MMD3_CHIP_ID);
  82        priv->chip_rev = phy_read_mmd(phydev, 3, LAN88XX_MMD3_CHIP_REV);
  83
  84        phydev->priv = priv;
  85
  86        return 0;
  87}
  88
  89static void lan88xx_remove(struct phy_device *phydev)
  90{
  91        struct device *dev = &phydev->mdio.dev;
  92        struct lan88xx_priv *priv = phydev->priv;
  93
  94        if (priv)
  95                devm_kfree(dev, priv);
  96}
  97
  98static int lan88xx_set_wol(struct phy_device *phydev,
  99                           struct ethtool_wolinfo *wol)
 100{
 101        struct lan88xx_priv *priv = phydev->priv;
 102
 103        priv->wolopts = wol->wolopts;
 104
 105        return 0;
 106}
 107
 108static void lan88xx_set_mdix(struct phy_device *phydev)
 109{
 110        int buf;
 111        int val;
 112
 113        switch (phydev->mdix_ctrl) {
 114        case ETH_TP_MDI:
 115                val = LAN88XX_EXT_MODE_CTRL_MDI_;
 116                break;
 117        case ETH_TP_MDI_X:
 118                val = LAN88XX_EXT_MODE_CTRL_MDI_X_;
 119                break;
 120        case ETH_TP_MDI_AUTO:
 121                val = LAN88XX_EXT_MODE_CTRL_AUTO_MDIX_;
 122                break;
 123        default:
 124                return;
 125        }
 126
 127        phy_write(phydev, LAN88XX_EXT_PAGE_ACCESS, LAN88XX_EXT_PAGE_SPACE_1);
 128        buf = phy_read(phydev, LAN88XX_EXT_MODE_CTRL);
 129        buf &= ~LAN88XX_EXT_MODE_CTRL_MDIX_MASK_;
 130        buf |= val;
 131        phy_write(phydev, LAN88XX_EXT_MODE_CTRL, buf);
 132        phy_write(phydev, LAN88XX_EXT_PAGE_ACCESS, LAN88XX_EXT_PAGE_SPACE_0);
 133}
 134
 135static int lan88xx_config_aneg(struct phy_device *phydev)
 136{
 137        lan88xx_set_mdix(phydev);
 138
 139        return genphy_config_aneg(phydev);
 140}
 141
 142static struct phy_driver microchip_phy_driver[] = {
 143{
 144        .phy_id         = 0x0007c130,
 145        .phy_id_mask    = 0xfffffff0,
 146        .name           = "Microchip LAN88xx",
 147
 148        .features       = PHY_GBIT_FEATURES,
 149        .flags          = PHY_HAS_INTERRUPT,
 150
 151        .probe          = lan88xx_probe,
 152        .remove         = lan88xx_remove,
 153
 154        .config_init    = genphy_config_init,
 155        .config_aneg    = lan88xx_config_aneg,
 156
 157        .ack_interrupt  = lan88xx_phy_ack_interrupt,
 158        .config_intr    = lan88xx_phy_config_intr,
 159
 160        .suspend        = lan88xx_suspend,
 161        .resume         = genphy_resume,
 162        .set_wol        = lan88xx_set_wol,
 163} };
 164
 165module_phy_driver(microchip_phy_driver);
 166
 167static struct mdio_device_id __maybe_unused microchip_tbl[] = {
 168        { 0x0007c130, 0xfffffff0 },
 169        { }
 170};
 171
 172MODULE_DEVICE_TABLE(mdio, microchip_tbl);
 173
 174MODULE_AUTHOR(DRIVER_AUTHOR);
 175MODULE_DESCRIPTION(DRIVER_DESC);
 176MODULE_LICENSE("GPL");
 177