Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: phy: Add Wake-on-LAN driver for Microsemi PHYs.

Wake-on-LAN (WoL) is an Ethernet networking standard that allows
a computer/device to be turned on or awakened by a network message.

VSC8531 PHY can support this feature configure by driver set function.
WoL status get by driver get function.

Tested on Beaglebone Black with VSC 8531 PHY.

Signed-off-by: Raju Lakkaraju <Raju.Lakkaraju@microsemi.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Raju Lakkaraju and committed by
David S. Miller
0a55c12f ed2eb0fb

+128
+128
drivers/net/phy/mscc.c
··· 13 13 #include <linux/phy.h> 14 14 #include <linux/of.h> 15 15 #include <dt-bindings/net/mscc-phy-vsc8531.h> 16 + #include <linux/netdevice.h> 16 17 17 18 enum rgmii_rx_clock_delay { 18 19 RGMII_RX_CLK_DELAY_0_2_NS = 0, ··· 38 37 39 38 #define MII_VSC85XX_INT_MASK 25 40 39 #define MII_VSC85XX_INT_MASK_MASK 0xa000 40 + #define MII_VSC85XX_INT_MASK_WOL 0x0040 41 41 #define MII_VSC85XX_INT_STATUS 26 42 42 43 43 #define MSCC_PHY_WOL_MAC_CONTROL 27 ··· 53 51 #define MSCC_PHY_RGMII_CNTL 20 54 52 #define RGMII_RX_CLK_DELAY_MASK 0x0070 55 53 #define RGMII_RX_CLK_DELAY_POS 4 54 + 55 + #define MSCC_PHY_WOL_LOWER_MAC_ADDR 21 56 + #define MSCC_PHY_WOL_MID_MAC_ADDR 22 57 + #define MSCC_PHY_WOL_UPPER_MAC_ADDR 23 58 + #define MSCC_PHY_WOL_LOWER_PASSWD 24 59 + #define MSCC_PHY_WOL_MID_PASSWD 25 60 + #define MSCC_PHY_WOL_UPPER_PASSWD 26 61 + 62 + #define MSCC_PHY_WOL_MAC_CONTROL 27 63 + #define SECURE_ON_ENABLE 0x8000 64 + #define SECURE_ON_PASSWD_LEN_4 0x4000 56 65 57 66 /* Microsemi PHY ID's */ 58 67 #define PHY_ID_VSC8531 0x00070570 ··· 92 79 93 80 rc = phy_write(phydev, MSCC_EXT_PAGE_ACCESS, page); 94 81 return rc; 82 + } 83 + 84 + static int vsc85xx_wol_set(struct phy_device *phydev, 85 + struct ethtool_wolinfo *wol) 86 + { 87 + int rc; 88 + u16 reg_val; 89 + u8 i; 90 + u16 pwd[3] = {0, 0, 0}; 91 + struct ethtool_wolinfo *wol_conf = wol; 92 + u8 *mac_addr = phydev->attached_dev->dev_addr; 93 + 94 + mutex_lock(&phydev->lock); 95 + rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_EXTENDED_2); 96 + if (rc != 0) 97 + goto out_unlock; 98 + 99 + if (wol->wolopts & WAKE_MAGIC) { 100 + /* Store the device address for the magic packet */ 101 + for (i = 0; i < ARRAY_SIZE(pwd); i++) 102 + pwd[i] = mac_addr[5 - (i * 2 + 1)] << 8 | 103 + mac_addr[5 - i * 2]; 104 + phy_write(phydev, MSCC_PHY_WOL_LOWER_MAC_ADDR, pwd[0]); 105 + phy_write(phydev, MSCC_PHY_WOL_MID_MAC_ADDR, pwd[1]); 106 + phy_write(phydev, MSCC_PHY_WOL_UPPER_MAC_ADDR, pwd[2]); 107 + } else { 108 + phy_write(phydev, MSCC_PHY_WOL_LOWER_MAC_ADDR, 0); 109 + phy_write(phydev, MSCC_PHY_WOL_MID_MAC_ADDR, 0); 110 + phy_write(phydev, MSCC_PHY_WOL_UPPER_MAC_ADDR, 0); 111 + } 112 + 113 + if (wol_conf->wolopts & WAKE_MAGICSECURE) { 114 + for (i = 0; i < ARRAY_SIZE(pwd); i++) 115 + pwd[i] = wol_conf->sopass[5 - (i * 2 + 1)] << 8 | 116 + wol_conf->sopass[5 - i * 2]; 117 + phy_write(phydev, MSCC_PHY_WOL_LOWER_PASSWD, pwd[0]); 118 + phy_write(phydev, MSCC_PHY_WOL_MID_PASSWD, pwd[1]); 119 + phy_write(phydev, MSCC_PHY_WOL_UPPER_PASSWD, pwd[2]); 120 + } else { 121 + phy_write(phydev, MSCC_PHY_WOL_LOWER_PASSWD, 0); 122 + phy_write(phydev, MSCC_PHY_WOL_MID_PASSWD, 0); 123 + phy_write(phydev, MSCC_PHY_WOL_UPPER_PASSWD, 0); 124 + } 125 + 126 + reg_val = phy_read(phydev, MSCC_PHY_WOL_MAC_CONTROL); 127 + if (wol_conf->wolopts & WAKE_MAGICSECURE) 128 + reg_val |= SECURE_ON_ENABLE; 129 + else 130 + reg_val &= ~SECURE_ON_ENABLE; 131 + phy_write(phydev, MSCC_PHY_WOL_MAC_CONTROL, reg_val); 132 + 133 + rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_STANDARD); 134 + if (rc != 0) 135 + goto out_unlock; 136 + 137 + if (wol->wolopts & WAKE_MAGIC) { 138 + /* Enable the WOL interrupt */ 139 + reg_val = phy_read(phydev, MII_VSC85XX_INT_MASK); 140 + reg_val |= MII_VSC85XX_INT_MASK_WOL; 141 + rc = phy_write(phydev, MII_VSC85XX_INT_MASK, reg_val); 142 + if (rc != 0) 143 + goto out_unlock; 144 + } else { 145 + /* Disable the WOL interrupt */ 146 + reg_val = phy_read(phydev, MII_VSC85XX_INT_MASK); 147 + reg_val &= (~MII_VSC85XX_INT_MASK_WOL); 148 + rc = phy_write(phydev, MII_VSC85XX_INT_MASK, reg_val); 149 + if (rc != 0) 150 + goto out_unlock; 151 + } 152 + /* Clear WOL iterrupt status */ 153 + reg_val = phy_read(phydev, MII_VSC85XX_INT_STATUS); 154 + 155 + out_unlock: 156 + mutex_unlock(&phydev->lock); 157 + 158 + return rc; 159 + } 160 + 161 + static void vsc85xx_wol_get(struct phy_device *phydev, 162 + struct ethtool_wolinfo *wol) 163 + { 164 + int rc; 165 + u16 reg_val; 166 + u8 i; 167 + u16 pwd[3] = {0, 0, 0}; 168 + struct ethtool_wolinfo *wol_conf = wol; 169 + 170 + mutex_lock(&phydev->lock); 171 + rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_EXTENDED_2); 172 + if (rc != 0) 173 + goto out_unlock; 174 + 175 + reg_val = phy_read(phydev, MSCC_PHY_WOL_MAC_CONTROL); 176 + if (reg_val & SECURE_ON_ENABLE) 177 + wol_conf->wolopts |= WAKE_MAGICSECURE; 178 + if (wol_conf->wolopts & WAKE_MAGICSECURE) { 179 + pwd[0] = phy_read(phydev, MSCC_PHY_WOL_LOWER_PASSWD); 180 + pwd[1] = phy_read(phydev, MSCC_PHY_WOL_MID_PASSWD); 181 + pwd[2] = phy_read(phydev, MSCC_PHY_WOL_UPPER_PASSWD); 182 + for (i = 0; i < ARRAY_SIZE(pwd); i++) { 183 + wol_conf->sopass[5 - i * 2] = pwd[i] & 0x00ff; 184 + wol_conf->sopass[5 - (i * 2 + 1)] = (pwd[i] & 0xff00) 185 + >> 8; 186 + } 187 + } 188 + 189 + rc = vsc85xx_phy_page_set(phydev, MSCC_PHY_PAGE_STANDARD); 190 + 191 + out_unlock: 192 + mutex_unlock(&phydev->lock); 95 193 } 96 194 97 195 static u8 edge_rate_magic_get(u16 vddmac, ··· 425 301 .suspend = &genphy_suspend, 426 302 .resume = &genphy_resume, 427 303 .probe = &vsc85xx_probe, 304 + .set_wol = &vsc85xx_wol_set, 305 + .get_wol = &vsc85xx_wol_get, 428 306 }, 429 307 { 430 308 .phy_id = PHY_ID_VSC8541, ··· 444 318 .suspend = &genphy_suspend, 445 319 .resume = &genphy_resume, 446 320 .probe = &vsc85xx_probe, 321 + .set_wol = &vsc85xx_wol_set, 322 + .get_wol = &vsc85xx_wol_get, 447 323 } 448 324 449 325 };