239 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Amlogic Meson GXL Internal PHY Driver4 *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#include <linux/smscphy.h>17 18#define TSTCNTL 2019#define TSTCNTL_READ BIT(15)20#define TSTCNTL_WRITE BIT(14)21#define TSTCNTL_REG_BANK_SEL GENMASK(12, 11)22#define TSTCNTL_TEST_MODE BIT(10)23#define TSTCNTL_READ_ADDRESS GENMASK(9, 5)24#define TSTCNTL_WRITE_ADDRESS GENMASK(4, 0)25#define TSTREAD1 2126#define TSTWRITE 2327 28#define BANK_ANALOG_DSP 029#define BANK_WOL 130#define BANK_BIST 331 32/* WOL Registers */33#define LPI_STATUS 0xc34#define LPI_STATUS_RSV12 BIT(12)35 36/* BIST Registers */37#define FR_PLL_CONTROL 0x1b38#define FR_PLL_DIV0 0x1c39#define FR_PLL_DIV1 0x1d40 41static int meson_gxl_open_banks(struct phy_device *phydev)42{43 int ret;44 45 /* Enable Analog and DSP register Bank access by46 * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register47 */48 ret = phy_write(phydev, TSTCNTL, 0);49 if (ret)50 return ret;51 ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);52 if (ret)53 return ret;54 ret = phy_write(phydev, TSTCNTL, 0);55 if (ret)56 return ret;57 return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);58}59 60static void meson_gxl_close_banks(struct phy_device *phydev)61{62 phy_write(phydev, TSTCNTL, 0);63}64 65static int meson_gxl_read_reg(struct phy_device *phydev,66 unsigned int bank, unsigned int reg)67{68 int ret;69 70 ret = meson_gxl_open_banks(phydev);71 if (ret)72 goto out;73 74 ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ |75 FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |76 TSTCNTL_TEST_MODE |77 FIELD_PREP(TSTCNTL_READ_ADDRESS, reg));78 if (ret)79 goto out;80 81 ret = phy_read(phydev, TSTREAD1);82out:83 /* Close the bank access on our way out */84 meson_gxl_close_banks(phydev);85 return ret;86}87 88static int meson_gxl_write_reg(struct phy_device *phydev,89 unsigned int bank, unsigned int reg,90 uint16_t value)91{92 int ret;93 94 ret = meson_gxl_open_banks(phydev);95 if (ret)96 goto out;97 98 ret = phy_write(phydev, TSTWRITE, value);99 if (ret)100 goto out;101 102 ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE |103 FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |104 TSTCNTL_TEST_MODE |105 FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg));106 107out:108 /* Close the bank access on our way out */109 meson_gxl_close_banks(phydev);110 return ret;111}112 113static int meson_gxl_config_init(struct phy_device *phydev)114{115 int ret;116 117 /* Enable fractional PLL */118 ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5);119 if (ret)120 return ret;121 122 /* Program fraction FR_PLL_DIV1 */123 ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a);124 if (ret)125 return ret;126 127 /* Program fraction FR_PLL_DIV1 */128 ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa);129 if (ret)130 return ret;131 132 return 0;133}134 135/* This function is provided to cope with the possible failures of this phy136 * during aneg process. When aneg fails, the PHY reports that aneg is done137 * but the value found in MII_LPA is wrong:138 * - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that139 * the link partner (LP) supports aneg but the LP never acked our base140 * code word, it is likely that we never sent it to begin with.141 * - Late failures: MII_LPA is filled with a value which seems to make sense142 * but it actually is not what the LP is advertising. It seems that we143 * can detect this using a magic bit in the WOL bank (reg 12 - bit 12).144 * If this particular bit is not set when aneg is reported being done,145 * it means MII_LPA is likely to be wrong.146 *147 * In both case, forcing a restart of the aneg process solve the problem.148 * When this failure happens, the first retry is usually successful but,149 * in some cases, it may take up to 6 retries to get a decent result150 */151static int meson_gxl_read_status(struct phy_device *phydev)152{153 int ret, wol, lpa, exp;154 155 if (phydev->autoneg == AUTONEG_ENABLE) {156 ret = genphy_aneg_done(phydev);157 if (ret < 0)158 return ret;159 else if (!ret)160 goto read_status_continue;161 162 /* Aneg is done, let's check everything is fine */163 wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS);164 if (wol < 0)165 return wol;166 167 lpa = phy_read(phydev, MII_LPA);168 if (lpa < 0)169 return lpa;170 171 exp = phy_read(phydev, MII_EXPANSION);172 if (exp < 0)173 return exp;174 175 if (!(wol & LPI_STATUS_RSV12) ||176 ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) {177 /* Looks like aneg failed after all */178 phydev_dbg(phydev, "LPA corruption - aneg restart\n");179 return genphy_restart_aneg(phydev);180 }181 }182 183read_status_continue:184 return genphy_read_status(phydev);185}186 187static struct phy_driver meson_gxl_phy[] = {188 {189 PHY_ID_MATCH_EXACT(0x01814400),190 .name = "Meson GXL Internal PHY",191 /* PHY_BASIC_FEATURES */192 .flags = PHY_IS_INTERNAL,193 .soft_reset = genphy_soft_reset,194 .config_init = meson_gxl_config_init,195 .read_status = meson_gxl_read_status,196 .config_intr = smsc_phy_config_intr,197 .handle_interrupt = smsc_phy_handle_interrupt,198 .suspend = genphy_suspend,199 .resume = genphy_resume,200 .read_mmd = genphy_read_mmd_unsupported,201 .write_mmd = genphy_write_mmd_unsupported,202 }, {203 PHY_ID_MATCH_EXACT(0x01803301),204 .name = "Meson G12A Internal PHY",205 /* PHY_BASIC_FEATURES */206 .flags = PHY_IS_INTERNAL,207 .probe = smsc_phy_probe,208 .config_init = smsc_phy_config_init,209 .soft_reset = genphy_soft_reset,210 .read_status = lan87xx_read_status,211 .config_intr = smsc_phy_config_intr,212 .handle_interrupt = smsc_phy_handle_interrupt,213 214 .get_tunable = smsc_phy_get_tunable,215 .set_tunable = smsc_phy_set_tunable,216 217 .suspend = genphy_suspend,218 .resume = genphy_resume,219 .read_mmd = genphy_read_mmd_unsupported,220 .write_mmd = genphy_write_mmd_unsupported,221 },222};223 224static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {225 { PHY_ID_MATCH_VENDOR(0x01814400) },226 { PHY_ID_MATCH_VENDOR(0x01803301) },227 { }228};229 230module_phy_driver(meson_gxl_phy);231 232MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl);233 234MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver");235MODULE_AUTHOR("Baoqi wang");236MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");237MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");238MODULE_LICENSE("GPL");239