1*87461f7aSDan Murphy /* 2*87461f7aSDan Murphy * Driver for the Texas Instruments DP83822 PHY 3*87461f7aSDan Murphy * 4*87461f7aSDan Murphy * Copyright (C) 2017 Texas Instruments Inc. 5*87461f7aSDan Murphy * 6*87461f7aSDan Murphy * This program is free software; you can redistribute it and/or modify 7*87461f7aSDan Murphy * it under the terms of the GNU General Public License as published by 8*87461f7aSDan Murphy * the Free Software Foundation; either version 2 of the License. 9*87461f7aSDan Murphy * 10*87461f7aSDan Murphy * This program is distributed in the hope that it will be useful, 11*87461f7aSDan Murphy * but WITHOUT ANY WARRANTY; without even the implied warranty of 12*87461f7aSDan Murphy * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13*87461f7aSDan Murphy * GNU General Public License for more details. 14*87461f7aSDan Murphy */ 15*87461f7aSDan Murphy 16*87461f7aSDan Murphy #include <linux/ethtool.h> 17*87461f7aSDan Murphy #include <linux/etherdevice.h> 18*87461f7aSDan Murphy #include <linux/kernel.h> 19*87461f7aSDan Murphy #include <linux/mii.h> 20*87461f7aSDan Murphy #include <linux/module.h> 21*87461f7aSDan Murphy #include <linux/of.h> 22*87461f7aSDan Murphy #include <linux/phy.h> 23*87461f7aSDan Murphy #include <linux/netdevice.h> 24*87461f7aSDan Murphy 25*87461f7aSDan Murphy #define DP83822_PHY_ID 0x2000a240 26*87461f7aSDan Murphy #define DP83822_DEVADDR 0x1f 27*87461f7aSDan Murphy 28*87461f7aSDan Murphy #define MII_DP83822_PHYSCR 0x11 29*87461f7aSDan Murphy #define MII_DP83822_MISR1 0x12 30*87461f7aSDan Murphy #define MII_DP83822_MISR2 0x13 31*87461f7aSDan Murphy #define MII_DP83822_RESET_CTRL 0x1f 32*87461f7aSDan Murphy 33*87461f7aSDan Murphy #define DP83822_HW_RESET BIT(15) 34*87461f7aSDan Murphy #define DP83822_SW_RESET BIT(14) 35*87461f7aSDan Murphy 36*87461f7aSDan Murphy /* PHYSCR Register Fields */ 37*87461f7aSDan Murphy #define DP83822_PHYSCR_INT_OE BIT(0) /* Interrupt Output Enable */ 38*87461f7aSDan Murphy #define DP83822_PHYSCR_INTEN BIT(1) /* Interrupt Enable */ 39*87461f7aSDan Murphy 40*87461f7aSDan Murphy /* MISR1 bits */ 41*87461f7aSDan Murphy #define DP83822_RX_ERR_HF_INT_EN BIT(0) 42*87461f7aSDan Murphy #define DP83822_FALSE_CARRIER_HF_INT_EN BIT(1) 43*87461f7aSDan Murphy #define DP83822_ANEG_COMPLETE_INT_EN BIT(2) 44*87461f7aSDan Murphy #define DP83822_DUP_MODE_CHANGE_INT_EN BIT(3) 45*87461f7aSDan Murphy #define DP83822_SPEED_CHANGED_INT_EN BIT(4) 46*87461f7aSDan Murphy #define DP83822_LINK_STAT_INT_EN BIT(5) 47*87461f7aSDan Murphy #define DP83822_ENERGY_DET_INT_EN BIT(6) 48*87461f7aSDan Murphy #define DP83822_LINK_QUAL_INT_EN BIT(7) 49*87461f7aSDan Murphy 50*87461f7aSDan Murphy /* MISR2 bits */ 51*87461f7aSDan Murphy #define DP83822_JABBER_DET_INT_EN BIT(0) 52*87461f7aSDan Murphy #define DP83822_WOL_PKT_INT_EN BIT(1) 53*87461f7aSDan Murphy #define DP83822_SLEEP_MODE_INT_EN BIT(2) 54*87461f7aSDan Murphy #define DP83822_MDI_XOVER_INT_EN BIT(3) 55*87461f7aSDan Murphy #define DP83822_LB_FIFO_INT_EN BIT(4) 56*87461f7aSDan Murphy #define DP83822_PAGE_RX_INT_EN BIT(5) 57*87461f7aSDan Murphy #define DP83822_ANEG_ERR_INT_EN BIT(6) 58*87461f7aSDan Murphy #define DP83822_EEE_ERROR_CHANGE_INT_EN BIT(7) 59*87461f7aSDan Murphy 60*87461f7aSDan Murphy /* INT_STAT1 bits */ 61*87461f7aSDan Murphy #define DP83822_WOL_INT_EN BIT(4) 62*87461f7aSDan Murphy #define DP83822_WOL_INT_STAT BIT(12) 63*87461f7aSDan Murphy 64*87461f7aSDan Murphy #define MII_DP83822_RXSOP1 0x04a5 65*87461f7aSDan Murphy #define MII_DP83822_RXSOP2 0x04a6 66*87461f7aSDan Murphy #define MII_DP83822_RXSOP3 0x04a7 67*87461f7aSDan Murphy 68*87461f7aSDan Murphy /* WoL Registers */ 69*87461f7aSDan Murphy #define MII_DP83822_WOL_CFG 0x04a0 70*87461f7aSDan Murphy #define MII_DP83822_WOL_STAT 0x04a1 71*87461f7aSDan Murphy #define MII_DP83822_WOL_DA1 0x04a2 72*87461f7aSDan Murphy #define MII_DP83822_WOL_DA2 0x04a3 73*87461f7aSDan Murphy #define MII_DP83822_WOL_DA3 0x04a4 74*87461f7aSDan Murphy 75*87461f7aSDan Murphy /* WoL bits */ 76*87461f7aSDan Murphy #define DP83822_WOL_MAGIC_EN BIT(0) 77*87461f7aSDan Murphy #define DP83822_WOL_SECURE_ON BIT(5) 78*87461f7aSDan Murphy #define DP83822_WOL_EN BIT(7) 79*87461f7aSDan Murphy #define DP83822_WOL_INDICATION_SEL BIT(8) 80*87461f7aSDan Murphy #define DP83822_WOL_CLR_INDICATION BIT(11) 81*87461f7aSDan Murphy 82*87461f7aSDan Murphy static int dp83822_ack_interrupt(struct phy_device *phydev) 83*87461f7aSDan Murphy { 84*87461f7aSDan Murphy int err; 85*87461f7aSDan Murphy 86*87461f7aSDan Murphy err = phy_read(phydev, MII_DP83822_MISR1); 87*87461f7aSDan Murphy if (err < 0) 88*87461f7aSDan Murphy return err; 89*87461f7aSDan Murphy 90*87461f7aSDan Murphy err = phy_read(phydev, MII_DP83822_MISR2); 91*87461f7aSDan Murphy if (err < 0) 92*87461f7aSDan Murphy return err; 93*87461f7aSDan Murphy 94*87461f7aSDan Murphy return 0; 95*87461f7aSDan Murphy } 96*87461f7aSDan Murphy 97*87461f7aSDan Murphy static int dp83822_set_wol(struct phy_device *phydev, 98*87461f7aSDan Murphy struct ethtool_wolinfo *wol) 99*87461f7aSDan Murphy { 100*87461f7aSDan Murphy struct net_device *ndev = phydev->attached_dev; 101*87461f7aSDan Murphy u16 value; 102*87461f7aSDan Murphy const u8 *mac; 103*87461f7aSDan Murphy 104*87461f7aSDan Murphy if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE)) { 105*87461f7aSDan Murphy mac = (const u8 *)ndev->dev_addr; 106*87461f7aSDan Murphy 107*87461f7aSDan Murphy if (!is_valid_ether_addr(mac)) 108*87461f7aSDan Murphy return -EINVAL; 109*87461f7aSDan Murphy 110*87461f7aSDan Murphy /* MAC addresses start with byte 5, but stored in mac[0]. 111*87461f7aSDan Murphy * 822 PHYs store bytes 4|5, 2|3, 0|1 112*87461f7aSDan Murphy */ 113*87461f7aSDan Murphy phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_DA1, 114*87461f7aSDan Murphy (mac[1] << 8) | mac[0]); 115*87461f7aSDan Murphy phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_DA2, 116*87461f7aSDan Murphy (mac[3] << 8) | mac[2]); 117*87461f7aSDan Murphy phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_DA3, 118*87461f7aSDan Murphy (mac[5] << 8) | mac[4]); 119*87461f7aSDan Murphy 120*87461f7aSDan Murphy value = phy_read_mmd(phydev, DP83822_DEVADDR, 121*87461f7aSDan Murphy MII_DP83822_WOL_CFG); 122*87461f7aSDan Murphy if (wol->wolopts & WAKE_MAGIC) 123*87461f7aSDan Murphy value |= DP83822_WOL_MAGIC_EN; 124*87461f7aSDan Murphy else 125*87461f7aSDan Murphy value &= ~DP83822_WOL_MAGIC_EN; 126*87461f7aSDan Murphy 127*87461f7aSDan Murphy if (wol->wolopts & WAKE_MAGICSECURE) { 128*87461f7aSDan Murphy phy_write_mmd(phydev, DP83822_DEVADDR, 129*87461f7aSDan Murphy MII_DP83822_RXSOP1, 130*87461f7aSDan Murphy (wol->sopass[1] << 8) | wol->sopass[0]); 131*87461f7aSDan Murphy phy_write_mmd(phydev, DP83822_DEVADDR, 132*87461f7aSDan Murphy MII_DP83822_RXSOP2, 133*87461f7aSDan Murphy (wol->sopass[3] << 8) | wol->sopass[2]); 134*87461f7aSDan Murphy phy_write_mmd(phydev, DP83822_DEVADDR, 135*87461f7aSDan Murphy MII_DP83822_RXSOP3, 136*87461f7aSDan Murphy (wol->sopass[5] << 8) | wol->sopass[4]); 137*87461f7aSDan Murphy value |= DP83822_WOL_SECURE_ON; 138*87461f7aSDan Murphy } else { 139*87461f7aSDan Murphy value &= ~DP83822_WOL_SECURE_ON; 140*87461f7aSDan Murphy } 141*87461f7aSDan Murphy 142*87461f7aSDan Murphy value |= (DP83822_WOL_EN | DP83822_WOL_INDICATION_SEL | 143*87461f7aSDan Murphy DP83822_WOL_CLR_INDICATION); 144*87461f7aSDan Murphy phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG, 145*87461f7aSDan Murphy value); 146*87461f7aSDan Murphy } else { 147*87461f7aSDan Murphy value = phy_read_mmd(phydev, DP83822_DEVADDR, 148*87461f7aSDan Murphy MII_DP83822_WOL_CFG); 149*87461f7aSDan Murphy value &= ~DP83822_WOL_EN; 150*87461f7aSDan Murphy phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG, 151*87461f7aSDan Murphy value); 152*87461f7aSDan Murphy } 153*87461f7aSDan Murphy 154*87461f7aSDan Murphy return 0; 155*87461f7aSDan Murphy } 156*87461f7aSDan Murphy 157*87461f7aSDan Murphy static void dp83822_get_wol(struct phy_device *phydev, 158*87461f7aSDan Murphy struct ethtool_wolinfo *wol) 159*87461f7aSDan Murphy { 160*87461f7aSDan Murphy int value; 161*87461f7aSDan Murphy u16 sopass_val; 162*87461f7aSDan Murphy 163*87461f7aSDan Murphy wol->supported = (WAKE_MAGIC | WAKE_MAGICSECURE); 164*87461f7aSDan Murphy wol->wolopts = 0; 165*87461f7aSDan Murphy 166*87461f7aSDan Murphy value = phy_read_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG); 167*87461f7aSDan Murphy 168*87461f7aSDan Murphy if (value & DP83822_WOL_MAGIC_EN) 169*87461f7aSDan Murphy wol->wolopts |= WAKE_MAGIC; 170*87461f7aSDan Murphy 171*87461f7aSDan Murphy if (value & DP83822_WOL_SECURE_ON) { 172*87461f7aSDan Murphy sopass_val = phy_read_mmd(phydev, DP83822_DEVADDR, 173*87461f7aSDan Murphy MII_DP83822_RXSOP1); 174*87461f7aSDan Murphy wol->sopass[0] = (sopass_val & 0xff); 175*87461f7aSDan Murphy wol->sopass[1] = (sopass_val >> 8); 176*87461f7aSDan Murphy 177*87461f7aSDan Murphy sopass_val = phy_read_mmd(phydev, DP83822_DEVADDR, 178*87461f7aSDan Murphy MII_DP83822_RXSOP2); 179*87461f7aSDan Murphy wol->sopass[2] = (sopass_val & 0xff); 180*87461f7aSDan Murphy wol->sopass[3] = (sopass_val >> 8); 181*87461f7aSDan Murphy 182*87461f7aSDan Murphy sopass_val = phy_read_mmd(phydev, DP83822_DEVADDR, 183*87461f7aSDan Murphy MII_DP83822_RXSOP3); 184*87461f7aSDan Murphy wol->sopass[4] = (sopass_val & 0xff); 185*87461f7aSDan Murphy wol->sopass[5] = (sopass_val >> 8); 186*87461f7aSDan Murphy 187*87461f7aSDan Murphy wol->wolopts |= WAKE_MAGICSECURE; 188*87461f7aSDan Murphy } 189*87461f7aSDan Murphy 190*87461f7aSDan Murphy /* WoL is not enabled so set wolopts to 0 */ 191*87461f7aSDan Murphy if (!(value & DP83822_WOL_EN)) 192*87461f7aSDan Murphy wol->wolopts = 0; 193*87461f7aSDan Murphy } 194*87461f7aSDan Murphy 195*87461f7aSDan Murphy static int dp83822_config_intr(struct phy_device *phydev) 196*87461f7aSDan Murphy { 197*87461f7aSDan Murphy int misr_status; 198*87461f7aSDan Murphy int physcr_status; 199*87461f7aSDan Murphy int err; 200*87461f7aSDan Murphy 201*87461f7aSDan Murphy if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { 202*87461f7aSDan Murphy misr_status = phy_read(phydev, MII_DP83822_MISR1); 203*87461f7aSDan Murphy if (misr_status < 0) 204*87461f7aSDan Murphy return misr_status; 205*87461f7aSDan Murphy 206*87461f7aSDan Murphy misr_status |= (DP83822_RX_ERR_HF_INT_EN | 207*87461f7aSDan Murphy DP83822_FALSE_CARRIER_HF_INT_EN | 208*87461f7aSDan Murphy DP83822_ANEG_COMPLETE_INT_EN | 209*87461f7aSDan Murphy DP83822_DUP_MODE_CHANGE_INT_EN | 210*87461f7aSDan Murphy DP83822_SPEED_CHANGED_INT_EN | 211*87461f7aSDan Murphy DP83822_LINK_STAT_INT_EN | 212*87461f7aSDan Murphy DP83822_ENERGY_DET_INT_EN | 213*87461f7aSDan Murphy DP83822_LINK_QUAL_INT_EN); 214*87461f7aSDan Murphy 215*87461f7aSDan Murphy err = phy_write(phydev, MII_DP83822_MISR1, misr_status); 216*87461f7aSDan Murphy if (err < 0) 217*87461f7aSDan Murphy return err; 218*87461f7aSDan Murphy 219*87461f7aSDan Murphy misr_status = phy_read(phydev, MII_DP83822_MISR2); 220*87461f7aSDan Murphy if (misr_status < 0) 221*87461f7aSDan Murphy return misr_status; 222*87461f7aSDan Murphy 223*87461f7aSDan Murphy misr_status |= (DP83822_JABBER_DET_INT_EN | 224*87461f7aSDan Murphy DP83822_WOL_PKT_INT_EN | 225*87461f7aSDan Murphy DP83822_SLEEP_MODE_INT_EN | 226*87461f7aSDan Murphy DP83822_MDI_XOVER_INT_EN | 227*87461f7aSDan Murphy DP83822_LB_FIFO_INT_EN | 228*87461f7aSDan Murphy DP83822_PAGE_RX_INT_EN | 229*87461f7aSDan Murphy DP83822_ANEG_ERR_INT_EN | 230*87461f7aSDan Murphy DP83822_EEE_ERROR_CHANGE_INT_EN); 231*87461f7aSDan Murphy 232*87461f7aSDan Murphy err = phy_write(phydev, MII_DP83822_MISR2, misr_status); 233*87461f7aSDan Murphy if (err < 0) 234*87461f7aSDan Murphy return err; 235*87461f7aSDan Murphy 236*87461f7aSDan Murphy physcr_status = phy_read(phydev, MII_DP83822_PHYSCR); 237*87461f7aSDan Murphy if (physcr_status < 0) 238*87461f7aSDan Murphy return physcr_status; 239*87461f7aSDan Murphy 240*87461f7aSDan Murphy physcr_status |= DP83822_PHYSCR_INT_OE | DP83822_PHYSCR_INTEN; 241*87461f7aSDan Murphy 242*87461f7aSDan Murphy } else { 243*87461f7aSDan Murphy err = phy_write(phydev, MII_DP83822_MISR1, 0); 244*87461f7aSDan Murphy if (err < 0) 245*87461f7aSDan Murphy return err; 246*87461f7aSDan Murphy 247*87461f7aSDan Murphy err = phy_write(phydev, MII_DP83822_MISR1, 0); 248*87461f7aSDan Murphy if (err < 0) 249*87461f7aSDan Murphy return err; 250*87461f7aSDan Murphy 251*87461f7aSDan Murphy physcr_status = phy_read(phydev, MII_DP83822_PHYSCR); 252*87461f7aSDan Murphy if (physcr_status < 0) 253*87461f7aSDan Murphy return physcr_status; 254*87461f7aSDan Murphy 255*87461f7aSDan Murphy physcr_status &= ~DP83822_PHYSCR_INTEN; 256*87461f7aSDan Murphy } 257*87461f7aSDan Murphy 258*87461f7aSDan Murphy return phy_write(phydev, MII_DP83822_PHYSCR, physcr_status); 259*87461f7aSDan Murphy } 260*87461f7aSDan Murphy 261*87461f7aSDan Murphy static int dp83822_config_init(struct phy_device *phydev) 262*87461f7aSDan Murphy { 263*87461f7aSDan Murphy int err; 264*87461f7aSDan Murphy int value; 265*87461f7aSDan Murphy 266*87461f7aSDan Murphy err = genphy_config_init(phydev); 267*87461f7aSDan Murphy if (err < 0) 268*87461f7aSDan Murphy return err; 269*87461f7aSDan Murphy 270*87461f7aSDan Murphy value = DP83822_WOL_MAGIC_EN | DP83822_WOL_SECURE_ON | DP83822_WOL_EN; 271*87461f7aSDan Murphy 272*87461f7aSDan Murphy return phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG, 273*87461f7aSDan Murphy value); 274*87461f7aSDan Murphy } 275*87461f7aSDan Murphy 276*87461f7aSDan Murphy static int dp83822_phy_reset(struct phy_device *phydev) 277*87461f7aSDan Murphy { 278*87461f7aSDan Murphy int err; 279*87461f7aSDan Murphy 280*87461f7aSDan Murphy err = phy_write(phydev, MII_DP83822_RESET_CTRL, DP83822_HW_RESET); 281*87461f7aSDan Murphy if (err < 0) 282*87461f7aSDan Murphy return err; 283*87461f7aSDan Murphy 284*87461f7aSDan Murphy dp83822_config_init(phydev); 285*87461f7aSDan Murphy 286*87461f7aSDan Murphy return 0; 287*87461f7aSDan Murphy } 288*87461f7aSDan Murphy 289*87461f7aSDan Murphy static int dp83822_suspend(struct phy_device *phydev) 290*87461f7aSDan Murphy { 291*87461f7aSDan Murphy int value; 292*87461f7aSDan Murphy 293*87461f7aSDan Murphy value = phy_read_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG); 294*87461f7aSDan Murphy 295*87461f7aSDan Murphy if (!(value & DP83822_WOL_EN)) 296*87461f7aSDan Murphy genphy_suspend(phydev); 297*87461f7aSDan Murphy 298*87461f7aSDan Murphy return 0; 299*87461f7aSDan Murphy } 300*87461f7aSDan Murphy 301*87461f7aSDan Murphy static int dp83822_resume(struct phy_device *phydev) 302*87461f7aSDan Murphy { 303*87461f7aSDan Murphy int value; 304*87461f7aSDan Murphy 305*87461f7aSDan Murphy genphy_resume(phydev); 306*87461f7aSDan Murphy 307*87461f7aSDan Murphy value = phy_read_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG); 308*87461f7aSDan Murphy 309*87461f7aSDan Murphy phy_write_mmd(phydev, DP83822_DEVADDR, MII_DP83822_WOL_CFG, value | 310*87461f7aSDan Murphy DP83822_WOL_CLR_INDICATION); 311*87461f7aSDan Murphy 312*87461f7aSDan Murphy return 0; 313*87461f7aSDan Murphy } 314*87461f7aSDan Murphy 315*87461f7aSDan Murphy static struct phy_driver dp83822_driver[] = { 316*87461f7aSDan Murphy { 317*87461f7aSDan Murphy .phy_id = DP83822_PHY_ID, 318*87461f7aSDan Murphy .phy_id_mask = 0xfffffff0, 319*87461f7aSDan Murphy .name = "TI DP83822", 320*87461f7aSDan Murphy .features = PHY_BASIC_FEATURES, 321*87461f7aSDan Murphy .flags = PHY_HAS_INTERRUPT, 322*87461f7aSDan Murphy .config_init = dp83822_config_init, 323*87461f7aSDan Murphy .soft_reset = dp83822_phy_reset, 324*87461f7aSDan Murphy .get_wol = dp83822_get_wol, 325*87461f7aSDan Murphy .set_wol = dp83822_set_wol, 326*87461f7aSDan Murphy .ack_interrupt = dp83822_ack_interrupt, 327*87461f7aSDan Murphy .config_intr = dp83822_config_intr, 328*87461f7aSDan Murphy .config_aneg = genphy_config_aneg, 329*87461f7aSDan Murphy .read_status = genphy_read_status, 330*87461f7aSDan Murphy .suspend = dp83822_suspend, 331*87461f7aSDan Murphy .resume = dp83822_resume, 332*87461f7aSDan Murphy }, 333*87461f7aSDan Murphy }; 334*87461f7aSDan Murphy module_phy_driver(dp83822_driver); 335*87461f7aSDan Murphy 336*87461f7aSDan Murphy static struct mdio_device_id __maybe_unused dp83822_tbl[] = { 337*87461f7aSDan Murphy { DP83822_PHY_ID, 0xfffffff0 }, 338*87461f7aSDan Murphy { }, 339*87461f7aSDan Murphy }; 340*87461f7aSDan Murphy MODULE_DEVICE_TABLE(mdio, dp83822_tbl); 341*87461f7aSDan Murphy 342*87461f7aSDan Murphy MODULE_DESCRIPTION("Texas Instruments DP83822 PHY driver"); 343*87461f7aSDan Murphy MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com"); 344*87461f7aSDan Murphy MODULE_LICENSE("GPL"); 345