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 #define TSTCNTL 20 28 #define TSTCNTL_READ BIT(15) 29 #define TSTCNTL_WRITE BIT(14) 30 #define TSTCNTL_REG_BANK_SEL GENMASK(12, 11) 31 #define TSTCNTL_TEST_MODE BIT(10) 32 #define TSTCNTL_READ_ADDRESS GENMASK(9, 5) 33 #define TSTCNTL_WRITE_ADDRESS GENMASK(4, 0) 34 #define TSTREAD1 21 35 #define TSTWRITE 23 36 #define INTSRC_FLAG 29 37 #define INTSRC_ANEG_PR BIT(1) 38 #define INTSRC_PARALLEL_FAULT BIT(2) 39 #define INTSRC_ANEG_LP_ACK BIT(3) 40 #define INTSRC_LINK_DOWN BIT(4) 41 #define INTSRC_REMOTE_FAULT BIT(5) 42 #define INTSRC_ANEG_COMPLETE BIT(6) 43 #define INTSRC_MASK 30 44 45 #define BANK_ANALOG_DSP 0 46 #define BANK_WOL 1 47 #define BANK_BIST 3 48 49 /* WOL Registers */ 50 #define LPI_STATUS 0xc 51 #define LPI_STATUS_RSV12 BIT(12) 52 53 /* BIST Registers */ 54 #define FR_PLL_CONTROL 0x1b 55 #define FR_PLL_DIV0 0x1c 56 #define FR_PLL_DIV1 0x1d 57 58 static int meson_gxl_open_banks(struct phy_device *phydev) 59 { 60 int ret; 61 62 /* Enable Analog and DSP register Bank access by 63 * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register 64 */ 65 ret = phy_write(phydev, TSTCNTL, 0); 66 if (ret) 67 return ret; 68 ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE); 69 if (ret) 70 return ret; 71 ret = phy_write(phydev, TSTCNTL, 0); 72 if (ret) 73 return ret; 74 return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE); 75 } 76 77 static void meson_gxl_close_banks(struct phy_device *phydev) 78 { 79 phy_write(phydev, TSTCNTL, 0); 80 } 81 82 static int meson_gxl_read_reg(struct phy_device *phydev, 83 unsigned int bank, unsigned int reg) 84 { 85 int ret; 86 87 ret = meson_gxl_open_banks(phydev); 88 if (ret) 89 goto out; 90 91 ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ | 92 FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) | 93 TSTCNTL_TEST_MODE | 94 FIELD_PREP(TSTCNTL_READ_ADDRESS, reg)); 95 if (ret) 96 goto out; 97 98 ret = phy_read(phydev, TSTREAD1); 99 out: 100 /* Close the bank access on our way out */ 101 meson_gxl_close_banks(phydev); 102 return ret; 103 } 104 105 static int meson_gxl_write_reg(struct phy_device *phydev, 106 unsigned int bank, unsigned int reg, 107 uint16_t value) 108 { 109 int ret; 110 111 ret = meson_gxl_open_banks(phydev); 112 if (ret) 113 goto out; 114 115 ret = phy_write(phydev, TSTWRITE, value); 116 if (ret) 117 goto out; 118 119 ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE | 120 FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) | 121 TSTCNTL_TEST_MODE | 122 FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg)); 123 124 out: 125 /* Close the bank access on our way out */ 126 meson_gxl_close_banks(phydev); 127 return ret; 128 } 129 130 static int meson_gxl_config_init(struct phy_device *phydev) 131 { 132 int ret; 133 134 /* Enable fractional PLL */ 135 ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5); 136 if (ret) 137 return ret; 138 139 /* Program fraction FR_PLL_DIV1 */ 140 ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a); 141 if (ret) 142 return ret; 143 144 /* Program fraction FR_PLL_DIV1 */ 145 ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa); 146 if (ret) 147 return ret; 148 149 return genphy_config_init(phydev); 150 } 151 152 /* This function is provided to cope with the possible failures of this phy 153 * during aneg process. When aneg fails, the PHY reports that aneg is done 154 * but the value found in MII_LPA is wrong: 155 * - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that 156 * the link partner (LP) supports aneg but the LP never acked our base 157 * code word, it is likely that we never sent it to begin with. 158 * - Late failures: MII_LPA is filled with a value which seems to make sense 159 * but it actually is not what the LP is advertising. It seems that we 160 * can detect this using a magic bit in the WOL bank (reg 12 - bit 12). 161 * If this particular bit is not set when aneg is reported being done, 162 * it means MII_LPA is likely to be wrong. 163 * 164 * In both case, forcing a restart of the aneg process solve the problem. 165 * When this failure happens, the first retry is usually successful but, 166 * in some cases, it may take up to 6 retries to get a decent result 167 */ 168 static int meson_gxl_read_status(struct phy_device *phydev) 169 { 170 int ret, wol, lpa, exp; 171 172 if (phydev->autoneg == AUTONEG_ENABLE) { 173 ret = genphy_aneg_done(phydev); 174 if (ret < 0) 175 return ret; 176 else if (!ret) 177 goto read_status_continue; 178 179 /* Aneg is done, let's check everything is fine */ 180 wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS); 181 if (wol < 0) 182 return wol; 183 184 lpa = phy_read(phydev, MII_LPA); 185 if (lpa < 0) 186 return lpa; 187 188 exp = phy_read(phydev, MII_EXPANSION); 189 if (exp < 0) 190 return exp; 191 192 if (!(wol & LPI_STATUS_RSV12) || 193 ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) { 194 /* Looks like aneg failed after all */ 195 phydev_dbg(phydev, "LPA corruption - aneg restart\n"); 196 return genphy_restart_aneg(phydev); 197 } 198 } 199 200 read_status_continue: 201 return genphy_read_status(phydev); 202 } 203 204 static int meson_gxl_ack_interrupt(struct phy_device *phydev) 205 { 206 int ret = phy_read(phydev, INTSRC_FLAG); 207 208 return ret < 0 ? ret : 0; 209 } 210 211 static int meson_gxl_config_intr(struct phy_device *phydev) 212 { 213 u16 val; 214 215 if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { 216 val = INTSRC_ANEG_PR 217 | INTSRC_PARALLEL_FAULT 218 | INTSRC_ANEG_LP_ACK 219 | INTSRC_LINK_DOWN 220 | INTSRC_REMOTE_FAULT 221 | INTSRC_ANEG_COMPLETE; 222 } else { 223 val = 0; 224 } 225 226 return phy_write(phydev, INTSRC_MASK, val); 227 } 228 229 static struct phy_driver meson_gxl_phy[] = { 230 { 231 .phy_id = 0x01814400, 232 .phy_id_mask = 0xfffffff0, 233 .name = "Meson GXL Internal PHY", 234 .features = PHY_BASIC_FEATURES, 235 .flags = PHY_IS_INTERNAL | PHY_HAS_INTERRUPT, 236 .config_init = meson_gxl_config_init, 237 .aneg_done = genphy_aneg_done, 238 .read_status = meson_gxl_read_status, 239 .ack_interrupt = meson_gxl_ack_interrupt, 240 .config_intr = meson_gxl_config_intr, 241 .suspend = genphy_suspend, 242 .resume = genphy_resume, 243 }, 244 }; 245 246 static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = { 247 { 0x01814400, 0xfffffff0 }, 248 { } 249 }; 250 251 module_phy_driver(meson_gxl_phy); 252 253 MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl); 254 255 MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver"); 256 MODULE_AUTHOR("Baoqi wang"); 257 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 258 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 259 MODULE_LICENSE("GPL"); 260