1a2443fd1SAndrew Lunn // SPDX-License-Identifier: GPL-2.0+ 27334b3e4SNeil Armstrong /* 37334b3e4SNeil Armstrong * Amlogic Meson GXL Internal PHY Driver 47334b3e4SNeil Armstrong * 57334b3e4SNeil Armstrong * Copyright (C) 2015 Amlogic, Inc. All rights reserved. 67334b3e4SNeil Armstrong * Copyright (C) 2016 BayLibre, SAS. All rights reserved. 77334b3e4SNeil Armstrong * Author: Neil Armstrong <narmstrong@baylibre.com> 87334b3e4SNeil Armstrong */ 97334b3e4SNeil Armstrong #include <linux/kernel.h> 107334b3e4SNeil Armstrong #include <linux/module.h> 117334b3e4SNeil Armstrong #include <linux/mii.h> 127334b3e4SNeil Armstrong #include <linux/ethtool.h> 137334b3e4SNeil Armstrong #include <linux/phy.h> 147334b3e4SNeil Armstrong #include <linux/netdevice.h> 15f1e2400aSJerome Brunet #include <linux/bitfield.h> 167334b3e4SNeil Armstrong 1700fd73ebSJerome Brunet #define TSTCNTL 20 1800fd73ebSJerome Brunet #define TSTCNTL_READ BIT(15) 1900fd73ebSJerome Brunet #define TSTCNTL_WRITE BIT(14) 2000fd73ebSJerome Brunet #define TSTCNTL_REG_BANK_SEL GENMASK(12, 11) 2100fd73ebSJerome Brunet #define TSTCNTL_TEST_MODE BIT(10) 2200fd73ebSJerome Brunet #define TSTCNTL_READ_ADDRESS GENMASK(9, 5) 2300fd73ebSJerome Brunet #define TSTCNTL_WRITE_ADDRESS GENMASK(4, 0) 2400fd73ebSJerome Brunet #define TSTREAD1 21 2500fd73ebSJerome Brunet #define TSTWRITE 23 26cf127ff2SJerome Brunet #define INTSRC_FLAG 29 27cf127ff2SJerome Brunet #define INTSRC_ANEG_PR BIT(1) 28cf127ff2SJerome Brunet #define INTSRC_PARALLEL_FAULT BIT(2) 29cf127ff2SJerome Brunet #define INTSRC_ANEG_LP_ACK BIT(3) 30cf127ff2SJerome Brunet #define INTSRC_LINK_DOWN BIT(4) 31cf127ff2SJerome Brunet #define INTSRC_REMOTE_FAULT BIT(5) 32cf127ff2SJerome Brunet #define INTSRC_ANEG_COMPLETE BIT(6) 33a502a8f0SHeiner Kallweit #define INTSRC_ENERGY_DETECT BIT(7) 34cf127ff2SJerome Brunet #define INTSRC_MASK 30 3500fd73ebSJerome Brunet 36a502a8f0SHeiner Kallweit #define INT_SOURCES (INTSRC_LINK_DOWN | INTSRC_ANEG_COMPLETE | \ 37a502a8f0SHeiner Kallweit INTSRC_ENERGY_DETECT) 38a502a8f0SHeiner Kallweit 3900fd73ebSJerome Brunet #define BANK_ANALOG_DSP 0 4000fd73ebSJerome Brunet #define BANK_WOL 1 4100fd73ebSJerome Brunet #define BANK_BIST 3 4200fd73ebSJerome Brunet 4300fd73ebSJerome Brunet /* WOL Registers */ 4400fd73ebSJerome Brunet #define LPI_STATUS 0xc 4500fd73ebSJerome Brunet #define LPI_STATUS_RSV12 BIT(12) 4600fd73ebSJerome Brunet 4700fd73ebSJerome Brunet /* BIST Registers */ 4800fd73ebSJerome Brunet #define FR_PLL_CONTROL 0x1b 4900fd73ebSJerome Brunet #define FR_PLL_DIV0 0x1c 5000fd73ebSJerome Brunet #define FR_PLL_DIV1 0x1d 5100fd73ebSJerome Brunet 52fdaa84c3SJerome Brunet static int meson_gxl_open_banks(struct phy_device *phydev) 53fdaa84c3SJerome Brunet { 54fdaa84c3SJerome Brunet int ret; 55fdaa84c3SJerome Brunet 56fdaa84c3SJerome Brunet /* Enable Analog and DSP register Bank access by 57fdaa84c3SJerome Brunet * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register 58fdaa84c3SJerome Brunet */ 59fdaa84c3SJerome Brunet ret = phy_write(phydev, TSTCNTL, 0); 60fdaa84c3SJerome Brunet if (ret) 61fdaa84c3SJerome Brunet return ret; 62fdaa84c3SJerome Brunet ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE); 63fdaa84c3SJerome Brunet if (ret) 64fdaa84c3SJerome Brunet return ret; 65fdaa84c3SJerome Brunet ret = phy_write(phydev, TSTCNTL, 0); 66fdaa84c3SJerome Brunet if (ret) 67fdaa84c3SJerome Brunet return ret; 68fdaa84c3SJerome Brunet return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE); 69fdaa84c3SJerome Brunet } 70fdaa84c3SJerome Brunet 71fdaa84c3SJerome Brunet static void meson_gxl_close_banks(struct phy_device *phydev) 72fdaa84c3SJerome Brunet { 73fdaa84c3SJerome Brunet phy_write(phydev, TSTCNTL, 0); 74fdaa84c3SJerome Brunet } 75fdaa84c3SJerome Brunet 76fdaa84c3SJerome Brunet static int meson_gxl_read_reg(struct phy_device *phydev, 77fdaa84c3SJerome Brunet unsigned int bank, unsigned int reg) 78fdaa84c3SJerome Brunet { 79fdaa84c3SJerome Brunet int ret; 80fdaa84c3SJerome Brunet 81fdaa84c3SJerome Brunet ret = meson_gxl_open_banks(phydev); 82fdaa84c3SJerome Brunet if (ret) 83fdaa84c3SJerome Brunet goto out; 84fdaa84c3SJerome Brunet 85fdaa84c3SJerome Brunet ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ | 86fdaa84c3SJerome Brunet FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) | 87fdaa84c3SJerome Brunet TSTCNTL_TEST_MODE | 88fdaa84c3SJerome Brunet FIELD_PREP(TSTCNTL_READ_ADDRESS, reg)); 89fdaa84c3SJerome Brunet if (ret) 90fdaa84c3SJerome Brunet goto out; 91fdaa84c3SJerome Brunet 92fdaa84c3SJerome Brunet ret = phy_read(phydev, TSTREAD1); 93fdaa84c3SJerome Brunet out: 94fdaa84c3SJerome Brunet /* Close the bank access on our way out */ 95fdaa84c3SJerome Brunet meson_gxl_close_banks(phydev); 96fdaa84c3SJerome Brunet return ret; 97fdaa84c3SJerome Brunet } 98fdaa84c3SJerome Brunet 99fdaa84c3SJerome Brunet static int meson_gxl_write_reg(struct phy_device *phydev, 100fdaa84c3SJerome Brunet unsigned int bank, unsigned int reg, 101fdaa84c3SJerome Brunet uint16_t value) 102fdaa84c3SJerome Brunet { 103fdaa84c3SJerome Brunet int ret; 104fdaa84c3SJerome Brunet 105fdaa84c3SJerome Brunet ret = meson_gxl_open_banks(phydev); 106fdaa84c3SJerome Brunet if (ret) 107fdaa84c3SJerome Brunet goto out; 108fdaa84c3SJerome Brunet 109fdaa84c3SJerome Brunet ret = phy_write(phydev, TSTWRITE, value); 110fdaa84c3SJerome Brunet if (ret) 111fdaa84c3SJerome Brunet goto out; 112fdaa84c3SJerome Brunet 113fdaa84c3SJerome Brunet ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE | 114fdaa84c3SJerome Brunet FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) | 115fdaa84c3SJerome Brunet TSTCNTL_TEST_MODE | 116fdaa84c3SJerome Brunet FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg)); 117fdaa84c3SJerome Brunet 118fdaa84c3SJerome Brunet out: 119fdaa84c3SJerome Brunet /* Close the bank access on our way out */ 120fdaa84c3SJerome Brunet meson_gxl_close_banks(phydev); 121fdaa84c3SJerome Brunet return ret; 122fdaa84c3SJerome Brunet } 123fdaa84c3SJerome Brunet 1247334b3e4SNeil Armstrong static int meson_gxl_config_init(struct phy_device *phydev) 1257334b3e4SNeil Armstrong { 1269042b46eSJerome Brunet int ret; 1279042b46eSJerome Brunet 1287334b3e4SNeil Armstrong /* Enable fractional PLL */ 129fdaa84c3SJerome Brunet ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5); 1309042b46eSJerome Brunet if (ret) 1319042b46eSJerome Brunet return ret; 1327334b3e4SNeil Armstrong 1337334b3e4SNeil Armstrong /* Program fraction FR_PLL_DIV1 */ 134fdaa84c3SJerome Brunet ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a); 1359042b46eSJerome Brunet if (ret) 1369042b46eSJerome Brunet return ret; 1377334b3e4SNeil Armstrong 1387334b3e4SNeil Armstrong /* Program fraction FR_PLL_DIV1 */ 139fdaa84c3SJerome Brunet ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa); 1409042b46eSJerome Brunet if (ret) 1419042b46eSJerome Brunet return ret; 1427334b3e4SNeil Armstrong 143c227ce44SHeiner Kallweit return 0; 1447334b3e4SNeil Armstrong } 1457334b3e4SNeil Armstrong 146f1e2400aSJerome Brunet /* This function is provided to cope with the possible failures of this phy 147f1e2400aSJerome Brunet * during aneg process. When aneg fails, the PHY reports that aneg is done 148f1e2400aSJerome Brunet * but the value found in MII_LPA is wrong: 149f1e2400aSJerome Brunet * - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that 150f1e2400aSJerome Brunet * the link partner (LP) supports aneg but the LP never acked our base 151f1e2400aSJerome Brunet * code word, it is likely that we never sent it to begin with. 152f1e2400aSJerome Brunet * - Late failures: MII_LPA is filled with a value which seems to make sense 153f1e2400aSJerome Brunet * but it actually is not what the LP is advertising. It seems that we 154f1e2400aSJerome Brunet * can detect this using a magic bit in the WOL bank (reg 12 - bit 12). 155f1e2400aSJerome Brunet * If this particular bit is not set when aneg is reported being done, 156f1e2400aSJerome Brunet * it means MII_LPA is likely to be wrong. 157f1e2400aSJerome Brunet * 158f1e2400aSJerome Brunet * In both case, forcing a restart of the aneg process solve the problem. 159f1e2400aSJerome Brunet * When this failure happens, the first retry is usually successful but, 160f1e2400aSJerome Brunet * in some cases, it may take up to 6 retries to get a decent result 161f1e2400aSJerome Brunet */ 1623b3397e2SColin Ian King static int meson_gxl_read_status(struct phy_device *phydev) 163f1e2400aSJerome Brunet { 164f1e2400aSJerome Brunet int ret, wol, lpa, exp; 165f1e2400aSJerome Brunet 166f1e2400aSJerome Brunet if (phydev->autoneg == AUTONEG_ENABLE) { 167f1e2400aSJerome Brunet ret = genphy_aneg_done(phydev); 168f1e2400aSJerome Brunet if (ret < 0) 169f1e2400aSJerome Brunet return ret; 170f1e2400aSJerome Brunet else if (!ret) 171f1e2400aSJerome Brunet goto read_status_continue; 172f1e2400aSJerome Brunet 173fdaa84c3SJerome Brunet /* Aneg is done, let's check everything is fine */ 174fdaa84c3SJerome Brunet wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS); 175f1e2400aSJerome Brunet if (wol < 0) 176f1e2400aSJerome Brunet return wol; 177f1e2400aSJerome Brunet 178f1e2400aSJerome Brunet lpa = phy_read(phydev, MII_LPA); 179f1e2400aSJerome Brunet if (lpa < 0) 180f1e2400aSJerome Brunet return lpa; 181f1e2400aSJerome Brunet 182f1e2400aSJerome Brunet exp = phy_read(phydev, MII_EXPANSION); 183f1e2400aSJerome Brunet if (exp < 0) 184f1e2400aSJerome Brunet return exp; 185f1e2400aSJerome Brunet 18600fd73ebSJerome Brunet if (!(wol & LPI_STATUS_RSV12) || 187f1e2400aSJerome Brunet ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) { 188f1e2400aSJerome Brunet /* Looks like aneg failed after all */ 189f1e2400aSJerome Brunet phydev_dbg(phydev, "LPA corruption - aneg restart\n"); 190f1e2400aSJerome Brunet return genphy_restart_aneg(phydev); 191f1e2400aSJerome Brunet } 192f1e2400aSJerome Brunet } 193f1e2400aSJerome Brunet 194f1e2400aSJerome Brunet read_status_continue: 195f1e2400aSJerome Brunet return genphy_read_status(phydev); 196f1e2400aSJerome Brunet } 197f1e2400aSJerome Brunet 198cf127ff2SJerome Brunet static int meson_gxl_ack_interrupt(struct phy_device *phydev) 199cf127ff2SJerome Brunet { 200cf127ff2SJerome Brunet int ret = phy_read(phydev, INTSRC_FLAG); 201cf127ff2SJerome Brunet 202cf127ff2SJerome Brunet return ret < 0 ? ret : 0; 203cf127ff2SJerome Brunet } 204cf127ff2SJerome Brunet 205cf127ff2SJerome Brunet static int meson_gxl_config_intr(struct phy_device *phydev) 206cf127ff2SJerome Brunet { 207daa5c4d0SJerome Brunet int ret; 208cf127ff2SJerome Brunet 209cf127ff2SJerome Brunet if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { 21084c8f773SIoana Ciornei /* Ack any pending IRQ */ 21184c8f773SIoana Ciornei ret = meson_gxl_ack_interrupt(phydev); 21284c8f773SIoana Ciornei if (ret) 21384c8f773SIoana Ciornei return ret; 21484c8f773SIoana Ciornei 215a502a8f0SHeiner Kallweit ret = phy_write(phydev, INTSRC_MASK, INT_SOURCES); 216cf127ff2SJerome Brunet } else { 217a502a8f0SHeiner Kallweit ret = phy_write(phydev, INTSRC_MASK, 0); 218cf127ff2SJerome Brunet 219daa5c4d0SJerome Brunet /* Ack any pending IRQ */ 220daa5c4d0SJerome Brunet ret = meson_gxl_ack_interrupt(phydev); 22184c8f773SIoana Ciornei } 222daa5c4d0SJerome Brunet 22384c8f773SIoana Ciornei return ret; 224cf127ff2SJerome Brunet } 225cf127ff2SJerome Brunet 2266719e2beSIoana Ciornei static irqreturn_t meson_gxl_handle_interrupt(struct phy_device *phydev) 2276719e2beSIoana Ciornei { 2286719e2beSIoana Ciornei int irq_status; 2296719e2beSIoana Ciornei 2306719e2beSIoana Ciornei irq_status = phy_read(phydev, INTSRC_FLAG); 2316719e2beSIoana Ciornei if (irq_status < 0) { 2326719e2beSIoana Ciornei phy_error(phydev); 2336719e2beSIoana Ciornei return IRQ_NONE; 2346719e2beSIoana Ciornei } 2356719e2beSIoana Ciornei 236a502a8f0SHeiner Kallweit irq_status &= INT_SOURCES; 237a502a8f0SHeiner Kallweit 2386719e2beSIoana Ciornei if (irq_status == 0) 2396719e2beSIoana Ciornei return IRQ_NONE; 2406719e2beSIoana Ciornei 241a502a8f0SHeiner Kallweit /* Aneg-complete interrupt is used for link-up detection */ 242a502a8f0SHeiner Kallweit if (phydev->autoneg == AUTONEG_ENABLE && 243a502a8f0SHeiner Kallweit irq_status == INTSRC_ENERGY_DETECT) 244a502a8f0SHeiner Kallweit return IRQ_HANDLED; 245a502a8f0SHeiner Kallweit 2466719e2beSIoana Ciornei phy_trigger_machine(phydev); 2476719e2beSIoana Ciornei 2486719e2beSIoana Ciornei return IRQ_HANDLED; 2496719e2beSIoana Ciornei } 2506719e2beSIoana Ciornei 2517334b3e4SNeil Armstrong static struct phy_driver meson_gxl_phy[] = { 2527334b3e4SNeil Armstrong { 253fad137c4SJerome Brunet PHY_ID_MATCH_EXACT(0x01814400), 2547334b3e4SNeil Armstrong .name = "Meson GXL Internal PHY", 255dcdecdcfSHeiner Kallweit /* PHY_BASIC_FEATURES */ 256a4307c0eSHeiner Kallweit .flags = PHY_IS_INTERNAL, 257f2f98c1dSTimotej Lazar .soft_reset = genphy_soft_reset, 2587334b3e4SNeil Armstrong .config_init = meson_gxl_config_init, 259f1e2400aSJerome Brunet .read_status = meson_gxl_read_status, 260cf127ff2SJerome Brunet .config_intr = meson_gxl_config_intr, 2616719e2beSIoana Ciornei .handle_interrupt = meson_gxl_handle_interrupt, 2627334b3e4SNeil Armstrong .suspend = genphy_suspend, 2637334b3e4SNeil Armstrong .resume = genphy_resume, 2645c3407abSJerome Brunet }, { 2655c3407abSJerome Brunet PHY_ID_MATCH_EXACT(0x01803301), 2665c3407abSJerome Brunet .name = "Meson G12A Internal PHY", 267dcdecdcfSHeiner Kallweit /* PHY_BASIC_FEATURES */ 2685c3407abSJerome Brunet .flags = PHY_IS_INTERNAL, 2695c3407abSJerome Brunet .soft_reset = genphy_soft_reset, 2705c3407abSJerome Brunet .config_intr = meson_gxl_config_intr, 2716719e2beSIoana Ciornei .handle_interrupt = meson_gxl_handle_interrupt, 2725c3407abSJerome Brunet .suspend = genphy_suspend, 2735c3407abSJerome Brunet .resume = genphy_resume, 274*afc2336fSChris Healy .read_mmd = genphy_read_mmd_unsupported, 275*afc2336fSChris Healy .write_mmd = genphy_write_mmd_unsupported, 2767334b3e4SNeil Armstrong }, 2777334b3e4SNeil Armstrong }; 2787334b3e4SNeil Armstrong 2797334b3e4SNeil Armstrong static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = { 280fad137c4SJerome Brunet { PHY_ID_MATCH_VENDOR(0x01814400) }, 2815c3407abSJerome Brunet { PHY_ID_MATCH_VENDOR(0x01803301) }, 2827334b3e4SNeil Armstrong { } 2837334b3e4SNeil Armstrong }; 2847334b3e4SNeil Armstrong 2857334b3e4SNeil Armstrong module_phy_driver(meson_gxl_phy); 2867334b3e4SNeil Armstrong 2877334b3e4SNeil Armstrong MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl); 2887334b3e4SNeil Armstrong 2897334b3e4SNeil Armstrong MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver"); 2907334b3e4SNeil Armstrong MODULE_AUTHOR("Baoqi wang"); 2917334b3e4SNeil Armstrong MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 292afb4fa47SJerome Brunet MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 2937334b3e4SNeil Armstrong MODULE_LICENSE("GPL"); 294