linux/drivers/net/phy/meson-gxl.c
<<
>>
Prefs
   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 genphy_config_init(phydev);
 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        }, {
 239                PHY_ID_MATCH_EXACT(0x01803301),
 240                .name           = "Meson G12A Internal PHY",
 241                /* PHY_BASIC_FEATURES */
 242                .flags          = PHY_IS_INTERNAL,
 243                .soft_reset     = genphy_soft_reset,
 244                .ack_interrupt  = meson_gxl_ack_interrupt,
 245                .config_intr    = meson_gxl_config_intr,
 246                .suspend        = genphy_suspend,
 247                .resume         = genphy_resume,
 248        },
 249};
 250
 251static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
 252        { PHY_ID_MATCH_VENDOR(0x01814400) },
 253        { PHY_ID_MATCH_VENDOR(0x01803301) },
 254        { }
 255};
 256
 257module_phy_driver(meson_gxl_phy);
 258
 259MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl);
 260
 261MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver");
 262MODULE_AUTHOR("Baoqi wang");
 263MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
 264MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
 265MODULE_LICENSE("GPL");
 266