162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Amlogic Meson GXL Internal PHY Driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2015 Amlogic, Inc. All rights reserved. 662306a36Sopenharmony_ci * Copyright (C) 2016 BayLibre, SAS. All rights reserved. 762306a36Sopenharmony_ci * Author: Neil Armstrong <narmstrong@baylibre.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/mii.h> 1262306a36Sopenharmony_ci#include <linux/ethtool.h> 1362306a36Sopenharmony_ci#include <linux/phy.h> 1462306a36Sopenharmony_ci#include <linux/netdevice.h> 1562306a36Sopenharmony_ci#include <linux/bitfield.h> 1662306a36Sopenharmony_ci#include <linux/smscphy.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define TSTCNTL 20 1962306a36Sopenharmony_ci#define TSTCNTL_READ BIT(15) 2062306a36Sopenharmony_ci#define TSTCNTL_WRITE BIT(14) 2162306a36Sopenharmony_ci#define TSTCNTL_REG_BANK_SEL GENMASK(12, 11) 2262306a36Sopenharmony_ci#define TSTCNTL_TEST_MODE BIT(10) 2362306a36Sopenharmony_ci#define TSTCNTL_READ_ADDRESS GENMASK(9, 5) 2462306a36Sopenharmony_ci#define TSTCNTL_WRITE_ADDRESS GENMASK(4, 0) 2562306a36Sopenharmony_ci#define TSTREAD1 21 2662306a36Sopenharmony_ci#define TSTWRITE 23 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define BANK_ANALOG_DSP 0 2962306a36Sopenharmony_ci#define BANK_WOL 1 3062306a36Sopenharmony_ci#define BANK_BIST 3 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci/* WOL Registers */ 3362306a36Sopenharmony_ci#define LPI_STATUS 0xc 3462306a36Sopenharmony_ci#define LPI_STATUS_RSV12 BIT(12) 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci/* BIST Registers */ 3762306a36Sopenharmony_ci#define FR_PLL_CONTROL 0x1b 3862306a36Sopenharmony_ci#define FR_PLL_DIV0 0x1c 3962306a36Sopenharmony_ci#define FR_PLL_DIV1 0x1d 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic int meson_gxl_open_banks(struct phy_device *phydev) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci int ret; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci /* Enable Analog and DSP register Bank access by 4662306a36Sopenharmony_ci * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register 4762306a36Sopenharmony_ci */ 4862306a36Sopenharmony_ci ret = phy_write(phydev, TSTCNTL, 0); 4962306a36Sopenharmony_ci if (ret) 5062306a36Sopenharmony_ci return ret; 5162306a36Sopenharmony_ci ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE); 5262306a36Sopenharmony_ci if (ret) 5362306a36Sopenharmony_ci return ret; 5462306a36Sopenharmony_ci ret = phy_write(phydev, TSTCNTL, 0); 5562306a36Sopenharmony_ci if (ret) 5662306a36Sopenharmony_ci return ret; 5762306a36Sopenharmony_ci return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE); 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic void meson_gxl_close_banks(struct phy_device *phydev) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci phy_write(phydev, TSTCNTL, 0); 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic int meson_gxl_read_reg(struct phy_device *phydev, 6662306a36Sopenharmony_ci unsigned int bank, unsigned int reg) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci int ret; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci ret = meson_gxl_open_banks(phydev); 7162306a36Sopenharmony_ci if (ret) 7262306a36Sopenharmony_ci goto out; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ | 7562306a36Sopenharmony_ci FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) | 7662306a36Sopenharmony_ci TSTCNTL_TEST_MODE | 7762306a36Sopenharmony_ci FIELD_PREP(TSTCNTL_READ_ADDRESS, reg)); 7862306a36Sopenharmony_ci if (ret) 7962306a36Sopenharmony_ci goto out; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci ret = phy_read(phydev, TSTREAD1); 8262306a36Sopenharmony_ciout: 8362306a36Sopenharmony_ci /* Close the bank access on our way out */ 8462306a36Sopenharmony_ci meson_gxl_close_banks(phydev); 8562306a36Sopenharmony_ci return ret; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic int meson_gxl_write_reg(struct phy_device *phydev, 8962306a36Sopenharmony_ci unsigned int bank, unsigned int reg, 9062306a36Sopenharmony_ci uint16_t value) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci int ret; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci ret = meson_gxl_open_banks(phydev); 9562306a36Sopenharmony_ci if (ret) 9662306a36Sopenharmony_ci goto out; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci ret = phy_write(phydev, TSTWRITE, value); 9962306a36Sopenharmony_ci if (ret) 10062306a36Sopenharmony_ci goto out; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE | 10362306a36Sopenharmony_ci FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) | 10462306a36Sopenharmony_ci TSTCNTL_TEST_MODE | 10562306a36Sopenharmony_ci FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg)); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ciout: 10862306a36Sopenharmony_ci /* Close the bank access on our way out */ 10962306a36Sopenharmony_ci meson_gxl_close_banks(phydev); 11062306a36Sopenharmony_ci return ret; 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistatic int meson_gxl_config_init(struct phy_device *phydev) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci int ret; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci /* Enable fractional PLL */ 11862306a36Sopenharmony_ci ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5); 11962306a36Sopenharmony_ci if (ret) 12062306a36Sopenharmony_ci return ret; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci /* Program fraction FR_PLL_DIV1 */ 12362306a36Sopenharmony_ci ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a); 12462306a36Sopenharmony_ci if (ret) 12562306a36Sopenharmony_ci return ret; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci /* Program fraction FR_PLL_DIV1 */ 12862306a36Sopenharmony_ci ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa); 12962306a36Sopenharmony_ci if (ret) 13062306a36Sopenharmony_ci return ret; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci return 0; 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci/* This function is provided to cope with the possible failures of this phy 13662306a36Sopenharmony_ci * during aneg process. When aneg fails, the PHY reports that aneg is done 13762306a36Sopenharmony_ci * but the value found in MII_LPA is wrong: 13862306a36Sopenharmony_ci * - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that 13962306a36Sopenharmony_ci * the link partner (LP) supports aneg but the LP never acked our base 14062306a36Sopenharmony_ci * code word, it is likely that we never sent it to begin with. 14162306a36Sopenharmony_ci * - Late failures: MII_LPA is filled with a value which seems to make sense 14262306a36Sopenharmony_ci * but it actually is not what the LP is advertising. It seems that we 14362306a36Sopenharmony_ci * can detect this using a magic bit in the WOL bank (reg 12 - bit 12). 14462306a36Sopenharmony_ci * If this particular bit is not set when aneg is reported being done, 14562306a36Sopenharmony_ci * it means MII_LPA is likely to be wrong. 14662306a36Sopenharmony_ci * 14762306a36Sopenharmony_ci * In both case, forcing a restart of the aneg process solve the problem. 14862306a36Sopenharmony_ci * When this failure happens, the first retry is usually successful but, 14962306a36Sopenharmony_ci * in some cases, it may take up to 6 retries to get a decent result 15062306a36Sopenharmony_ci */ 15162306a36Sopenharmony_cistatic int meson_gxl_read_status(struct phy_device *phydev) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci int ret, wol, lpa, exp; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if (phydev->autoneg == AUTONEG_ENABLE) { 15662306a36Sopenharmony_ci ret = genphy_aneg_done(phydev); 15762306a36Sopenharmony_ci if (ret < 0) 15862306a36Sopenharmony_ci return ret; 15962306a36Sopenharmony_ci else if (!ret) 16062306a36Sopenharmony_ci goto read_status_continue; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci /* Aneg is done, let's check everything is fine */ 16362306a36Sopenharmony_ci wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS); 16462306a36Sopenharmony_ci if (wol < 0) 16562306a36Sopenharmony_ci return wol; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci lpa = phy_read(phydev, MII_LPA); 16862306a36Sopenharmony_ci if (lpa < 0) 16962306a36Sopenharmony_ci return lpa; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci exp = phy_read(phydev, MII_EXPANSION); 17262306a36Sopenharmony_ci if (exp < 0) 17362306a36Sopenharmony_ci return exp; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci if (!(wol & LPI_STATUS_RSV12) || 17662306a36Sopenharmony_ci ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) { 17762306a36Sopenharmony_ci /* Looks like aneg failed after all */ 17862306a36Sopenharmony_ci phydev_dbg(phydev, "LPA corruption - aneg restart\n"); 17962306a36Sopenharmony_ci return genphy_restart_aneg(phydev); 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci } 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ciread_status_continue: 18462306a36Sopenharmony_ci return genphy_read_status(phydev); 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic struct phy_driver meson_gxl_phy[] = { 18862306a36Sopenharmony_ci { 18962306a36Sopenharmony_ci PHY_ID_MATCH_EXACT(0x01814400), 19062306a36Sopenharmony_ci .name = "Meson GXL Internal PHY", 19162306a36Sopenharmony_ci /* PHY_BASIC_FEATURES */ 19262306a36Sopenharmony_ci .flags = PHY_IS_INTERNAL, 19362306a36Sopenharmony_ci .soft_reset = genphy_soft_reset, 19462306a36Sopenharmony_ci .config_init = meson_gxl_config_init, 19562306a36Sopenharmony_ci .read_status = meson_gxl_read_status, 19662306a36Sopenharmony_ci .config_intr = smsc_phy_config_intr, 19762306a36Sopenharmony_ci .handle_interrupt = smsc_phy_handle_interrupt, 19862306a36Sopenharmony_ci .suspend = genphy_suspend, 19962306a36Sopenharmony_ci .resume = genphy_resume, 20062306a36Sopenharmony_ci .read_mmd = genphy_read_mmd_unsupported, 20162306a36Sopenharmony_ci .write_mmd = genphy_write_mmd_unsupported, 20262306a36Sopenharmony_ci }, { 20362306a36Sopenharmony_ci PHY_ID_MATCH_EXACT(0x01803301), 20462306a36Sopenharmony_ci .name = "Meson G12A Internal PHY", 20562306a36Sopenharmony_ci /* PHY_BASIC_FEATURES */ 20662306a36Sopenharmony_ci .flags = PHY_IS_INTERNAL, 20762306a36Sopenharmony_ci .probe = smsc_phy_probe, 20862306a36Sopenharmony_ci .config_init = smsc_phy_config_init, 20962306a36Sopenharmony_ci .soft_reset = genphy_soft_reset, 21062306a36Sopenharmony_ci .read_status = lan87xx_read_status, 21162306a36Sopenharmony_ci .config_intr = smsc_phy_config_intr, 21262306a36Sopenharmony_ci .handle_interrupt = smsc_phy_handle_interrupt, 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci .get_tunable = smsc_phy_get_tunable, 21562306a36Sopenharmony_ci .set_tunable = smsc_phy_set_tunable, 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci .suspend = genphy_suspend, 21862306a36Sopenharmony_ci .resume = genphy_resume, 21962306a36Sopenharmony_ci .read_mmd = genphy_read_mmd_unsupported, 22062306a36Sopenharmony_ci .write_mmd = genphy_write_mmd_unsupported, 22162306a36Sopenharmony_ci }, 22262306a36Sopenharmony_ci}; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cistatic struct mdio_device_id __maybe_unused meson_gxl_tbl[] = { 22562306a36Sopenharmony_ci { PHY_ID_MATCH_VENDOR(0x01814400) }, 22662306a36Sopenharmony_ci { PHY_ID_MATCH_VENDOR(0x01803301) }, 22762306a36Sopenharmony_ci { } 22862306a36Sopenharmony_ci}; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_cimodule_phy_driver(meson_gxl_phy); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(mdio, meson_gxl_tbl); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ciMODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver"); 23562306a36Sopenharmony_ciMODULE_AUTHOR("Baoqi wang"); 23662306a36Sopenharmony_ciMODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 23762306a36Sopenharmony_ciMODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 23862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 239