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