1 /* 2 * Amlogic Meson GXL Internal PHY Driver 3 * 4 * Copyright (C) 2015 Amlogic, Inc. All rights reserved. 5 * Copyright (C) 2016 BayLibre, SAS. All rights reserved. 6 * Author: Neil Armstrong <narmstrong@baylibre.com> 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 16 * more details. 17 * 18 */ 19 #include <linux/kernel.h> 20 #include <linux/module.h> 21 #include <linux/mii.h> 22 #include <linux/ethtool.h> 23 #include <linux/phy.h> 24 #include <linux/netdevice.h> 25 #include <linux/bitfield.h> 26 27 static int meson_gxl_config_init(struct phy_device *phydev) 28 { 29 /* Enable Analog and DSP register Bank access by */ 30 phy_write(phydev, 0x14, 0x0000); 31 phy_write(phydev, 0x14, 0x0400); 32 phy_write(phydev, 0x14, 0x0000); 33 phy_write(phydev, 0x14, 0x0400); 34 35 /* Write Analog register 23 */ 36 phy_write(phydev, 0x17, 0x8E0D); 37 phy_write(phydev, 0x14, 0x4417); 38 39 /* Enable fractional PLL */ 40 phy_write(phydev, 0x17, 0x0005); 41 phy_write(phydev, 0x14, 0x5C1B); 42 43 /* Program fraction FR_PLL_DIV1 */ 44 phy_write(phydev, 0x17, 0x029A); 45 phy_write(phydev, 0x14, 0x5C1D); 46 47 /* Program fraction FR_PLL_DIV1 */ 48 phy_write(phydev, 0x17, 0xAAAA); 49 phy_write(phydev, 0x14, 0x5C1C); 50 51 return 0; 52 } 53 54 /* This function is provided to cope with the possible failures of this phy 55 * during aneg process. When aneg fails, the PHY reports that aneg is done 56 * but the value found in MII_LPA is wrong: 57 * - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that 58 * the link partner (LP) supports aneg but the LP never acked our base 59 * code word, it is likely that we never sent it to begin with. 60 * - Late failures: MII_LPA is filled with a value which seems to make sense 61 * but it actually is not what the LP is advertising. It seems that we 62 * can detect this using a magic bit in the WOL bank (reg 12 - bit 12). 63 * If this particular bit is not set when aneg is reported being done, 64 * it means MII_LPA is likely to be wrong. 65 * 66 * In both case, forcing a restart of the aneg process solve the problem. 67 * When this failure happens, the first retry is usually successful but, 68 * in some cases, it may take up to 6 retries to get a decent result 69 */ 70 static int meson_gxl_read_status(struct phy_device *phydev) 71 { 72 int ret, wol, lpa, exp; 73 74 if (phydev->autoneg == AUTONEG_ENABLE) { 75 ret = genphy_aneg_done(phydev); 76 if (ret < 0) 77 return ret; 78 else if (!ret) 79 goto read_status_continue; 80 81 /* Need to access WOL bank, make sure the access is open */ 82 ret = phy_write(phydev, 0x14, 0x0000); 83 if (ret) 84 return ret; 85 ret = phy_write(phydev, 0x14, 0x0400); 86 if (ret) 87 return ret; 88 ret = phy_write(phydev, 0x14, 0x0000); 89 if (ret) 90 return ret; 91 ret = phy_write(phydev, 0x14, 0x0400); 92 if (ret) 93 return ret; 94 95 /* Request LPI_STATUS WOL register */ 96 ret = phy_write(phydev, 0x14, 0x8D80); 97 if (ret) 98 return ret; 99 100 /* Read LPI_STATUS value */ 101 wol = phy_read(phydev, 0x15); 102 if (wol < 0) 103 return wol; 104 105 lpa = phy_read(phydev, MII_LPA); 106 if (lpa < 0) 107 return lpa; 108 109 exp = phy_read(phydev, MII_EXPANSION); 110 if (exp < 0) 111 return exp; 112 113 if (!(wol & BIT(12)) || 114 ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) { 115 /* Looks like aneg failed after all */ 116 phydev_dbg(phydev, "LPA corruption - aneg restart\n"); 117 return genphy_restart_aneg(phydev); 118 } 119 } 120 121 read_status_continue: 122 return genphy_read_status(phydev); 123 } 124 125 static struct phy_driver meson_gxl_phy[] = { 126 { 127 .phy_id = 0x01814400, 128 .phy_id_mask = 0xfffffff0, 129 .name = "Meson GXL Internal PHY", 130 .features = PHY_BASIC_FEATURES, 131 .flags = PHY_IS_INTERNAL, 132 .config_init = meson_gxl_config_init, 133 .config_aneg = genphy_config_aneg, 134 .aneg_done = genphy_aneg_done, 135 .read_status = meson_gxl_read_status, 136 .suspend = genphy_suspend, 137 .resume = genphy_resume, 138 }, 139 }; 140 141 static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = { 142 { 0x01814400, 0xfffffff0 }, 143 { } 144 }; 145 146 module_phy_driver(meson_gxl_phy); 147 148 MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl); 149 150 MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver"); 151 MODULE_AUTHOR("Baoqi wang"); 152 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 153 MODULE_LICENSE("GPL"); 154