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

net: phy: smsc: add WoL support to LAN8740/LAN8742 PHYs

Microchip LAN8740/LAN8742 PHYs support basic unicast, broadcast, and
Magic Packet WoL. They have one pattern filter matching up to 128 bytes
of frame data, which can be used to implement ARP or multicast WoL.

ARP WoL matches any ARP frame with broadcast address.

Multicast WoL matches any multicast frame.

Signed-off-by: Tristram Ha <Tristram.Ha@microchip.com>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
Reviewed-by: Simon Horman <simon.horman@corigine.com>
Link: https://lore.kernel.org/r/1690329270-2873-1-git-send-email-Tristram.Ha@microchip.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Tristram Ha and committed by
Jakub Kicinski
8b305ee2 2303fae1

+285 -2
+1
drivers/net/phy/Kconfig
··· 350 350 351 351 config SMSC_PHY 352 352 tristate "SMSC PHYs" 353 + select CRC16 353 354 help 354 355 Currently supports the LAN83C185, LAN8187 and LAN8700 PHYs 355 356
+250 -2
drivers/net/phy/smsc.c
··· 20 20 #include <linux/of.h> 21 21 #include <linux/phy.h> 22 22 #include <linux/netdevice.h> 23 + #include <linux/crc16.h> 24 + #include <linux/etherdevice.h> 23 25 #include <linux/smscphy.h> 24 26 25 27 /* Vendor-specific PHY Definitions */ ··· 53 51 unsigned int edpd_enable:1; 54 52 unsigned int edpd_mode_set_by_user:1; 55 53 unsigned int edpd_max_wait_ms; 54 + bool wol_arp; 56 55 }; 57 56 58 57 static int smsc_phy_ack_interrupt(struct phy_device *phydev) ··· 260 257 return err; 261 258 } 262 259 EXPORT_SYMBOL_GPL(lan87xx_read_status); 260 + 261 + static int lan874x_phy_config_init(struct phy_device *phydev) 262 + { 263 + u16 val; 264 + int rc; 265 + 266 + /* Setup LED2/nINT/nPME pin to function as nPME. May need user option 267 + * to use LED1/nINT/nPME. 268 + */ 269 + val = MII_LAN874X_PHY_PME2_SET; 270 + 271 + /* The bits MII_LAN874X_PHY_WOL_PFDA_FR, MII_LAN874X_PHY_WOL_WUFR, 272 + * MII_LAN874X_PHY_WOL_MPR, and MII_LAN874X_PHY_WOL_BCAST_FR need to 273 + * be cleared to de-assert PME signal after a WoL event happens, but 274 + * using PME auto clear gets around that. 275 + */ 276 + val |= MII_LAN874X_PHY_PME_SELF_CLEAR; 277 + rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR, 278 + val); 279 + if (rc < 0) 280 + return rc; 281 + 282 + /* set nPME self clear delay time */ 283 + rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_MCFGR, 284 + MII_LAN874X_PHY_PME_SELF_CLEAR_DELAY); 285 + if (rc < 0) 286 + return rc; 287 + 288 + return smsc_phy_config_init(phydev); 289 + } 290 + 291 + static void lan874x_get_wol(struct phy_device *phydev, 292 + struct ethtool_wolinfo *wol) 293 + { 294 + struct smsc_phy_priv *priv = phydev->priv; 295 + int rc; 296 + 297 + wol->supported = (WAKE_UCAST | WAKE_BCAST | WAKE_MAGIC | 298 + WAKE_ARP | WAKE_MCAST); 299 + wol->wolopts = 0; 300 + 301 + rc = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR); 302 + if (rc < 0) 303 + return; 304 + 305 + if (rc & MII_LAN874X_PHY_WOL_PFDAEN) 306 + wol->wolopts |= WAKE_UCAST; 307 + 308 + if (rc & MII_LAN874X_PHY_WOL_BCSTEN) 309 + wol->wolopts |= WAKE_BCAST; 310 + 311 + if (rc & MII_LAN874X_PHY_WOL_MPEN) 312 + wol->wolopts |= WAKE_MAGIC; 313 + 314 + if (rc & MII_LAN874X_PHY_WOL_WUEN) { 315 + if (priv->wol_arp) 316 + wol->wolopts |= WAKE_ARP; 317 + else 318 + wol->wolopts |= WAKE_MCAST; 319 + } 320 + } 321 + 322 + static u16 smsc_crc16(const u8 *buffer, size_t len) 323 + { 324 + return bitrev16(crc16(0xFFFF, buffer, len)); 325 + } 326 + 327 + static int lan874x_chk_wol_pattern(const u8 pattern[], const u16 *mask, 328 + u8 len, u8 *data, u8 *datalen) 329 + { 330 + size_t i, j, k; 331 + int ret = 0; 332 + u16 bits; 333 + 334 + /* Pattern filtering can match up to 128 bytes of frame data. There 335 + * are 8 registers to program the 16-bit masks, where each bit means 336 + * the byte will be compared. The frame data will then go through a 337 + * CRC16 calculation for hardware comparison. This helper function 338 + * makes sure only relevant frame data are included in this 339 + * calculation. It provides a warning when the masks and expected 340 + * data size do not match. 341 + */ 342 + i = 0; 343 + k = 0; 344 + while (len > 0) { 345 + bits = *mask; 346 + for (j = 0; j < 16; j++, i++, len--) { 347 + /* No more pattern. */ 348 + if (!len) { 349 + /* The rest of bitmap is not empty. */ 350 + if (bits) 351 + ret = i + 1; 352 + break; 353 + } 354 + if (bits & 1) 355 + data[k++] = pattern[i]; 356 + bits >>= 1; 357 + } 358 + mask++; 359 + } 360 + *datalen = k; 361 + return ret; 362 + } 363 + 364 + static int lan874x_set_wol_pattern(struct phy_device *phydev, u16 val, 365 + const u8 data[], u8 datalen, 366 + const u16 *mask, u8 masklen) 367 + { 368 + u16 crc, reg; 369 + int rc; 370 + 371 + /* Starting pattern offset is set before calling this function. */ 372 + val |= MII_LAN874X_PHY_WOL_FILTER_EN; 373 + rc = phy_write_mmd(phydev, MDIO_MMD_PCS, 374 + MII_LAN874X_PHY_MMD_WOL_WUF_CFGA, val); 375 + if (rc < 0) 376 + return rc; 377 + 378 + crc = smsc_crc16(data, datalen); 379 + rc = phy_write_mmd(phydev, MDIO_MMD_PCS, 380 + MII_LAN874X_PHY_MMD_WOL_WUF_CFGB, crc); 381 + if (rc < 0) 382 + return rc; 383 + 384 + masklen = (masklen + 15) & ~0xf; 385 + reg = MII_LAN874X_PHY_MMD_WOL_WUF_MASK7; 386 + while (masklen >= 16) { 387 + rc = phy_write_mmd(phydev, MDIO_MMD_PCS, reg, *mask); 388 + if (rc < 0) 389 + return rc; 390 + reg--; 391 + mask++; 392 + masklen -= 16; 393 + } 394 + 395 + /* Clear out the rest of mask registers. */ 396 + while (reg != MII_LAN874X_PHY_MMD_WOL_WUF_MASK0) { 397 + phy_write_mmd(phydev, MDIO_MMD_PCS, reg, 0); 398 + reg--; 399 + } 400 + return rc; 401 + } 402 + 403 + static int lan874x_set_wol(struct phy_device *phydev, 404 + struct ethtool_wolinfo *wol) 405 + { 406 + struct net_device *ndev = phydev->attached_dev; 407 + struct smsc_phy_priv *priv = phydev->priv; 408 + u16 val, val_wucsr; 409 + u8 data[128]; 410 + u8 datalen; 411 + int rc; 412 + 413 + /* lan874x has only one WoL filter pattern */ 414 + if ((wol->wolopts & (WAKE_ARP | WAKE_MCAST)) == 415 + (WAKE_ARP | WAKE_MCAST)) { 416 + phydev_info(phydev, 417 + "lan874x WoL supports one of ARP|MCAST at a time\n"); 418 + return -EOPNOTSUPP; 419 + } 420 + 421 + rc = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR); 422 + if (rc < 0) 423 + return rc; 424 + 425 + val_wucsr = rc; 426 + 427 + if (wol->wolopts & WAKE_UCAST) 428 + val_wucsr |= MII_LAN874X_PHY_WOL_PFDAEN; 429 + else 430 + val_wucsr &= ~MII_LAN874X_PHY_WOL_PFDAEN; 431 + 432 + if (wol->wolopts & WAKE_BCAST) 433 + val_wucsr |= MII_LAN874X_PHY_WOL_BCSTEN; 434 + else 435 + val_wucsr &= ~MII_LAN874X_PHY_WOL_BCSTEN; 436 + 437 + if (wol->wolopts & WAKE_MAGIC) 438 + val_wucsr |= MII_LAN874X_PHY_WOL_MPEN; 439 + else 440 + val_wucsr &= ~MII_LAN874X_PHY_WOL_MPEN; 441 + 442 + /* Need to use pattern matching */ 443 + if (wol->wolopts & (WAKE_ARP | WAKE_MCAST)) 444 + val_wucsr |= MII_LAN874X_PHY_WOL_WUEN; 445 + else 446 + val_wucsr &= ~MII_LAN874X_PHY_WOL_WUEN; 447 + 448 + if (wol->wolopts & WAKE_ARP) { 449 + const u8 pattern[2] = { 0x08, 0x06 }; 450 + const u16 mask[1] = { 0x0003 }; 451 + 452 + rc = lan874x_chk_wol_pattern(pattern, mask, 2, data, 453 + &datalen); 454 + if (rc) 455 + phydev_dbg(phydev, "pattern not valid at %d\n", rc); 456 + 457 + /* Need to match broadcast destination address and provided 458 + * data pattern at offset 12. 459 + */ 460 + val = 12 | MII_LAN874X_PHY_WOL_FILTER_BCSTEN; 461 + rc = lan874x_set_wol_pattern(phydev, val, data, datalen, mask, 462 + 2); 463 + if (rc < 0) 464 + return rc; 465 + priv->wol_arp = true; 466 + } 467 + 468 + if (wol->wolopts & WAKE_MCAST) { 469 + /* Need to match multicast destination address. */ 470 + val = MII_LAN874X_PHY_WOL_FILTER_MCASTTEN; 471 + rc = lan874x_set_wol_pattern(phydev, val, data, 0, NULL, 0); 472 + if (rc < 0) 473 + return rc; 474 + priv->wol_arp = false; 475 + } 476 + 477 + if (wol->wolopts & (WAKE_MAGIC | WAKE_UCAST)) { 478 + const u8 *mac = (const u8 *)ndev->dev_addr; 479 + int i, reg; 480 + 481 + reg = MII_LAN874X_PHY_MMD_WOL_RX_ADDRC; 482 + for (i = 0; i < 6; i += 2, reg--) { 483 + rc = phy_write_mmd(phydev, MDIO_MMD_PCS, reg, 484 + ((mac[i + 1] << 8) | mac[i])); 485 + if (rc < 0) 486 + return rc; 487 + } 488 + } 489 + 490 + rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR, 491 + val_wucsr); 492 + if (rc < 0) 493 + return rc; 494 + 495 + return 0; 496 + } 263 497 264 498 static int smsc_get_sset_count(struct phy_device *phydev) 265 499 { ··· 773 533 774 534 /* basic functions */ 775 535 .read_status = lan87xx_read_status, 776 - .config_init = smsc_phy_config_init, 536 + .config_init = lan874x_phy_config_init, 777 537 .soft_reset = smsc_phy_reset, 778 538 779 539 /* IRQ related */ ··· 787 547 788 548 .get_tunable = smsc_phy_get_tunable, 789 549 .set_tunable = smsc_phy_set_tunable, 550 + 551 + /* WoL */ 552 + .set_wol = lan874x_set_wol, 553 + .get_wol = lan874x_get_wol, 790 554 791 555 .suspend = genphy_suspend, 792 556 .resume = genphy_resume, ··· 810 566 811 567 /* basic functions */ 812 568 .read_status = lan87xx_read_status, 813 - .config_init = smsc_phy_config_init, 569 + .config_init = lan874x_phy_config_init, 814 570 .soft_reset = smsc_phy_reset, 815 571 816 572 /* IRQ related */ ··· 824 580 825 581 .get_tunable = smsc_phy_get_tunable, 826 582 .set_tunable = smsc_phy_set_tunable, 583 + 584 + /* WoL */ 585 + .set_wol = lan874x_set_wol, 586 + .get_wol = lan874x_get_wol, 827 587 828 588 .suspend = genphy_suspend, 829 589 .resume = genphy_resume,
+34
include/linux/smscphy.h
··· 38 38 struct ethtool_tunable *tuna, const void *data); 39 39 int smsc_phy_probe(struct phy_device *phydev); 40 40 41 + #define MII_LAN874X_PHY_MMD_WOL_WUCSR 0x8010 42 + #define MII_LAN874X_PHY_MMD_WOL_WUF_CFGA 0x8011 43 + #define MII_LAN874X_PHY_MMD_WOL_WUF_CFGB 0x8012 44 + #define MII_LAN874X_PHY_MMD_WOL_WUF_MASK0 0x8021 45 + #define MII_LAN874X_PHY_MMD_WOL_WUF_MASK1 0x8022 46 + #define MII_LAN874X_PHY_MMD_WOL_WUF_MASK2 0x8023 47 + #define MII_LAN874X_PHY_MMD_WOL_WUF_MASK3 0x8024 48 + #define MII_LAN874X_PHY_MMD_WOL_WUF_MASK4 0x8025 49 + #define MII_LAN874X_PHY_MMD_WOL_WUF_MASK5 0x8026 50 + #define MII_LAN874X_PHY_MMD_WOL_WUF_MASK6 0x8027 51 + #define MII_LAN874X_PHY_MMD_WOL_WUF_MASK7 0x8028 52 + #define MII_LAN874X_PHY_MMD_WOL_RX_ADDRA 0x8061 53 + #define MII_LAN874X_PHY_MMD_WOL_RX_ADDRB 0x8062 54 + #define MII_LAN874X_PHY_MMD_WOL_RX_ADDRC 0x8063 55 + #define MII_LAN874X_PHY_MMD_MCFGR 0x8064 56 + 57 + #define MII_LAN874X_PHY_PME1_SET (2 << 13) 58 + #define MII_LAN874X_PHY_PME2_SET (2 << 11) 59 + #define MII_LAN874X_PHY_PME_SELF_CLEAR BIT(9) 60 + #define MII_LAN874X_PHY_WOL_PFDA_FR BIT(7) 61 + #define MII_LAN874X_PHY_WOL_WUFR BIT(6) 62 + #define MII_LAN874X_PHY_WOL_MPR BIT(5) 63 + #define MII_LAN874X_PHY_WOL_BCAST_FR BIT(4) 64 + #define MII_LAN874X_PHY_WOL_PFDAEN BIT(3) 65 + #define MII_LAN874X_PHY_WOL_WUEN BIT(2) 66 + #define MII_LAN874X_PHY_WOL_MPEN BIT(1) 67 + #define MII_LAN874X_PHY_WOL_BCSTEN BIT(0) 68 + 69 + #define MII_LAN874X_PHY_WOL_FILTER_EN BIT(15) 70 + #define MII_LAN874X_PHY_WOL_FILTER_MCASTTEN BIT(9) 71 + #define MII_LAN874X_PHY_WOL_FILTER_BCSTEN BIT(8) 72 + 73 + #define MII_LAN874X_PHY_PME_SELF_CLEAR_DELAY 0x1000 /* 81 milliseconds */ 74 + 41 75 #endif /* __LINUX_SMSCPHY_H__ */