17334b3e4SNeil Armstrong /* 27334b3e4SNeil Armstrong * Amlogic Meson GXL Internal PHY Driver 37334b3e4SNeil Armstrong * 47334b3e4SNeil Armstrong * Copyright (C) 2015 Amlogic, Inc. All rights reserved. 57334b3e4SNeil Armstrong * Copyright (C) 2016 BayLibre, SAS. All rights reserved. 67334b3e4SNeil Armstrong * Author: Neil Armstrong <narmstrong@baylibre.com> 77334b3e4SNeil Armstrong * 87334b3e4SNeil Armstrong * This program is free software; you can redistribute it and/or modify 97334b3e4SNeil Armstrong * it under the terms of the GNU General Public License as published by 107334b3e4SNeil Armstrong * the Free Software Foundation; either version 2 of the License, or 117334b3e4SNeil Armstrong * (at your option) any later version. 127334b3e4SNeil Armstrong * 137334b3e4SNeil Armstrong * This program is distributed in the hope that it will be useful, but WITHOUT 147334b3e4SNeil Armstrong * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 157334b3e4SNeil Armstrong * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 167334b3e4SNeil Armstrong * more details. 177334b3e4SNeil Armstrong * 187334b3e4SNeil Armstrong */ 197334b3e4SNeil Armstrong #include <linux/kernel.h> 207334b3e4SNeil Armstrong #include <linux/module.h> 217334b3e4SNeil Armstrong #include <linux/mii.h> 227334b3e4SNeil Armstrong #include <linux/ethtool.h> 237334b3e4SNeil Armstrong #include <linux/phy.h> 247334b3e4SNeil Armstrong #include <linux/netdevice.h> 25f1e2400aSJerome Brunet #include <linux/bitfield.h> 267334b3e4SNeil Armstrong 2700fd73ebSJerome Brunet #define TSTCNTL 20 2800fd73ebSJerome Brunet #define TSTCNTL_READ BIT(15) 2900fd73ebSJerome Brunet #define TSTCNTL_WRITE BIT(14) 3000fd73ebSJerome Brunet #define TSTCNTL_REG_BANK_SEL GENMASK(12, 11) 3100fd73ebSJerome Brunet #define TSTCNTL_TEST_MODE BIT(10) 3200fd73ebSJerome Brunet #define TSTCNTL_READ_ADDRESS GENMASK(9, 5) 3300fd73ebSJerome Brunet #define TSTCNTL_WRITE_ADDRESS GENMASK(4, 0) 3400fd73ebSJerome Brunet #define TSTREAD1 21 3500fd73ebSJerome Brunet #define TSTWRITE 23 36cf127ff2SJerome Brunet #define INTSRC_FLAG 29 37cf127ff2SJerome Brunet #define INTSRC_ANEG_PR BIT(1) 38cf127ff2SJerome Brunet #define INTSRC_PARALLEL_FAULT BIT(2) 39cf127ff2SJerome Brunet #define INTSRC_ANEG_LP_ACK BIT(3) 40cf127ff2SJerome Brunet #define INTSRC_LINK_DOWN BIT(4) 41cf127ff2SJerome Brunet #define INTSRC_REMOTE_FAULT BIT(5) 42cf127ff2SJerome Brunet #define INTSRC_ANEG_COMPLETE BIT(6) 43cf127ff2SJerome Brunet #define INTSRC_MASK 30 4400fd73ebSJerome Brunet 4500fd73ebSJerome Brunet #define BANK_ANALOG_DSP 0 4600fd73ebSJerome Brunet #define BANK_WOL 1 4700fd73ebSJerome Brunet #define BANK_BIST 3 4800fd73ebSJerome Brunet 4900fd73ebSJerome Brunet /* WOL Registers */ 5000fd73ebSJerome Brunet #define LPI_STATUS 0xc 5100fd73ebSJerome Brunet #define LPI_STATUS_RSV12 BIT(12) 5200fd73ebSJerome Brunet 5300fd73ebSJerome Brunet /* BIST Registers */ 5400fd73ebSJerome Brunet #define FR_PLL_CONTROL 0x1b 5500fd73ebSJerome Brunet #define FR_PLL_DIV0 0x1c 5600fd73ebSJerome Brunet #define FR_PLL_DIV1 0x1d 5700fd73ebSJerome Brunet 58fdaa84c3SJerome Brunet static int meson_gxl_open_banks(struct phy_device *phydev) 59fdaa84c3SJerome Brunet { 60fdaa84c3SJerome Brunet int ret; 61fdaa84c3SJerome Brunet 62fdaa84c3SJerome Brunet /* Enable Analog and DSP register Bank access by 63fdaa84c3SJerome Brunet * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register 64fdaa84c3SJerome Brunet */ 65fdaa84c3SJerome Brunet ret = phy_write(phydev, TSTCNTL, 0); 66fdaa84c3SJerome Brunet if (ret) 67fdaa84c3SJerome Brunet return ret; 68fdaa84c3SJerome Brunet ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE); 69fdaa84c3SJerome Brunet if (ret) 70fdaa84c3SJerome Brunet return ret; 71fdaa84c3SJerome Brunet ret = phy_write(phydev, TSTCNTL, 0); 72fdaa84c3SJerome Brunet if (ret) 73fdaa84c3SJerome Brunet return ret; 74fdaa84c3SJerome Brunet return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE); 75fdaa84c3SJerome Brunet } 76fdaa84c3SJerome Brunet 77fdaa84c3SJerome Brunet static void meson_gxl_close_banks(struct phy_device *phydev) 78fdaa84c3SJerome Brunet { 79fdaa84c3SJerome Brunet phy_write(phydev, TSTCNTL, 0); 80fdaa84c3SJerome Brunet } 81fdaa84c3SJerome Brunet 82fdaa84c3SJerome Brunet static int meson_gxl_read_reg(struct phy_device *phydev, 83fdaa84c3SJerome Brunet unsigned int bank, unsigned int reg) 84fdaa84c3SJerome Brunet { 85fdaa84c3SJerome Brunet int ret; 86fdaa84c3SJerome Brunet 87fdaa84c3SJerome Brunet ret = meson_gxl_open_banks(phydev); 88fdaa84c3SJerome Brunet if (ret) 89fdaa84c3SJerome Brunet goto out; 90fdaa84c3SJerome Brunet 91fdaa84c3SJerome Brunet ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ | 92fdaa84c3SJerome Brunet FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) | 93fdaa84c3SJerome Brunet TSTCNTL_TEST_MODE | 94fdaa84c3SJerome Brunet FIELD_PREP(TSTCNTL_READ_ADDRESS, reg)); 95fdaa84c3SJerome Brunet if (ret) 96fdaa84c3SJerome Brunet goto out; 97fdaa84c3SJerome Brunet 98fdaa84c3SJerome Brunet ret = phy_read(phydev, TSTREAD1); 99fdaa84c3SJerome Brunet out: 100fdaa84c3SJerome Brunet /* Close the bank access on our way out */ 101fdaa84c3SJerome Brunet meson_gxl_close_banks(phydev); 102fdaa84c3SJerome Brunet return ret; 103fdaa84c3SJerome Brunet } 104fdaa84c3SJerome Brunet 105fdaa84c3SJerome Brunet static int meson_gxl_write_reg(struct phy_device *phydev, 106fdaa84c3SJerome Brunet unsigned int bank, unsigned int reg, 107fdaa84c3SJerome Brunet uint16_t value) 108fdaa84c3SJerome Brunet { 109fdaa84c3SJerome Brunet int ret; 110fdaa84c3SJerome Brunet 111fdaa84c3SJerome Brunet ret = meson_gxl_open_banks(phydev); 112fdaa84c3SJerome Brunet if (ret) 113fdaa84c3SJerome Brunet goto out; 114fdaa84c3SJerome Brunet 115fdaa84c3SJerome Brunet ret = phy_write(phydev, TSTWRITE, value); 116fdaa84c3SJerome Brunet if (ret) 117fdaa84c3SJerome Brunet goto out; 118fdaa84c3SJerome Brunet 119fdaa84c3SJerome Brunet ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE | 120fdaa84c3SJerome Brunet FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) | 121fdaa84c3SJerome Brunet TSTCNTL_TEST_MODE | 122fdaa84c3SJerome Brunet FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg)); 123fdaa84c3SJerome Brunet 124fdaa84c3SJerome Brunet out: 125fdaa84c3SJerome Brunet /* Close the bank access on our way out */ 126fdaa84c3SJerome Brunet meson_gxl_close_banks(phydev); 127fdaa84c3SJerome Brunet return ret; 128fdaa84c3SJerome Brunet } 129fdaa84c3SJerome Brunet 1307334b3e4SNeil Armstrong static int meson_gxl_config_init(struct phy_device *phydev) 1317334b3e4SNeil Armstrong { 1329042b46eSJerome Brunet int ret; 1339042b46eSJerome Brunet 1347334b3e4SNeil Armstrong /* Enable fractional PLL */ 135fdaa84c3SJerome Brunet ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5); 1369042b46eSJerome Brunet if (ret) 1379042b46eSJerome Brunet return ret; 1387334b3e4SNeil Armstrong 1397334b3e4SNeil Armstrong /* Program fraction FR_PLL_DIV1 */ 140fdaa84c3SJerome Brunet ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a); 1419042b46eSJerome Brunet if (ret) 1429042b46eSJerome Brunet return ret; 1437334b3e4SNeil Armstrong 1447334b3e4SNeil Armstrong /* Program fraction FR_PLL_DIV1 */ 145fdaa84c3SJerome Brunet ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa); 1469042b46eSJerome Brunet if (ret) 1479042b46eSJerome Brunet return ret; 1487334b3e4SNeil Armstrong 149c1e53551SJerome Brunet return genphy_config_init(phydev); 1507334b3e4SNeil Armstrong } 1517334b3e4SNeil Armstrong 152f1e2400aSJerome Brunet /* This function is provided to cope with the possible failures of this phy 153f1e2400aSJerome Brunet * during aneg process. When aneg fails, the PHY reports that aneg is done 154f1e2400aSJerome Brunet * but the value found in MII_LPA is wrong: 155f1e2400aSJerome Brunet * - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that 156f1e2400aSJerome Brunet * the link partner (LP) supports aneg but the LP never acked our base 157f1e2400aSJerome Brunet * code word, it is likely that we never sent it to begin with. 158f1e2400aSJerome Brunet * - Late failures: MII_LPA is filled with a value which seems to make sense 159f1e2400aSJerome Brunet * but it actually is not what the LP is advertising. It seems that we 160f1e2400aSJerome Brunet * can detect this using a magic bit in the WOL bank (reg 12 - bit 12). 161f1e2400aSJerome Brunet * If this particular bit is not set when aneg is reported being done, 162f1e2400aSJerome Brunet * it means MII_LPA is likely to be wrong. 163f1e2400aSJerome Brunet * 164f1e2400aSJerome Brunet * In both case, forcing a restart of the aneg process solve the problem. 165f1e2400aSJerome Brunet * When this failure happens, the first retry is usually successful but, 166f1e2400aSJerome Brunet * in some cases, it may take up to 6 retries to get a decent result 167f1e2400aSJerome Brunet */ 1683b3397e2SColin Ian King static int meson_gxl_read_status(struct phy_device *phydev) 169f1e2400aSJerome Brunet { 170f1e2400aSJerome Brunet int ret, wol, lpa, exp; 171f1e2400aSJerome Brunet 172f1e2400aSJerome Brunet if (phydev->autoneg == AUTONEG_ENABLE) { 173f1e2400aSJerome Brunet ret = genphy_aneg_done(phydev); 174f1e2400aSJerome Brunet if (ret < 0) 175f1e2400aSJerome Brunet return ret; 176f1e2400aSJerome Brunet else if (!ret) 177f1e2400aSJerome Brunet goto read_status_continue; 178f1e2400aSJerome Brunet 179fdaa84c3SJerome Brunet /* Aneg is done, let's check everything is fine */ 180fdaa84c3SJerome Brunet wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS); 181f1e2400aSJerome Brunet if (wol < 0) 182f1e2400aSJerome Brunet return wol; 183f1e2400aSJerome Brunet 184f1e2400aSJerome Brunet lpa = phy_read(phydev, MII_LPA); 185f1e2400aSJerome Brunet if (lpa < 0) 186f1e2400aSJerome Brunet return lpa; 187f1e2400aSJerome Brunet 188f1e2400aSJerome Brunet exp = phy_read(phydev, MII_EXPANSION); 189f1e2400aSJerome Brunet if (exp < 0) 190f1e2400aSJerome Brunet return exp; 191f1e2400aSJerome Brunet 19200fd73ebSJerome Brunet if (!(wol & LPI_STATUS_RSV12) || 193f1e2400aSJerome Brunet ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) { 194f1e2400aSJerome Brunet /* Looks like aneg failed after all */ 195f1e2400aSJerome Brunet phydev_dbg(phydev, "LPA corruption - aneg restart\n"); 196f1e2400aSJerome Brunet return genphy_restart_aneg(phydev); 197f1e2400aSJerome Brunet } 198f1e2400aSJerome Brunet } 199f1e2400aSJerome Brunet 200f1e2400aSJerome Brunet read_status_continue: 201f1e2400aSJerome Brunet return genphy_read_status(phydev); 202f1e2400aSJerome Brunet } 203f1e2400aSJerome Brunet 204cf127ff2SJerome Brunet static int meson_gxl_ack_interrupt(struct phy_device *phydev) 205cf127ff2SJerome Brunet { 206cf127ff2SJerome Brunet int ret = phy_read(phydev, INTSRC_FLAG); 207cf127ff2SJerome Brunet 208cf127ff2SJerome Brunet return ret < 0 ? ret : 0; 209cf127ff2SJerome Brunet } 210cf127ff2SJerome Brunet 211cf127ff2SJerome Brunet static int meson_gxl_config_intr(struct phy_device *phydev) 212cf127ff2SJerome Brunet { 213cf127ff2SJerome Brunet u16 val; 214cf127ff2SJerome Brunet 215cf127ff2SJerome Brunet if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { 216cf127ff2SJerome Brunet val = INTSRC_ANEG_PR 217cf127ff2SJerome Brunet | INTSRC_PARALLEL_FAULT 218cf127ff2SJerome Brunet | INTSRC_ANEG_LP_ACK 219cf127ff2SJerome Brunet | INTSRC_LINK_DOWN 220cf127ff2SJerome Brunet | INTSRC_REMOTE_FAULT 221cf127ff2SJerome Brunet | INTSRC_ANEG_COMPLETE; 222cf127ff2SJerome Brunet } else { 223cf127ff2SJerome Brunet val = 0; 224cf127ff2SJerome Brunet } 225cf127ff2SJerome Brunet 226cf127ff2SJerome Brunet return phy_write(phydev, INTSRC_MASK, val); 227cf127ff2SJerome Brunet } 228cf127ff2SJerome Brunet 2297334b3e4SNeil Armstrong static struct phy_driver meson_gxl_phy[] = { 2307334b3e4SNeil Armstrong { 2317334b3e4SNeil Armstrong .phy_id = 0x01814400, 2327334b3e4SNeil Armstrong .phy_id_mask = 0xfffffff0, 2337334b3e4SNeil Armstrong .name = "Meson GXL Internal PHY", 2347334b3e4SNeil Armstrong .features = PHY_BASIC_FEATURES, 235cf127ff2SJerome Brunet .flags = PHY_IS_INTERNAL | PHY_HAS_INTERRUPT, 2367334b3e4SNeil Armstrong .config_init = meson_gxl_config_init, 2377334b3e4SNeil Armstrong .aneg_done = genphy_aneg_done, 238f1e2400aSJerome Brunet .read_status = meson_gxl_read_status, 239cf127ff2SJerome Brunet .ack_interrupt = meson_gxl_ack_interrupt, 240cf127ff2SJerome Brunet .config_intr = meson_gxl_config_intr, 2417334b3e4SNeil Armstrong .suspend = genphy_suspend, 2427334b3e4SNeil Armstrong .resume = genphy_resume, 2437334b3e4SNeil Armstrong }, 2447334b3e4SNeil Armstrong }; 2457334b3e4SNeil Armstrong 2467334b3e4SNeil Armstrong static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = { 2477334b3e4SNeil Armstrong { 0x01814400, 0xfffffff0 }, 2487334b3e4SNeil Armstrong { } 2497334b3e4SNeil Armstrong }; 2507334b3e4SNeil Armstrong 2517334b3e4SNeil Armstrong module_phy_driver(meson_gxl_phy); 2527334b3e4SNeil Armstrong 2537334b3e4SNeil Armstrong MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl); 2547334b3e4SNeil Armstrong 2557334b3e4SNeil Armstrong MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver"); 2567334b3e4SNeil Armstrong MODULE_AUTHOR("Baoqi wang"); 2577334b3e4SNeil Armstrong MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 2587334b3e4SNeil Armstrong MODULE_LICENSE("GPL"); 259