|  | /* | 
|  | * Copyright (C) 2015 Microchip Technology | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU General Public License | 
|  | * as published by the Free Software Foundation; either version 2 | 
|  | * of the License, or (at your option) any later version. | 
|  | * | 
|  | * This program is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | * GNU General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU General Public License | 
|  | * along with this program; if not, see <http://www.gnu.org/licenses/>. | 
|  | */ | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/mii.h> | 
|  | #include <linux/ethtool.h> | 
|  | #include <linux/phy.h> | 
|  | #include <linux/microchipphy.h> | 
|  |  | 
|  | #define DRIVER_AUTHOR	"WOOJUNG HUH <woojung.huh@microchip.com>" | 
|  | #define DRIVER_DESC	"Microchip LAN88XX PHY driver" | 
|  |  | 
|  | struct lan88xx_priv { | 
|  | int	chip_id; | 
|  | int	chip_rev; | 
|  | __u32	wolopts; | 
|  | }; | 
|  |  | 
|  | static int lan88xx_phy_config_intr(struct phy_device *phydev) | 
|  | { | 
|  | int rc; | 
|  |  | 
|  | if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { | 
|  | /* unmask all source and clear them before enable */ | 
|  | rc = phy_write(phydev, LAN88XX_INT_MASK, 0x7FFF); | 
|  | rc = phy_read(phydev, LAN88XX_INT_STS); | 
|  | rc = phy_write(phydev, LAN88XX_INT_MASK, | 
|  | LAN88XX_INT_MASK_MDINTPIN_EN_ | | 
|  | LAN88XX_INT_MASK_LINK_CHANGE_); | 
|  | } else { | 
|  | rc = phy_write(phydev, LAN88XX_INT_MASK, 0); | 
|  | } | 
|  |  | 
|  | return rc < 0 ? rc : 0; | 
|  | } | 
|  |  | 
|  | static int lan88xx_phy_ack_interrupt(struct phy_device *phydev) | 
|  | { | 
|  | int rc = phy_read(phydev, LAN88XX_INT_STS); | 
|  |  | 
|  | return rc < 0 ? rc : 0; | 
|  | } | 
|  |  | 
|  | static int lan88xx_suspend(struct phy_device *phydev) | 
|  | { | 
|  | struct lan88xx_priv *priv = phydev->priv; | 
|  |  | 
|  | /* do not power down PHY when WOL is enabled */ | 
|  | if (!priv->wolopts) | 
|  | genphy_suspend(phydev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int lan88xx_probe(struct phy_device *phydev) | 
|  | { | 
|  | struct device *dev = &phydev->mdio.dev; | 
|  | struct lan88xx_priv *priv; | 
|  |  | 
|  | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | 
|  | if (!priv) | 
|  | return -ENOMEM; | 
|  |  | 
|  | priv->wolopts = 0; | 
|  |  | 
|  | /* these values can be used to identify internal PHY */ | 
|  | priv->chip_id = phy_read_mmd(phydev, 3, LAN88XX_MMD3_CHIP_ID); | 
|  | priv->chip_rev = phy_read_mmd(phydev, 3, LAN88XX_MMD3_CHIP_REV); | 
|  |  | 
|  | phydev->priv = priv; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void lan88xx_remove(struct phy_device *phydev) | 
|  | { | 
|  | struct device *dev = &phydev->mdio.dev; | 
|  | struct lan88xx_priv *priv = phydev->priv; | 
|  |  | 
|  | if (priv) | 
|  | devm_kfree(dev, priv); | 
|  | } | 
|  |  | 
|  | static int lan88xx_set_wol(struct phy_device *phydev, | 
|  | struct ethtool_wolinfo *wol) | 
|  | { | 
|  | struct lan88xx_priv *priv = phydev->priv; | 
|  |  | 
|  | priv->wolopts = wol->wolopts; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void lan88xx_set_mdix(struct phy_device *phydev) | 
|  | { | 
|  | int buf; | 
|  | int val; | 
|  |  | 
|  | switch (phydev->mdix_ctrl) { | 
|  | case ETH_TP_MDI: | 
|  | val = LAN88XX_EXT_MODE_CTRL_MDI_; | 
|  | break; | 
|  | case ETH_TP_MDI_X: | 
|  | val = LAN88XX_EXT_MODE_CTRL_MDI_X_; | 
|  | break; | 
|  | case ETH_TP_MDI_AUTO: | 
|  | val = LAN88XX_EXT_MODE_CTRL_AUTO_MDIX_; | 
|  | break; | 
|  | default: | 
|  | return; | 
|  | } | 
|  |  | 
|  | phy_write(phydev, LAN88XX_EXT_PAGE_ACCESS, LAN88XX_EXT_PAGE_SPACE_1); | 
|  | buf = phy_read(phydev, LAN88XX_EXT_MODE_CTRL); | 
|  | buf &= ~LAN88XX_EXT_MODE_CTRL_MDIX_MASK_; | 
|  | buf |= val; | 
|  | phy_write(phydev, LAN88XX_EXT_MODE_CTRL, buf); | 
|  | phy_write(phydev, LAN88XX_EXT_PAGE_ACCESS, LAN88XX_EXT_PAGE_SPACE_0); | 
|  | } | 
|  |  | 
|  | static int lan88xx_config_aneg(struct phy_device *phydev) | 
|  | { | 
|  | lan88xx_set_mdix(phydev); | 
|  |  | 
|  | return genphy_config_aneg(phydev); | 
|  | } | 
|  |  | 
|  | static struct phy_driver microchip_phy_driver[] = { | 
|  | { | 
|  | .phy_id		= 0x0007c130, | 
|  | .phy_id_mask	= 0xfffffff0, | 
|  | .name		= "Microchip LAN88xx", | 
|  |  | 
|  | .features	= PHY_GBIT_FEATURES, | 
|  | .flags		= PHY_HAS_INTERRUPT, | 
|  |  | 
|  | .probe		= lan88xx_probe, | 
|  | .remove		= lan88xx_remove, | 
|  |  | 
|  | .config_init	= genphy_config_init, | 
|  | .config_aneg	= lan88xx_config_aneg, | 
|  | .read_status	= genphy_read_status, | 
|  |  | 
|  | .ack_interrupt	= lan88xx_phy_ack_interrupt, | 
|  | .config_intr	= lan88xx_phy_config_intr, | 
|  |  | 
|  | .suspend	= lan88xx_suspend, | 
|  | .resume		= genphy_resume, | 
|  | .set_wol	= lan88xx_set_wol, | 
|  | } }; | 
|  |  | 
|  | module_phy_driver(microchip_phy_driver); | 
|  |  | 
|  | static struct mdio_device_id __maybe_unused microchip_tbl[] = { | 
|  | { 0x0007c130, 0xfffffff0 }, | 
|  | { } | 
|  | }; | 
|  |  | 
|  | MODULE_DEVICE_TABLE(mdio, microchip_tbl); | 
|  |  | 
|  | MODULE_AUTHOR(DRIVER_AUTHOR); | 
|  | MODULE_DESCRIPTION(DRIVER_DESC); | 
|  | MODULE_LICENSE("GPL"); |